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/record-test/action.yml b/.github/actions/launchable/record-test/action.yml deleted file mode 100644 index 51ad6b086ed9be..00000000000000 --- a/.github/actions/launchable/record-test/action.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Record tests in Launchable -description: >- - Upload the test results to Launchable - -inputs: - os: - required: true - description: An operating system that CI runs on. This value is used in Launchable flavor. - - report-path: - default: launchable_reports.json - required: true - description: A file path of the test report for uploading to Launchable - - test-opts: - default: none - required: false - description: >- - Test options that determine how tests are run. - This value is used in the Launchable flavor. - - srcdir: - required: false - default: ${{ github.workspace }} - description: >- - Directory to (re-)checkout source codes. Launchable retrives the commit information - from the directory. - -outputs: {} # nothing? - -runs: - using: composite - - steps: - - name: Launchable - record tests - working-directory: ${{ inputs.srcdir }} - shell: bash - run: | - test_opts="$(echo ${{ inputs.test-opts }} | sed 's/=/:/g' | sed 's/ //g')" - launchable record tests --flavor os=${{ inputs.os }} --flavor test_task=${{ matrix.test_task }} --flavor test_opts=${test_opts} raw ${{ inputs.report-path }} diff --git a/.github/actions/launchable/setup/action.yml b/.github/actions/launchable/setup/action.yml index 1bc0b55d640208..6d50318ded99f1 100644 --- a/.github/actions/launchable/setup/action.yml +++ b/.github/actions/launchable/setup/action.yml @@ -8,12 +8,29 @@ inputs: required: true description: The file path of the test report for uploading to Launchable + os: + required: true + description: The operating system that CI runs on. This value is used in Launchable flavor. + + test-opts: + default: none + required: false + description: >- + Test options that determine how tests are run. + This value is used in the Launchable flavor. + launchable-token: required: false description: >- Launchable token is needed if you want to run Launchable on your forked repository. See https://github.com/ruby/ruby/wiki/CI-Servers#launchable-ci for details. + builddir: + required: false + default: ${{ github.workspace }} + description: >- + Directory to create Launchable report file. + srcdir: required: false default: ${{ github.workspace }} @@ -21,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 @@ -79,9 +91,10 @@ 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="$(echo ${{ github.ref }} | sed 's/\//_/g')" + github_ref="${{ github.ref }}" + github_ref="${github_ref//\//_}" : # With the --name option, we need to configure a unique identifier for this build. : # To avoid setting the same build name as the CI which runs on other branches, we use the branch name here. : # @@ -90,3 +103,42 @@ runs: launchable record build --name ${github_ref}_${GITHUB_PR_HEAD_SHA} echo "TESTS=${TESTS} --launchable-test-reports=${{ inputs.report-path }}" >> $GITHUB_ENV if: steps.enable-launchable.outputs.enable-launchable + + - name: Variables to report Launchable + id: variables + shell: bash + run: | + set -x + : # flavor + test_opts="${{ inputs.test-opts }}" + test_opts="${test_opts// /}" + test_opts="${test_opts//=/:}" + echo test-opts="$test_opts" >> $GITHUB_OUTPUT + : # report-path from srcdir + if [ "${srcdir}" = "${{ github.workspace }}" ]; then + dir= + else + # srcdir must be equal to or under workspace + dir=$(echo ${srcdir:+${srcdir}/} | sed 's:[^/][^/]*/:../:g') + fi + report_path="${dir}${builddir:+${builddir}/}${report_path}" + echo report-path="${report_path}" >> $GITHUB_OUTPUT + if: steps.enable-launchable.outputs.enable-launchable + env: + srcdir: ${{ inputs.srcdir }} + builddir: ${{ inputs.builddir }} + report_path: ${{ inputs.report-path }} + + - name: Record test results in Launchable + uses: gacts/run-and-post-run@674528335da98a7afc80915ff2b4b860a0b3553a # v1.4.0 + with: + shell: bash + working-directory: ${{ inputs.srcdir }} + post: | + : # record + launchable record tests --flavor os=${{ inputs.os }} --flavor test_task=${{ matrix.test_task }} --flavor test_opts=${test_opts} raw ${report_path} + rm -f ${report_path} + if: ${{ always() && steps.enable-launchable.outputs.enable-launchable }} + env: + test_opts: ${{ steps.variables.outputs.test-opts }} + report_path: ${{ steps.variables.outputs.report-path }} diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml index 598096ef3a46d2..a59f575c991a76 100644 --- a/.github/actions/setup/directories/action.yml +++ b/.github/actions/setup/directories/action.yml @@ -49,6 +49,13 @@ inputs: default: '1' description: The depth of commit history fetched from the remote repository + clean: + required: false + type: boolean + default: '' + description: >- + If set to true, clean build directory. + outputs: {} # nothing? runs: @@ -81,12 +88,12 @@ runs: git config --global init.defaultBranch garbage - if: inputs.checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 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 @@ -135,9 +142,33 @@ runs: - if: inputs.dummy-files == 'true' shell: bash + id: dummy-files working-directory: ${{ inputs.builddir }} run: | : Create dummy files in build dir - for basename in {a..z} {A..Z} {0..9} foo bar test zzz; do - echo > ${basename}.rb "raise %(do not load ${basename}.rb)" + set {{a..z},{A..Z},{0..9},foo,bar,test,zzz}.rb + for file; do \ + echo > $file "raise 'do not load $file'"; \ done + # drop {a..z}.rb if case-insensitive filesystem + grep -F A.rb a.rb > /dev/null && set "${@:27}" + echo clean="cd ${{ inputs.builddir }} && rm $*" >> $GITHUB_OUTPUT + + - if: inputs.clean == 'true' + shell: bash + id: clean + run: | + echo distclean='make -C ${{ inputs.builddir }} distclean' >> $GITHUB_OUTPUT + echo remained-files='find ${{ inputs.builddir }} -ls' >> $GITHUB_OUTPUT + [ "${{ inputs.builddir }}" = "${{ inputs.srcdir }}" ] || + echo final='rmdir ${{ inputs.builddir }}' >> $GITHUB_OUTPUT + + - name: clean + uses: gacts/run-and-post-run@7aec950f3b114c4fcf6012070c3709ecff0eb6f8 # v1.4.0 + with: + working-directory: + post: | + ${{ steps.dummy-files.outputs.clean }} + ${{ steps.clean.outputs.distclean }} + ${{ steps.clean.outputs.remained-files }} + ${{ steps.clean.outputs.final }} 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 69798ed8b9cf0b..589672b4bb53f5 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -39,8 +39,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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]') )}} @@ -61,7 +63,7 @@ jobs: - run: id working-directory: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -72,7 +74,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@70da3bbf44ac06db1b0547ce2acc9380a5270d1e # v1.175.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 2b024de4994190..ac0d5f967f0e1a 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -35,8 +35,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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]') )}} @@ -49,12 +51,12 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@70da3bbf44ac06db1b0547ce2acc9380a5270d1e # v1.175.0 with: ruby-version: ${{ matrix.ruby }} bundler: none - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - uses: ./.github/actions/setup/ubuntu diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 81d4a2a9dc0e36..fcc005239be6a0 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 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 83808784f6673c..076af08aedf5c9 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -37,13 +37,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} @@ -53,7 +55,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@70da3bbf44ac06db1b0547ce2acc9380a5270d1e # v1.175.0 with: ruby-version: '3.0' bundler: none diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index ce4397b35aac02..48434a47a942a2 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 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 4482ac931477bd..c7d0def7aeaf22 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,8 +41,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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]') )}} @@ -57,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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Install libraries if: ${{ contains(matrix.os, 'macos') }} @@ -78,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@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/autobuild@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 with: category: '/language:${{ matrix.language }}' upload: False @@ -116,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@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 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 bec69043741648..659e4e61c9f7e2 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,14 +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, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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]') )}} @@ -230,7 +233,7 @@ jobs: - run: id working-directory: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -240,6 +243,7 @@ jobs: srcdir: src builddir: build makeup: true + clean: true - name: Run configure run: > @@ -252,14 +256,23 @@ jobs: --${{ matrix.entry.shared || 'enable' }}-shared - name: Add to ext/Setup + id: ext-setup run: | mkdir ext cd ext - for ext in {${{ matrix.entry.static-exts }}}/extconf.rb; do - echo ${ext%/extconf.rb} + for ext in {${{ matrix.entry.static-exts }}}; do + echo "${ext}" done >> Setup if: ${{ (matrix.entry.static-exts || '') != '' }} + - name: Clean up ext/Setup + uses: gacts/run-and-post-run@7aec950f3b114c4fcf6012070c3709ecff0eb6f8 # v1.4.0 + with: + shell: bash + working-directory: build + post: rm ext/Setup + if: ${{ steps.ext-setup.outcome == 'success' }} + - run: make showflags - run: make diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml index 5858507ae73763..80112a0af3500b 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@5e5f99653a5b510e8555840e80cbf1514ad4af38 # v2.1.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 e71666ecee3098..54e30129e427fa 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -24,37 +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, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -67,6 +65,7 @@ jobs: srcdir: src builddir: build makeup: true + clean: true dummy-files: ${{ matrix.test_task == 'check' }} # Set fetch-depth: 0 so that Launchable can receive commits information. fetch-depth: 10 @@ -87,11 +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 || (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 @@ -103,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 @@ -116,17 +118,6 @@ jobs: if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - name: Record test results in Launchable - uses: ./.github/actions/launchable/record-test - with: - # We need to configure the `build` directory because - # this composite action is executed in the default working directory. - report-path: ../build/launchable_reports.json - os: ${{ matrix.os }} - test-opts: ${{ matrix.test_opts }} - srcdir: src - if: ${{ always() && steps.enable-launchable.outputs.enable-launchable }} - - uses: ./.github/actions/slack with: label: ${{ matrix.os }} / ${{ matrix.test_task }} diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 824de656aac08d..75cdfbab65f09a 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -57,14 +57,16 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@70da3bbf44ac06db1b0547ce2acc9380a5270d1e # v1.175.0 with: ruby-version: ${{ matrix.baseruby }} @@ -95,7 +97,7 @@ jobs: $result working-directory: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/prism.yml b/.github/workflows/prism.yml index 3ebccf976ed9b2..95a36f1634345f 100644 --- a/.github/workflows/prism.yml +++ b/.github/workflows/prism.yml @@ -47,13 +47,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -90,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 fef9ce8fe13499..1263c20505942f 100644 --- a/.github/workflows/rjit-bindgen.yml +++ b/.github/workflows/rjit-bindgen.yml @@ -38,18 +38,20 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@70da3bbf44ac06db1b0547ce2acc9380a5270d1e # v1.175.0 with: ruby-version: '3.1' - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/rjit.yml b/.github/workflows/rjit.yml index 86610d6bc009a6..7a5e6cf7c0f71b 100644 --- a/.github/workflows/rjit.yml +++ b/.github/workflows/rjit.yml @@ -47,13 +47,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 18ff7719d3a381..00a0638e967cf5 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 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@d39d31e687223d841ef683f52467bd88e9b21c14 # v2.1.27 with: sarif_file: results.sarif diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 95d34448301665..165a0150b54331 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -27,8 +27,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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]') )}} @@ -43,9 +45,9 @@ jobs: - ruby-3.3 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@70da3bbf44ac06db1b0547ce2acc9380a5270d1e # v1.175.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 2e11cca98ee624..b6f989a03ddea4 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -24,37 +24,41 @@ jobs: make: strategy: matrix: - 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 - test_task: check configure: '--disable-yjit' - test_task: check configure: '--enable-shared --enable-load-relative' + - test_task: check + configure: '--with-shared-gc' - test_task: test-bundler-parallel - test_task: test-bundled-gems + - test_task: check + os: ubuntu-20.04 fail-fast: false env: GITPULLOPTIONS: --no-tags origin ${{ github.ref }} RUBY_DEBUG: ci - runs-on: ubuntu-20.04 + runs-on: ${{ matrix.os || 'ubuntu-22.04' }} if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -63,20 +67,21 @@ jobs: with: arch: ${{ matrix.arch }} + - uses: ruby/setup-ruby@70da3bbf44ac06db1b0547ce2acc9380a5270d1e # v1.175.0 + with: + ruby-version: '3.0' + bundler: none + - uses: ./.github/actions/setup/directories with: srcdir: src builddir: build makeup: true + clean: true dummy-files: ${{ matrix.test_task == 'check' }} # Set fetch-depth: 10 so that Launchable can receive commits information. fetch-depth: 10 - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 - with: - ruby-version: '3.0' - bundler: none - - name: Run configure env: arch: ${{ matrix.arch }} @@ -98,11 +103,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 || '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: >- @@ -112,7 +120,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 @@ -124,17 +132,6 @@ jobs: if: ${{ matrix.test_task == 'check' && matrix.skipped_tests }} continue-on-error: ${{ matrix.continue-on-skipped_tests || false }} - - name: Record test results in Launchable - uses: ./.github/actions/launchable/record-test - with: - # We need to configure the `build` directory because - # this composite action is executed in the default working directory. - report-path: ../build/launchable_reports.json - os: ubuntu-20.04 - test-opts: ${{ matrix.configure }} - srcdir: src - if: ${{ always() && steps.enable-launchable.outputs.enable-launchable }} - - uses: ./.github/actions/slack with: label: ${{ matrix.test_task }} ${{ matrix.configure }}${{ matrix.arch }} diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 26a14c58703b4d..e0b67496ac032d 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -53,13 +53,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -98,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@70da3bbf44ac06db1b0547ce2acc9380a5270d1e # v1.175.0 with: ruby-version: '3.0' bundler: none @@ -118,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 \ @@ -134,7 +136,7 @@ jobs: - run: tar cfz ../install.tar.gz -C ../install . - name: Upload artifacts - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: ruby-wasm-install path: ${{ github.workspace }}/install.tar.gz @@ -162,7 +164,7 @@ jobs: - name: Save Pull Request number if: ${{ github.event_name == 'pull_request' }} run: echo "${{ github.event.pull_request.number }}" >> ${{ github.workspace }}/github-pr-info.txt - - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: ${{ github.event_name == 'pull_request' }} with: name: github-pr-info diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index efc40ee87273c3..67cca44fa68320 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -42,8 +42,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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]') )}} @@ -89,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@70da3bbf44ac06db1b0547ce2acc9380a5270d1e # v1.175.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 }} @@ -103,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 }} @@ -121,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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index 2637f9fd23a7de..948644d06eba7f 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -29,13 +29,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - run: RUST_BACKTRACE=1 cargo test working-directory: yjit @@ -69,13 +71,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -89,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 }} @@ -108,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 ab4ba4d9e262c9..fd3bbd8a659b06 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # Check that we don't have linting errors in release mode, too - run: cargo clippy --all-targets --all-features @@ -117,13 +117,15 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || 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@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: sparse-checkout-cone-mode: false sparse-checkout: /.github @@ -136,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@70da3bbf44ac06db1b0547ce2acc9380a5270d1e # v1.175.0 with: ruby-version: '3.0' bundler: none @@ -163,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" - timeout-minutes: 60 + 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: 90 env: - RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'minitest,test-unit,debug,bigdecimal,drb,typeprof' + RUBY_TESTOPTS: '-v --tty=no' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' SYNTAX_SUGGEST_TIMEOUT: '5' YJIT_BINDGEN_DIFF_OPTS: '--exit-code' diff --git a/.gitignore b/.gitignore index 89b817b4f666ff..d7a8b978c98aee 100644 --- a/.gitignore +++ b/.gitignore @@ -261,12 +261,16 @@ lcov*.info /lib/prism/dispatcher.rb /lib/prism/dot_visitor.rb /lib/prism/dsl.rb +/lib/prism/inspect_visitor.rb /lib/prism/mutation_compiler.rb /lib/prism/node.rb +/lib/prism/reflection.rb /lib/prism/serialize.rb /lib/prism/visitor.rb /prism/api_node.c /prism/ast.h +/prism/diagnostic.c +/prism/diagnostic.h /prism/node.c /prism/prettyprint.c /prism/serialize.c diff --git a/.mailmap b/.mailmap index 36bef53d644de6..213a0f49164c0f 100644 --- a/.mailmap +++ b/.mailmap @@ -3,429 +3,429 @@ git[bot] git svn # a_matsuda -Akira Matsuda (a_matsuda) -Akira Matsuda (a_matsuda) +Akira Matsuda +Akira Matsuda # aamine -Minero Aoki (aamine) -Minero Aoki (aamine) +Minero Aoki +Minero Aoki # akira -akira yamada (akira) -## akira yamada (akira) -akira yamada (akira) -akira yamada (akira) +akira yamada +## akira yamada +akira yamada +akira yamada # akiyoshi -AKIYOSHI, Masamichi (akiyoshi) -AKIYOSHI, Masamichi (akiyoshi) +AKIYOSHI, Masamichi +AKIYOSHI, Masamichi # akr -Tanaka Akira (akr) -Tanaka Akira (akr) +Tanaka Akira +Tanaka Akira # arai -Koji Arai (arai) -Koji Arai (arai) +Koji Arai +Koji Arai # arton -Akio Tajima (arton) -Akio Tajima (arton) +Akio Tajima +Akio Tajima # aycabta -aycabta (aycabta) -aycabta (aycabta) +aycabta +aycabta # ayumin -Ayumu AIZAWA (ayumin) -Ayumu AIZAWA (ayumin) +Ayumu AIZAWA +Ayumu AIZAWA # azav -Alexander Zavorine (azav) -Alexander Zavorine (azav) +Alexander Zavorine +Alexander Zavorine # charliesome -Charlie Somerville (charliesome) -Charlie Somerville (charliesome) +Charlie Somerville +Charlie Somerville # dave -Dave Thomas (dave) -Dave Thomas (dave) +Dave Thomas +Dave Thomas # davidflanagan -David Flanagan (davidflanagan) -David Flanagan (davidflanagan) -David Flanagan (davidflanagan) -David Flanagan (davidflanagan) +David Flanagan +David Flanagan +David Flanagan +David Flanagan # dblack -David A. Black (dblack) -David A. Black (dblack) -David A. Black (dblack) -David A. Black (dblack) +David A. Black +David A. Black +David A. Black +David A. Black # drbrain -Eric Hodel (drbrain) -Eric Hodel (drbrain) +Eric Hodel +Eric Hodel # duerst -Martin Dürst (duerst) -Martin Dürst (duerst) +Martin Dürst +Martin Dürst # eban -WATANABE Hirofumi (eban) -WATANABE Hirofumi (eban) +WATANABE Hirofumi +WATANABE Hirofumi # emboss -Martin Bosslet (emboss) -Martin Bosslet (emboss) -Martin Bosslet (emboss) +Martin Bosslet +Martin Bosslet +Martin Bosslet # eregon -Benoit Daloze (eregon) -Benoit Daloze (eregon) +Benoit Daloze +Benoit Daloze # evan -Evan Phoenix (evan) -Evan Phoenix (evan) -Evan Phoenix (evan) +Evan Phoenix +Evan Phoenix +Evan Phoenix # glass -Masaki Matsushita (glass) -Masaki Matsushita (glass) +Masaki Matsushita +Masaki Matsushita # gogotanaka -Kazuki Tanaka (gogotanaka) -Kazuki Tanaka (gogotanaka) +Kazuki Tanaka +Kazuki Tanaka # gotoken -Kentaro Goto (gotoken) -Kentaro Goto (gotoken) +Kentaro Goto +Kentaro Goto # gotoyuzo -GOTOU Yuuzou (gotoyuzo) -GOTOU Yuuzou (gotoyuzo) +GOTOU Yuuzou +GOTOU Yuuzou # gsinclair -Gavin Sinclair (gsinclair) -Gavin Sinclair (gsinclair) +Gavin Sinclair +Gavin Sinclair # H_Konishi -KONISHI Hiromasa (H_Konishi) -KONISHI Hiromasa (H_Konishi) +KONISHI Hiromasa +KONISHI Hiromasa # headius -Charles Oliver Nutter (headius) -Charles Oliver Nutter (headius) +Charles Oliver Nutter +Charles Oliver Nutter # hone -Terence Lee (hone) -Terence Lee (hone) +Terence Lee +Terence Lee # hsbt -Hiroshi SHIBATA (hsbt) -Hiroshi SHIBATA (hsbt) +Hiroshi SHIBATA +Hiroshi SHIBATA # iwamatsu -Nobuhiro Iwamatsu (iwamatsu) -Nobuhiro Iwamatsu (iwamatsu) +Nobuhiro Iwamatsu +Nobuhiro Iwamatsu # jeg2 -James Edward Gray II (jeg2) -James Edward Gray II (jeg2) +James Edward Gray II +James Edward Gray II # jim -Jim Weirich (jim) -Jim Weirich (jim) +Jim Weirich +Jim Weirich # k0kubun -Takashi Kokubun (k0kubun) -Takashi Kokubun (k0kubun) +Takashi Kokubun +Takashi Kokubun # kanemoto -Yutaka Kanemoto (kanemoto) -Yutaka Kanemoto (kanemoto) +Yutaka Kanemoto +Yutaka Kanemoto # katsu -UENO Katsuhiro (katsu) -UENO Katsuhiro (katsu) +UENO Katsuhiro +UENO Katsuhiro # kazu -Kazuhiro NISHIYAMA (kazu) -Kazuhiro NISHIYAMA (kazu) +Kazuhiro NISHIYAMA +Kazuhiro NISHIYAMA # keiju -Keiju Ishitsuka (keiju) -Keiju Ishitsuka (keiju) +Keiju Ishitsuka +Keiju Ishitsuka # knu -Akinori MUSHA (knu) -Akinori MUSHA (knu) +Akinori MUSHA +Akinori MUSHA # ko1 -Koichi Sasada (ko1) -Koichi Sasada (ko1) +Koichi Sasada +Koichi Sasada # kosaki -KOSAKI Motohiro (kosaki) -KOSAKI Motohiro (kosaki) +KOSAKI Motohiro +KOSAKI Motohiro # kosako -K.Kosako (kosako) -K.Kosako (kosako) +K.Kosako +K.Kosako # kou -Sutou Kouhei (kou) -Sutou Kouhei (kou) -Sutou Kouhei (kou) +Sutou Kouhei +Sutou Kouhei +Sutou Kouhei # kouji -Kouji Takao (kouji) -Kouji Takao (kouji) -Kouji Takao (kouji) +Kouji Takao +Kouji Takao +Kouji Takao # ksaito -Kazuo Saito (ksaito) -Kazuo Saito (ksaito) +Kazuo Saito +Kazuo Saito # ktsj -Kazuki Tsujimoto (ktsj) -Kazuki Tsujimoto (ktsj) +Kazuki Tsujimoto +Kazuki Tsujimoto # luislavena -Luis Lavena (luislavena) -Luis Lavena (luislavena) +Luis Lavena +Luis Lavena # mame -Yusuke Endoh (mame) -## Yusuke Endoh (mame) -Yusuke Endoh (mame) +Yusuke Endoh +## Yusuke Endoh +Yusuke Endoh # marcandre -Marc-Andre Lafortune (marcandre) -Marc-Andre Lafortune (marcandre) -Marc-Andre Lafortune (marcandre) +Marc-Andre Lafortune +Marc-Andre Lafortune +Marc-Andre Lafortune # matz -Yukihiro "Matz" Matsumoto (matz) -Yukihiro "Matz" Matsumoto (matz) -Yukihiro "Matz" Matsumoto (matz) +Yukihiro "Matz" Matsumoto +Yukihiro "Matz" Matsumoto +Yukihiro "Matz" Matsumoto # michal -Michal Rokos (michal) -Michal Rokos (michal) +Michal Rokos +Michal Rokos # mneumann -Michael Neumann (mneumann) -Michael Neumann (mneumann) +Michael Neumann +Michael Neumann # mrkn -Kenta Murata (mrkn) -Kenta Murata (mrkn) -Kenta Murata (mrkn) <3959+mrkn@users.noreply.github.com> -Kenta Murata (mrkn) +Kenta Murata +Kenta Murata +Kenta Murata <3959+mrkn@users.noreply.github.com> +Kenta Murata # nagachika -nagachika (nagachika) -nagachika (nagachika) +nagachika +nagachika # nagai -Hidetoshi NAGAI (nagai) -Hidetoshi NAGAI (nagai) +Hidetoshi NAGAI +Hidetoshi NAGAI # nahi -Hiroshi Nakamura (nahi) -Hiroshi Nakamura (nahi) +Hiroshi Nakamura +Hiroshi Nakamura # nari -Narihiro Nakamura (nari) -Narihiro Nakamura (nari) +Narihiro Nakamura +Narihiro Nakamura # naruse -NARUSE, Yui (naruse) -NARUSE, Yui (naruse) -NARUSE, Yui (naruse) +NARUSE, Yui +NARUSE, Yui +NARUSE, Yui # ngoto -Naohisa Goto (ngoto) -Naohisa Goto (ngoto) +Naohisa Goto +Naohisa Goto # nobu -Nobuyoshi Nakada (nobu) -Nobuyoshi Nakada (nobu) +Nobuyoshi Nakada +Nobuyoshi Nakada # normal -Eric Wong (normal) -Eric Wong (normal) -Eric Wong (normal) +Eric Wong +Eric Wong +Eric Wong # ntalbott -Nathaniel Talbott (ntalbott) -Nathaniel Talbott (ntalbott) +Nathaniel Talbott +Nathaniel Talbott # ocean -Hirokazu Yamamoto (ocean) -Hirokazu Yamamoto (ocean) +Hirokazu Yamamoto +Hirokazu Yamamoto # odaira -Rei Odaira (odaira) -Rei Odaira (odaira) -Rei Odaira (odaira) +Rei Odaira +Rei Odaira +Rei Odaira # okkez -okkez (okkez) -okkez (okkez) +okkez +okkez # rhe -Kazuki Yamaguchi (rhe) -Kazuki Yamaguchi (rhe) +Kazuki Yamaguchi +Kazuki Yamaguchi # ryan -Ryan Davis (ryan) -Ryan Davis (ryan) -Ryan Davis (ryan) +Ryan Davis +Ryan Davis +Ryan Davis # samuel -Samuel Williams (samuel) -Samuel Williams (samuel) +Samuel Williams +Samuel Williams # seki -Masatoshi SEKI (seki) -Masatoshi SEKI (seki) +Masatoshi SEKI +Masatoshi SEKI # ser -Sean Russell (ser) -Sean Russell (ser) +Sean Russell +Sean Russell # shigek -Shigeo Kobayashi (shigek) -Shigeo Kobayashi (shigek) +Shigeo Kobayashi +Shigeo Kobayashi # shirosaki -Hiroshi Shirosaki (shirosaki) -Hiroshi Shirosaki (shirosaki) +Hiroshi Shirosaki +Hiroshi Shirosaki # sho-h -Sho Hashimoto (sho-h) -Sho Hashimoto (sho-h) -Sho Hashimoto (sho-h) -Sho Hashimoto (sho-h) +Sho Hashimoto +Sho Hashimoto +Sho Hashimoto +Sho Hashimoto # shugo -Shugo Maeda (shugo) -Shugo Maeda (shugo) +Shugo Maeda +Shugo Maeda # shyouhei -卜部昌平 (shyouhei) -卜部昌平 (shyouhei) +卜部昌平 +卜部昌平 # siena -Siena. (siena) -Siena. (siena) +Siena. +Siena. # sonots -sonots (sonots) -sonots (sonots) +sonots +sonots # sorah -Sorah Fukumori (sorah) -Sorah Fukumori (sorah) +Sorah Fukumori +Sorah Fukumori # stomar -Marcus Stollsteimer (stomar) -Marcus Stollsteimer (stomar) +Marcus Stollsteimer +Marcus Stollsteimer # suke -Masaki Suketa (suke) -Masaki Suketa (suke) +Masaki Suketa +Masaki Suketa # tadd -Tadashi Saito (tadd) -Tadashi Saito (tadd) +Tadashi Saito +Tadashi Saito # tadf -Tadayoshi Funaba (tadf) -Tadayoshi Funaba (tadf) +Tadayoshi Funaba +Tadayoshi Funaba # takano32 -TAKANO Mitsuhiro (takano32) -TAKANO Mitsuhiro (takano32) +TAKANO Mitsuhiro +TAKANO Mitsuhiro # tarui -Masaya Tarui (tarui) -Masaya Tarui (tarui) +Masaya Tarui +Masaya Tarui # technorama -Technorama Ltd. (technorama) -Technorama Ltd. (technorama) +Technorama Ltd. +Technorama Ltd. # tenderlove -Aaron Patterson (tenderlove) -Aaron Patterson (tenderlove) +Aaron Patterson +Aaron Patterson # tmm1 -Aman Gupta (tmm1) -Aman Gupta (tmm1) +Aman Gupta +Aman Gupta # ts -Guy Decoux (ts) -Guy Decoux (ts) +Guy Decoux +Guy Decoux # ttate -Takaaki Tateishi (ttate) -## Takaaki Tateishi (ttate) -Takaaki Tateishi (ttate) +Takaaki Tateishi +## Takaaki Tateishi +Takaaki Tateishi # uema2 -Takaaki Uematsu (uema2) -Takaaki Uematsu (uema2) +Takaaki Uematsu +Takaaki Uematsu # usa -U.Nakamura (usa) -U.Nakamura (usa) -U.Nakamura (usa) +U.Nakamura +U.Nakamura +U.Nakamura # wakou -Wakou Aoyama (wakou) -Wakou Aoyama (wakou) +Wakou Aoyama +Wakou Aoyama # wanabe -wanabe (wanabe) -wanabe (wanabe) +wanabe +wanabe # watson1978 -Watson (watson1978) -Watson (watson1978) +Watson +Watson # wew -William Webber (wew) -William Webber (wew) +William Webber +William Webber # why -why the lucky stiff (why) -why the lucky stiff (why) +why the lucky stiff +why the lucky stiff # xibbar -Takeyuki FUJIOKA (xibbar) -Takeyuki FUJIOKA (xibbar) +Takeyuki FUJIOKA +Takeyuki FUJIOKA # yugui -Yuki Yugui Sonoda (yugui) -Yuki Yugui Sonoda (yugui) +Yuki Yugui Sonoda +Yuki Yugui Sonoda # yui-knk -yui-knk (yui-knk) -yui-knk (yui-knk) +yui-knk +yui-knk # yuki -Yuki Nishijima (yuki) -Yuki Nishijima (yuki) -Yuki Nishijima (yuki) +Yuki Nishijima +Yuki Nishijima +Yuki Nishijima # zsombor -Dee Zsombor (zsombor) -Dee Zsombor (zsombor) +Dee Zsombor +Dee Zsombor # zzak -zzak (zzak) -zzak (zzak) +zzak +zzak diff --git a/.travis.yml b/.travis.yml index 5235f5548adb8b..06de3dd493263f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -113,14 +113,15 @@ matrix: - <<: *arm32-linux allow_failures: # Allow failures for the unstable jobs. - - name: arm64-linux + # - name: arm64-linux - name: ppc64le-linux - name: s390x-linux # The 2nd arm64 pipeline may be unstable. - - name: arm32-linux + # - name: arm32-linux 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 2579f985fb1a64..1451b0ed98a81d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,17 +7,35 @@ 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]] + +* Range + + * Range#size now raises TypeError if the range is not iterable. [[Misc #18984]] + ## Stdlib updates The following default gems are updated. @@ -27,30 +45,35 @@ The following default gems are updated. * erb 4.0.4 * fiddle 1.1.3 * io-console 0.7.2 -* irb 1.11.2 +* irb 1.12.0 +* json 2.7.2 * net-http 0.4.1 -* prism 0.24.0 -* reline 0.4.3 +* optparse 0.5.0 +* prism 0.26.0 +* rdoc 6.6.3.1 +* reline 0.5.3 +* 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 +81,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. @@ -67,8 +90,8 @@ See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/log ## Compatibility issues * Error messages and backtrace displays have been changed. - * Use a single quote instead of a backtick as a opening quote. [Feature #16495] - * Display a class name before a method name (only when the class has a permanent name). [Feature #19117] + * Use a single quote instead of a backtick as a opening quote. [[Feature #16495]] + * Display a class name before a method name (only when the class has a permanent name). [[Feature #19117]] * `Kernel#caller`, `Thread::Backtrace::Location`'s methods, etc. are also changed accordingly. ``` Old: @@ -84,14 +107,37 @@ 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 +## Miscellaneous changes + +* Passing a block to a method which doesn't use the passed block will show + a warning on verbose mode (`-w`). + [[Feature #15554]] + +* Redefining some core methods that are specially optimized by the interpeter + and JIT like `String.freeze` or `Integer#+` now emits a performance class + warning (`-W:performance` or `Warning[:performance] = true`). + [[Feature #20429]] + +[Feature #13557]: https://bugs.ruby-lang.org/issues/13557 +[Feature #15554]: https://bugs.ruby-lang.org/issues/15554 [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 +[Misc #18984]: https://bugs.ruby-lang.org/issues/18984 [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 +[Feature #20429]: https://bugs.ruby-lang.org/issues/20429 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 a6cfd9bd22ad62..56f3584e1dbc81 100644 --- a/array.c +++ b/array.c @@ -1733,6 +1733,16 @@ static VALUE rb_ary_aref2(VALUE ary, VALUE b, VALUE e); * * Returns elements from +self+; does not modify +self+. * + * In brief: + * + * a = [:foo, 'bar', 2] + * a[0] # => :foo + * a[-1] # => 2 + * a[1, 2] # => ["bar", 2] + * a[0..1] # => [:foo, "bar"] + * a[0..-2] # => [:foo, "bar"] + * a[-2..2] # => ["bar", 2] + * * When a single Integer argument +index+ is given, returns the element at offset +index+: * * a = [:foo, 'bar', 2] @@ -2284,6 +2294,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+: @@ -3358,6 +3393,9 @@ rb_ary_sort_bang(VALUE ary) rb_ary_unshare(ary); FL_SET_EMBED(ary); } + if (ARY_EMBED_LEN(tmp) > ARY_CAPA(ary)) { + ary_resize_capa(ary, ARY_EMBED_LEN(tmp)); + } ary_memcpy(ary, 0, ARY_EMBED_LEN(tmp), ARY_EMBED_PTR(tmp)); ARY_SET_LEN(ary, ARY_EMBED_LEN(tmp)); } @@ -6608,6 +6646,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/array.rb b/array.rb index 8e809b35c9a58f..f63ff000568186 100644 --- a/array.rb +++ b/array.rb @@ -43,6 +43,8 @@ class Array # Related: #each_index, #reverse_each. def each Primitive.attr! :inline_block + Primitive.attr! :use_block + unless defined?(yield) return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)' end diff --git a/ast.c b/ast.c index 66e237b1f22e67..48db3982ac5b34 100644 --- a/ast.c +++ b/ast.c @@ -16,7 +16,7 @@ static VALUE rb_mAST; static VALUE rb_cNode; struct ASTNodeData { - rb_ast_t *ast; + VALUE vast; const NODE *node; }; @@ -24,14 +24,16 @@ static void node_gc_mark(void *ptr) { struct ASTNodeData *data = (struct ASTNodeData *)ptr; - rb_gc_mark((VALUE)data->ast); + rb_gc_mark(data->vast); } static size_t node_memsize(const void *ptr) { struct ASTNodeData *data = (struct ASTNodeData *)ptr; - return rb_ast_memsize(data->ast); + rb_ast_t *ast = rb_ruby_ast_data_get(data->vast); + + return sizeof(struct ASTNodeData) + rb_ast_memsize(ast); } static const rb_data_type_t rb_node_type = { @@ -44,22 +46,22 @@ static const rb_data_type_t rb_node_type = { static VALUE rb_ast_node_alloc(VALUE klass); static void -setup_node(VALUE obj, rb_ast_t *ast, const NODE *node) +setup_node(VALUE obj, VALUE vast, const NODE *node) { struct ASTNodeData *data; TypedData_Get_Struct(obj, struct ASTNodeData, &rb_node_type, data); - data->ast = ast; + data->vast = vast; data->node = node; } static VALUE -ast_new_internal(rb_ast_t *ast, const NODE *node) +ast_new_internal(VALUE vast, const NODE *node) { VALUE obj; obj = rb_ast_node_alloc(rb_cNode); - setup_node(obj, ast, node); + setup_node(obj, vast, node); return obj; } @@ -74,14 +76,16 @@ ast_parse_new(void) } static VALUE -ast_parse_done(rb_ast_t *ast) +ast_parse_done(VALUE vast) { + rb_ast_t *ast = rb_ruby_ast_data_get(vast); + if (!ast->body.root) { rb_ast_dispose(ast); rb_exc_raise(GET_EC()->errinfo); } - return ast_new_internal(ast, (NODE *)ast->body.root); + return ast_new_internal(vast, (NODE *)ast->body.root); } static VALUE @@ -93,15 +97,15 @@ ast_s_parse(rb_execution_context_t *ec, VALUE module, VALUE str, VALUE keep_scri static VALUE rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { - rb_ast_t *ast = 0; + VALUE vast; StringValue(str); VALUE vparser = ast_parse_new(); - if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser, Qtrue); + if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); - ast = rb_parser_compile_string_path(vparser, Qnil, str, 1); - return ast_parse_done(ast); + vast = rb_parser_compile_string_path(vparser, Qnil, str, 1); + return ast_parse_done(vast); } static VALUE @@ -114,48 +118,35 @@ static VALUE rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { VALUE f; - rb_ast_t *ast = 0; + VALUE vast = Qnil; rb_encoding *enc = rb_utf8_encoding(); f = rb_file_open_str(path, "r"); rb_funcall(f, rb_intern("set_encoding"), 2, rb_enc_from_encoding(enc), rb_str_new_cstr("-")); VALUE vparser = ast_parse_new(); - if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser, Qtrue); + if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); - ast = rb_parser_compile_file_path(vparser, Qnil, f, 1); + vast = rb_parser_compile_file_path(vparser, Qnil, f, 1); rb_io_close(f); - return ast_parse_done(ast); -} - -static VALUE -lex_array(VALUE array, int index) -{ - VALUE str = rb_ary_entry(array, index); - if (!NIL_P(str)) { - StringValue(str); - if (!rb_enc_asciicompat(rb_enc_get(str))) { - rb_raise(rb_eArgError, "invalid source encoding"); - } - } - return str; + return ast_parse_done(vast); } static VALUE rb_ast_parse_array(VALUE array, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { - rb_ast_t *ast = 0; + VALUE vast = Qnil; array = rb_check_array_type(array); VALUE vparser = ast_parse_new(); - if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser, Qtrue); + if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); - ast = rb_parser_compile_generic(vparser, lex_array, Qnil, array, 1); - return ast_parse_done(ast); + vast = rb_parser_compile_array(vparser, Qnil, array, 1); + return ast_parse_done(vast); } -static VALUE node_children(rb_ast_t*, const NODE*); +static VALUE node_children(VALUE, const NODE*); static VALUE node_find(VALUE self, const int node_id) @@ -167,7 +158,7 @@ node_find(VALUE self, const int node_id) if (nd_node_id(data->node) == node_id) return self; - ary = node_children(data->ast, data->node); + ary = node_children(data->vast, data->node); for (i = 0; i < RARRAY_LEN(ary); i++) { VALUE child = RARRAY_AREF(ary, i); @@ -183,29 +174,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 +235,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) { @@ -313,10 +281,10 @@ ast_node_node_id(rb_execution_context_t *ec, VALUE self) return INT2FIX(nd_node_id(data->node)); } -#define NEW_CHILD(ast, node) node ? ast_new_internal(ast, node) : Qnil +#define NEW_CHILD(vast, node) (node ? ast_new_internal(vast, node) : Qnil) static VALUE -rb_ary_new_from_node_args(rb_ast_t *ast, long n, ...) +rb_ary_new_from_node_args(VALUE vast, long n, ...) { va_list ar; VALUE ary; @@ -328,39 +296,39 @@ rb_ary_new_from_node_args(rb_ast_t *ast, long n, ...) for (i=0; ind_head)); + rb_ary_push(ary, NEW_CHILD(vast, node->nd_head)); } while (node->nd_next && nd_type_p(node->nd_next, NODE_BLOCK) && (node = RNODE_BLOCK(node->nd_next), 1)); if (node->nd_next) { - rb_ary_push(ary, NEW_CHILD(ast, node->nd_next)); + rb_ary_push(ary, NEW_CHILD(vast, node->nd_next)); } return ary; } static VALUE -dump_array(rb_ast_t *ast, const struct RNode_LIST *node) +dump_array(VALUE vast, const struct RNode_LIST *node) { VALUE ary = rb_ary_new(); - rb_ary_push(ary, NEW_CHILD(ast, node->nd_head)); + rb_ary_push(ary, NEW_CHILD(vast, node->nd_head)); while (node->nd_next && nd_type_p(node->nd_next, NODE_LIST)) { node = RNODE_LIST(node->nd_next); - rb_ary_push(ary, NEW_CHILD(ast, node->nd_head)); + rb_ary_push(ary, NEW_CHILD(vast, node->nd_head)); } - rb_ary_push(ary, NEW_CHILD(ast, node->nd_next)); + rb_ary_push(ary, NEW_CHILD(vast, node->nd_next)); return ary; } @@ -382,155 +350,155 @@ no_name_rest(void) } static VALUE -rest_arg(rb_ast_t *ast, const NODE *rest_arg) +rest_arg(VALUE vast, const NODE *rest_arg) { - return NODE_NAMED_REST_P(rest_arg) ? NEW_CHILD(ast, rest_arg) : no_name_rest(); + return NODE_NAMED_REST_P(rest_arg) ? NEW_CHILD(vast, rest_arg) : no_name_rest(); } static VALUE -node_children(rb_ast_t *ast, const NODE *node) +node_children(VALUE vast, const NODE *node) { char name[sizeof("$") + DECIMAL_SIZE_OF(long)]; enum node_type type = nd_type(node); switch (type) { case NODE_BLOCK: - return dump_block(ast, RNODE_BLOCK(node)); + return dump_block(vast, RNODE_BLOCK(node)); case NODE_IF: - return rb_ary_new_from_node_args(ast, 3, RNODE_IF(node)->nd_cond, RNODE_IF(node)->nd_body, RNODE_IF(node)->nd_else); + return rb_ary_new_from_node_args(vast, 3, RNODE_IF(node)->nd_cond, RNODE_IF(node)->nd_body, RNODE_IF(node)->nd_else); case NODE_UNLESS: - return rb_ary_new_from_node_args(ast, 3, RNODE_UNLESS(node)->nd_cond, RNODE_UNLESS(node)->nd_body, RNODE_UNLESS(node)->nd_else); + return rb_ary_new_from_node_args(vast, 3, RNODE_UNLESS(node)->nd_cond, RNODE_UNLESS(node)->nd_body, RNODE_UNLESS(node)->nd_else); case NODE_CASE: - return rb_ary_new_from_node_args(ast, 2, RNODE_CASE(node)->nd_head, RNODE_CASE(node)->nd_body); + return rb_ary_new_from_node_args(vast, 2, RNODE_CASE(node)->nd_head, RNODE_CASE(node)->nd_body); case NODE_CASE2: - return rb_ary_new_from_node_args(ast, 2, RNODE_CASE2(node)->nd_head, RNODE_CASE2(node)->nd_body); + return rb_ary_new_from_node_args(vast, 2, RNODE_CASE2(node)->nd_head, RNODE_CASE2(node)->nd_body); case NODE_CASE3: - return rb_ary_new_from_node_args(ast, 2, RNODE_CASE3(node)->nd_head, RNODE_CASE3(node)->nd_body); + return rb_ary_new_from_node_args(vast, 2, RNODE_CASE3(node)->nd_head, RNODE_CASE3(node)->nd_body); case NODE_WHEN: - return rb_ary_new_from_node_args(ast, 3, RNODE_WHEN(node)->nd_head, RNODE_WHEN(node)->nd_body, RNODE_WHEN(node)->nd_next); + return rb_ary_new_from_node_args(vast, 3, RNODE_WHEN(node)->nd_head, RNODE_WHEN(node)->nd_body, RNODE_WHEN(node)->nd_next); case NODE_IN: - return rb_ary_new_from_node_args(ast, 3, RNODE_IN(node)->nd_head, RNODE_IN(node)->nd_body, RNODE_IN(node)->nd_next); + return rb_ary_new_from_node_args(vast, 3, RNODE_IN(node)->nd_head, RNODE_IN(node)->nd_body, RNODE_IN(node)->nd_next); case NODE_WHILE: case NODE_UNTIL: - return rb_ary_push(rb_ary_new_from_node_args(ast, 2, RNODE_WHILE(node)->nd_cond, RNODE_WHILE(node)->nd_body), + return rb_ary_push(rb_ary_new_from_node_args(vast, 2, RNODE_WHILE(node)->nd_cond, RNODE_WHILE(node)->nd_body), RBOOL(RNODE_WHILE(node)->nd_state)); case NODE_ITER: case NODE_FOR: - return rb_ary_new_from_node_args(ast, 2, RNODE_ITER(node)->nd_iter, RNODE_ITER(node)->nd_body); + return rb_ary_new_from_node_args(vast, 2, RNODE_ITER(node)->nd_iter, RNODE_ITER(node)->nd_body); case NODE_FOR_MASGN: - return rb_ary_new_from_node_args(ast, 1, RNODE_FOR_MASGN(node)->nd_var); + return rb_ary_new_from_node_args(vast, 1, RNODE_FOR_MASGN(node)->nd_var); case NODE_BREAK: - return rb_ary_new_from_node_args(ast, 1, RNODE_BREAK(node)->nd_stts); + return rb_ary_new_from_node_args(vast, 1, RNODE_BREAK(node)->nd_stts); case NODE_NEXT: - return rb_ary_new_from_node_args(ast, 1, RNODE_NEXT(node)->nd_stts); + return rb_ary_new_from_node_args(vast, 1, RNODE_NEXT(node)->nd_stts); case NODE_RETURN: - return rb_ary_new_from_node_args(ast, 1, RNODE_RETURN(node)->nd_stts); + return rb_ary_new_from_node_args(vast, 1, RNODE_RETURN(node)->nd_stts); case NODE_REDO: - return rb_ary_new_from_node_args(ast, 0); + return rb_ary_new_from_node_args(vast, 0); case NODE_RETRY: - return rb_ary_new_from_node_args(ast, 0); + return rb_ary_new_from_node_args(vast, 0); case NODE_BEGIN: - return rb_ary_new_from_node_args(ast, 1, RNODE_BEGIN(node)->nd_body); + return rb_ary_new_from_node_args(vast, 1, RNODE_BEGIN(node)->nd_body); case NODE_RESCUE: - return rb_ary_new_from_node_args(ast, 3, RNODE_RESCUE(node)->nd_head, RNODE_RESCUE(node)->nd_resq, RNODE_RESCUE(node)->nd_else); + return rb_ary_new_from_node_args(vast, 3, RNODE_RESCUE(node)->nd_head, RNODE_RESCUE(node)->nd_resq, RNODE_RESCUE(node)->nd_else); case NODE_RESBODY: - return rb_ary_new_from_node_args(ast, 3, RNODE_RESBODY(node)->nd_args, RNODE_RESBODY(node)->nd_body, RNODE_RESBODY(node)->nd_next); + return rb_ary_new_from_node_args(vast, 3, RNODE_RESBODY(node)->nd_args, RNODE_RESBODY(node)->nd_body, RNODE_RESBODY(node)->nd_next); case NODE_ENSURE: - return rb_ary_new_from_node_args(ast, 2, RNODE_ENSURE(node)->nd_head, RNODE_ENSURE(node)->nd_ensr); + return rb_ary_new_from_node_args(vast, 2, RNODE_ENSURE(node)->nd_head, RNODE_ENSURE(node)->nd_ensr); case NODE_AND: case NODE_OR: { VALUE ary = rb_ary_new(); while (1) { - rb_ary_push(ary, NEW_CHILD(ast, RNODE_AND(node)->nd_1st)); + rb_ary_push(ary, NEW_CHILD(vast, RNODE_AND(node)->nd_1st)); if (!RNODE_AND(node)->nd_2nd || !nd_type_p(RNODE_AND(node)->nd_2nd, type)) break; node = RNODE_AND(node)->nd_2nd; } - rb_ary_push(ary, NEW_CHILD(ast, RNODE_AND(node)->nd_2nd)); + rb_ary_push(ary, NEW_CHILD(vast, RNODE_AND(node)->nd_2nd)); return ary; } case NODE_MASGN: if (NODE_NAMED_REST_P(RNODE_MASGN(node)->nd_args)) { - return rb_ary_new_from_node_args(ast, 3, RNODE_MASGN(node)->nd_value, RNODE_MASGN(node)->nd_head, RNODE_MASGN(node)->nd_args); + return rb_ary_new_from_node_args(vast, 3, RNODE_MASGN(node)->nd_value, RNODE_MASGN(node)->nd_head, RNODE_MASGN(node)->nd_args); } else { - return rb_ary_new_from_args(3, NEW_CHILD(ast, RNODE_MASGN(node)->nd_value), - NEW_CHILD(ast, RNODE_MASGN(node)->nd_head), + return rb_ary_new_from_args(3, NEW_CHILD(vast, RNODE_MASGN(node)->nd_value), + NEW_CHILD(vast, RNODE_MASGN(node)->nd_head), no_name_rest()); } case NODE_LASGN: if (NODE_REQUIRED_KEYWORD_P(RNODE_LASGN(node)->nd_value)) { return rb_ary_new_from_args(2, var_name(RNODE_LASGN(node)->nd_vid), ID2SYM(rb_intern("NODE_SPECIAL_REQUIRED_KEYWORD"))); } - return rb_ary_new_from_args(2, var_name(RNODE_LASGN(node)->nd_vid), NEW_CHILD(ast, RNODE_LASGN(node)->nd_value)); + return rb_ary_new_from_args(2, var_name(RNODE_LASGN(node)->nd_vid), NEW_CHILD(vast, RNODE_LASGN(node)->nd_value)); case NODE_DASGN: if (NODE_REQUIRED_KEYWORD_P(RNODE_DASGN(node)->nd_value)) { return rb_ary_new_from_args(2, var_name(RNODE_DASGN(node)->nd_vid), ID2SYM(rb_intern("NODE_SPECIAL_REQUIRED_KEYWORD"))); } - return rb_ary_new_from_args(2, var_name(RNODE_DASGN(node)->nd_vid), NEW_CHILD(ast, RNODE_DASGN(node)->nd_value)); + return rb_ary_new_from_args(2, var_name(RNODE_DASGN(node)->nd_vid), NEW_CHILD(vast, RNODE_DASGN(node)->nd_value)); case NODE_IASGN: - return rb_ary_new_from_args(2, var_name(RNODE_IASGN(node)->nd_vid), NEW_CHILD(ast, RNODE_IASGN(node)->nd_value)); + return rb_ary_new_from_args(2, var_name(RNODE_IASGN(node)->nd_vid), NEW_CHILD(vast, RNODE_IASGN(node)->nd_value)); case NODE_CVASGN: - return rb_ary_new_from_args(2, var_name(RNODE_CVASGN(node)->nd_vid), NEW_CHILD(ast, RNODE_CVASGN(node)->nd_value)); + return rb_ary_new_from_args(2, var_name(RNODE_CVASGN(node)->nd_vid), NEW_CHILD(vast, RNODE_CVASGN(node)->nd_value)); case NODE_GASGN: - return rb_ary_new_from_args(2, var_name(RNODE_GASGN(node)->nd_vid), NEW_CHILD(ast, RNODE_GASGN(node)->nd_value)); + return rb_ary_new_from_args(2, var_name(RNODE_GASGN(node)->nd_vid), NEW_CHILD(vast, RNODE_GASGN(node)->nd_value)); case NODE_CDECL: if (RNODE_CDECL(node)->nd_vid) { - return rb_ary_new_from_args(2, ID2SYM(RNODE_CDECL(node)->nd_vid), NEW_CHILD(ast, RNODE_CDECL(node)->nd_value)); + return rb_ary_new_from_args(2, ID2SYM(RNODE_CDECL(node)->nd_vid), NEW_CHILD(vast, RNODE_CDECL(node)->nd_value)); } - return rb_ary_new_from_args(3, NEW_CHILD(ast, RNODE_CDECL(node)->nd_else), ID2SYM(RNODE_COLON2(RNODE_CDECL(node)->nd_else)->nd_mid), NEW_CHILD(ast, RNODE_CDECL(node)->nd_value)); + return rb_ary_new_from_args(3, NEW_CHILD(vast, RNODE_CDECL(node)->nd_else), ID2SYM(RNODE_COLON2(RNODE_CDECL(node)->nd_else)->nd_mid), NEW_CHILD(vast, RNODE_CDECL(node)->nd_value)); case NODE_OP_ASGN1: - return rb_ary_new_from_args(4, NEW_CHILD(ast, RNODE_OP_ASGN1(node)->nd_recv), + return rb_ary_new_from_args(4, NEW_CHILD(vast, RNODE_OP_ASGN1(node)->nd_recv), ID2SYM(RNODE_OP_ASGN1(node)->nd_mid), - NEW_CHILD(ast, RNODE_OP_ASGN1(node)->nd_index), - NEW_CHILD(ast, RNODE_OP_ASGN1(node)->nd_rvalue)); + NEW_CHILD(vast, RNODE_OP_ASGN1(node)->nd_index), + NEW_CHILD(vast, RNODE_OP_ASGN1(node)->nd_rvalue)); case NODE_OP_ASGN2: - return rb_ary_new_from_args(5, NEW_CHILD(ast, RNODE_OP_ASGN2(node)->nd_recv), + return rb_ary_new_from_args(5, NEW_CHILD(vast, RNODE_OP_ASGN2(node)->nd_recv), RBOOL(RNODE_OP_ASGN2(node)->nd_aid), ID2SYM(RNODE_OP_ASGN2(node)->nd_vid), ID2SYM(RNODE_OP_ASGN2(node)->nd_mid), - NEW_CHILD(ast, RNODE_OP_ASGN2(node)->nd_value)); + NEW_CHILD(vast, RNODE_OP_ASGN2(node)->nd_value)); case NODE_OP_ASGN_AND: - return rb_ary_new_from_args(3, NEW_CHILD(ast, RNODE_OP_ASGN_AND(node)->nd_head), ID2SYM(idANDOP), - NEW_CHILD(ast, RNODE_OP_ASGN_AND(node)->nd_value)); + return rb_ary_new_from_args(3, NEW_CHILD(vast, RNODE_OP_ASGN_AND(node)->nd_head), ID2SYM(idANDOP), + NEW_CHILD(vast, RNODE_OP_ASGN_AND(node)->nd_value)); case NODE_OP_ASGN_OR: - return rb_ary_new_from_args(3, NEW_CHILD(ast, RNODE_OP_ASGN_OR(node)->nd_head), ID2SYM(idOROP), - NEW_CHILD(ast, RNODE_OP_ASGN_OR(node)->nd_value)); + return rb_ary_new_from_args(3, NEW_CHILD(vast, RNODE_OP_ASGN_OR(node)->nd_head), ID2SYM(idOROP), + NEW_CHILD(vast, RNODE_OP_ASGN_OR(node)->nd_value)); case NODE_OP_CDECL: - return rb_ary_new_from_args(3, NEW_CHILD(ast, RNODE_OP_CDECL(node)->nd_head), + return rb_ary_new_from_args(3, NEW_CHILD(vast, RNODE_OP_CDECL(node)->nd_head), ID2SYM(RNODE_OP_CDECL(node)->nd_aid), - NEW_CHILD(ast, RNODE_OP_CDECL(node)->nd_value)); + NEW_CHILD(vast, RNODE_OP_CDECL(node)->nd_value)); case NODE_CALL: - return rb_ary_new_from_args(3, NEW_CHILD(ast, RNODE_CALL(node)->nd_recv), + return rb_ary_new_from_args(3, NEW_CHILD(vast, RNODE_CALL(node)->nd_recv), ID2SYM(RNODE_CALL(node)->nd_mid), - NEW_CHILD(ast, RNODE_CALL(node)->nd_args)); + NEW_CHILD(vast, RNODE_CALL(node)->nd_args)); case NODE_OPCALL: - return rb_ary_new_from_args(3, NEW_CHILD(ast, RNODE_OPCALL(node)->nd_recv), + return rb_ary_new_from_args(3, NEW_CHILD(vast, RNODE_OPCALL(node)->nd_recv), ID2SYM(RNODE_OPCALL(node)->nd_mid), - NEW_CHILD(ast, RNODE_OPCALL(node)->nd_args)); + NEW_CHILD(vast, RNODE_OPCALL(node)->nd_args)); case NODE_QCALL: - return rb_ary_new_from_args(3, NEW_CHILD(ast, RNODE_QCALL(node)->nd_recv), + return rb_ary_new_from_args(3, NEW_CHILD(vast, RNODE_QCALL(node)->nd_recv), ID2SYM(RNODE_QCALL(node)->nd_mid), - NEW_CHILD(ast, RNODE_QCALL(node)->nd_args)); + NEW_CHILD(vast, RNODE_QCALL(node)->nd_args)); case NODE_FCALL: return rb_ary_new_from_args(2, ID2SYM(RNODE_FCALL(node)->nd_mid), - NEW_CHILD(ast, RNODE_FCALL(node)->nd_args)); + NEW_CHILD(vast, RNODE_FCALL(node)->nd_args)); case NODE_VCALL: return rb_ary_new_from_args(1, ID2SYM(RNODE_VCALL(node)->nd_mid)); case NODE_SUPER: - return rb_ary_new_from_node_args(ast, 1, RNODE_SUPER(node)->nd_args); + return rb_ary_new_from_node_args(vast, 1, RNODE_SUPER(node)->nd_args); case NODE_ZSUPER: - return rb_ary_new_from_node_args(ast, 0); + return rb_ary_new_from_node_args(vast, 0); case NODE_LIST: - return dump_array(ast, RNODE_LIST(node)); + return dump_array(vast, RNODE_LIST(node)); case NODE_ZLIST: - return rb_ary_new_from_node_args(ast, 0); + return rb_ary_new_from_node_args(vast, 0); case NODE_HASH: - return rb_ary_new_from_node_args(ast, 1, RNODE_HASH(node)->nd_head); + return rb_ary_new_from_node_args(vast, 1, RNODE_HASH(node)->nd_head); case NODE_YIELD: - return rb_ary_new_from_node_args(ast, 1, RNODE_YIELD(node)->nd_head); + return rb_ary_new_from_node_args(vast, 1, RNODE_YIELD(node)->nd_head); case NODE_LVAR: return rb_ary_new_from_args(1, var_name(RNODE_LVAR(node)->nd_vid)); case NODE_DVAR: @@ -551,16 +519,15 @@ 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); + return rb_ary_new_from_node_args(vast, 3, RNODE_MATCH2(node)->nd_recv, RNODE_MATCH2(node)->nd_value, RNODE_MATCH2(node)->nd_args); } - return rb_ary_new_from_node_args(ast, 2, RNODE_MATCH2(node)->nd_recv, RNODE_MATCH2(node)->nd_value); + return rb_ary_new_from_node_args(vast, 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); + return rb_ary_new_from_node_args(vast, 2, RNODE_MATCH3(node)->nd_recv, RNODE_MATCH3(node)->nd_value); case NODE_STR: case NODE_XSTR: return rb_ary_new_from_args(1, rb_node_str_string_val(node)); @@ -575,7 +542,7 @@ node_children(rb_ast_t *ast, const NODE *node) case NODE_REGX: return rb_ary_new_from_args(1, rb_node_regx_string_val(node)); case NODE_ONCE: - return rb_ary_new_from_node_args(ast, 1, RNODE_ONCE(node)->nd_body); + return rb_ary_new_from_node_args(vast, 1, RNODE_ONCE(node)->nd_body); case NODE_DSTR: case NODE_DXSTR: case NODE_DREGX: @@ -584,91 +551,91 @@ node_children(rb_ast_t *ast, const NODE *node) struct RNode_LIST *n = RNODE_DSTR(node)->nd_next; VALUE head = Qnil, next = Qnil; if (n) { - head = NEW_CHILD(ast, n->nd_head); - next = NEW_CHILD(ast, n->nd_next); + head = NEW_CHILD(vast, n->nd_head); + next = NEW_CHILD(vast, n->nd_next); } return rb_ary_new_from_args(3, rb_node_dstr_string_val(node), head, next); } case NODE_SYM: return rb_ary_new_from_args(1, rb_node_sym_string_val(node)); case NODE_EVSTR: - return rb_ary_new_from_node_args(ast, 1, RNODE_EVSTR(node)->nd_body); + return rb_ary_new_from_node_args(vast, 1, RNODE_EVSTR(node)->nd_body); case NODE_ARGSCAT: - return rb_ary_new_from_node_args(ast, 2, RNODE_ARGSCAT(node)->nd_head, RNODE_ARGSCAT(node)->nd_body); + return rb_ary_new_from_node_args(vast, 2, RNODE_ARGSCAT(node)->nd_head, RNODE_ARGSCAT(node)->nd_body); case NODE_ARGSPUSH: - return rb_ary_new_from_node_args(ast, 2, RNODE_ARGSPUSH(node)->nd_head, RNODE_ARGSPUSH(node)->nd_body); + return rb_ary_new_from_node_args(vast, 2, RNODE_ARGSPUSH(node)->nd_head, RNODE_ARGSPUSH(node)->nd_body); case NODE_SPLAT: - return rb_ary_new_from_node_args(ast, 1, RNODE_SPLAT(node)->nd_head); + return rb_ary_new_from_node_args(vast, 1, RNODE_SPLAT(node)->nd_head); case NODE_BLOCK_PASS: - return rb_ary_new_from_node_args(ast, 2, RNODE_BLOCK_PASS(node)->nd_head, RNODE_BLOCK_PASS(node)->nd_body); + return rb_ary_new_from_node_args(vast, 2, RNODE_BLOCK_PASS(node)->nd_head, RNODE_BLOCK_PASS(node)->nd_body); case NODE_DEFN: - return rb_ary_new_from_args(2, ID2SYM(RNODE_DEFN(node)->nd_mid), NEW_CHILD(ast, RNODE_DEFN(node)->nd_defn)); + return rb_ary_new_from_args(2, ID2SYM(RNODE_DEFN(node)->nd_mid), NEW_CHILD(vast, RNODE_DEFN(node)->nd_defn)); case NODE_DEFS: - return rb_ary_new_from_args(3, NEW_CHILD(ast, RNODE_DEFS(node)->nd_recv), ID2SYM(RNODE_DEFS(node)->nd_mid), NEW_CHILD(ast, RNODE_DEFS(node)->nd_defn)); + return rb_ary_new_from_args(3, NEW_CHILD(vast, RNODE_DEFS(node)->nd_recv), ID2SYM(RNODE_DEFS(node)->nd_mid), NEW_CHILD(vast, RNODE_DEFS(node)->nd_defn)); case NODE_ALIAS: - return rb_ary_new_from_node_args(ast, 2, RNODE_ALIAS(node)->nd_1st, RNODE_ALIAS(node)->nd_2nd); + return rb_ary_new_from_node_args(vast, 2, RNODE_ALIAS(node)->nd_1st, RNODE_ALIAS(node)->nd_2nd); case NODE_VALIAS: return rb_ary_new_from_args(2, ID2SYM(RNODE_VALIAS(node)->nd_alias), ID2SYM(RNODE_VALIAS(node)->nd_orig)); case NODE_UNDEF: - return rb_ary_new_from_node_args(ast, 1, RNODE_UNDEF(node)->nd_undef); + return rb_ary_new_from_node_args(vast, 1, RNODE_UNDEF(node)->nd_undef); case NODE_CLASS: - return rb_ary_new_from_node_args(ast, 3, RNODE_CLASS(node)->nd_cpath, RNODE_CLASS(node)->nd_super, RNODE_CLASS(node)->nd_body); + return rb_ary_new_from_node_args(vast, 3, RNODE_CLASS(node)->nd_cpath, RNODE_CLASS(node)->nd_super, RNODE_CLASS(node)->nd_body); case NODE_MODULE: - return rb_ary_new_from_node_args(ast, 2, RNODE_MODULE(node)->nd_cpath, RNODE_MODULE(node)->nd_body); + return rb_ary_new_from_node_args(vast, 2, RNODE_MODULE(node)->nd_cpath, RNODE_MODULE(node)->nd_body); case NODE_SCLASS: - return rb_ary_new_from_node_args(ast, 2, RNODE_SCLASS(node)->nd_recv, RNODE_SCLASS(node)->nd_body); + return rb_ary_new_from_node_args(vast, 2, RNODE_SCLASS(node)->nd_recv, RNODE_SCLASS(node)->nd_body); case NODE_COLON2: - return rb_ary_new_from_args(2, NEW_CHILD(ast, RNODE_COLON2(node)->nd_head), ID2SYM(RNODE_COLON2(node)->nd_mid)); + return rb_ary_new_from_args(2, NEW_CHILD(vast, RNODE_COLON2(node)->nd_head), ID2SYM(RNODE_COLON2(node)->nd_mid)); case NODE_COLON3: return rb_ary_new_from_args(1, ID2SYM(RNODE_COLON3(node)->nd_mid)); case NODE_DOT2: case NODE_DOT3: case NODE_FLIP2: case NODE_FLIP3: - return rb_ary_new_from_node_args(ast, 2, RNODE_DOT2(node)->nd_beg, RNODE_DOT2(node)->nd_end); + return rb_ary_new_from_node_args(vast, 2, RNODE_DOT2(node)->nd_beg, RNODE_DOT2(node)->nd_end); case NODE_SELF: - return rb_ary_new_from_node_args(ast, 0); + return rb_ary_new_from_node_args(vast, 0); case NODE_NIL: - return rb_ary_new_from_node_args(ast, 0); + return rb_ary_new_from_node_args(vast, 0); case NODE_TRUE: - return rb_ary_new_from_node_args(ast, 0); + return rb_ary_new_from_node_args(vast, 0); case NODE_FALSE: - return rb_ary_new_from_node_args(ast, 0); + return rb_ary_new_from_node_args(vast, 0); case NODE_ERRINFO: - return rb_ary_new_from_node_args(ast, 0); + return rb_ary_new_from_node_args(vast, 0); case NODE_DEFINED: - return rb_ary_new_from_node_args(ast, 1, RNODE_DEFINED(node)->nd_head); + return rb_ary_new_from_node_args(vast, 1, RNODE_DEFINED(node)->nd_head); case NODE_POSTEXE: - return rb_ary_new_from_node_args(ast, 1, RNODE_POSTEXE(node)->nd_body); + return rb_ary_new_from_node_args(vast, 1, RNODE_POSTEXE(node)->nd_body); case NODE_ATTRASGN: - return rb_ary_new_from_args(3, NEW_CHILD(ast, RNODE_ATTRASGN(node)->nd_recv), ID2SYM(RNODE_ATTRASGN(node)->nd_mid), NEW_CHILD(ast, RNODE_ATTRASGN(node)->nd_args)); + return rb_ary_new_from_args(3, NEW_CHILD(vast, RNODE_ATTRASGN(node)->nd_recv), ID2SYM(RNODE_ATTRASGN(node)->nd_mid), NEW_CHILD(vast, RNODE_ATTRASGN(node)->nd_args)); case NODE_LAMBDA: - return rb_ary_new_from_node_args(ast, 1, RNODE_LAMBDA(node)->nd_body); + return rb_ary_new_from_node_args(vast, 1, RNODE_LAMBDA(node)->nd_body); case NODE_OPT_ARG: - return rb_ary_new_from_node_args(ast, 2, RNODE_OPT_ARG(node)->nd_body, RNODE_OPT_ARG(node)->nd_next); + return rb_ary_new_from_node_args(vast, 2, RNODE_OPT_ARG(node)->nd_body, RNODE_OPT_ARG(node)->nd_next); case NODE_KW_ARG: - return rb_ary_new_from_node_args(ast, 2, RNODE_KW_ARG(node)->nd_body, RNODE_KW_ARG(node)->nd_next); + return rb_ary_new_from_node_args(vast, 2, RNODE_KW_ARG(node)->nd_body, RNODE_KW_ARG(node)->nd_next); case NODE_POSTARG: if (NODE_NAMED_REST_P(RNODE_POSTARG(node)->nd_1st)) { - return rb_ary_new_from_node_args(ast, 2, RNODE_POSTARG(node)->nd_1st, RNODE_POSTARG(node)->nd_2nd); + return rb_ary_new_from_node_args(vast, 2, RNODE_POSTARG(node)->nd_1st, RNODE_POSTARG(node)->nd_2nd); } return rb_ary_new_from_args(2, no_name_rest(), - NEW_CHILD(ast, RNODE_POSTARG(node)->nd_2nd)); + NEW_CHILD(vast, RNODE_POSTARG(node)->nd_2nd)); case NODE_ARGS: { struct rb_args_info *ainfo = &RNODE_ARGS(node)->nd_ainfo; return rb_ary_new_from_args(10, INT2NUM(ainfo->pre_args_num), - NEW_CHILD(ast, ainfo->pre_init), - NEW_CHILD(ast, (NODE *)ainfo->opt_args), + NEW_CHILD(vast, ainfo->pre_init), + NEW_CHILD(vast, (NODE *)ainfo->opt_args), var_name(ainfo->first_post_arg), INT2NUM(ainfo->post_args_num), - NEW_CHILD(ast, ainfo->post_init), + NEW_CHILD(vast, ainfo->post_init), (ainfo->rest_arg == NODE_SPECIAL_EXCESSIVE_COMMA ? ID2SYM(rb_intern("NODE_SPECIAL_EXCESSIVE_COMMA")) : var_name(ainfo->rest_arg)), - (ainfo->no_kwarg ? Qfalse : NEW_CHILD(ast, (NODE *)ainfo->kw_args)), - (ainfo->no_kwarg ? Qfalse : NEW_CHILD(ast, ainfo->kw_rest_arg)), + (ainfo->no_kwarg ? Qfalse : NEW_CHILD(vast, (NODE *)ainfo->kw_args)), + (ainfo->no_kwarg ? Qfalse : NEW_CHILD(vast, ainfo->kw_rest_arg)), var_name(ainfo->block_arg)); } case NODE_SCOPE: @@ -679,35 +646,35 @@ node_children(rb_ast_t *ast, const NODE *node) for (i = 0; i < size; i++) { rb_ary_push(locals, var_name(tbl->ids[i])); } - return rb_ary_new_from_args(3, locals, NEW_CHILD(ast, (NODE *)RNODE_SCOPE(node)->nd_args), NEW_CHILD(ast, RNODE_SCOPE(node)->nd_body)); + return rb_ary_new_from_args(3, locals, NEW_CHILD(vast, (NODE *)RNODE_SCOPE(node)->nd_args), NEW_CHILD(vast, RNODE_SCOPE(node)->nd_body)); } case NODE_ARYPTN: { - VALUE rest = rest_arg(ast, RNODE_ARYPTN(node)->rest_arg); + VALUE rest = rest_arg(vast, RNODE_ARYPTN(node)->rest_arg); return rb_ary_new_from_args(4, - NEW_CHILD(ast, RNODE_ARYPTN(node)->nd_pconst), - NEW_CHILD(ast, RNODE_ARYPTN(node)->pre_args), + NEW_CHILD(vast, RNODE_ARYPTN(node)->nd_pconst), + NEW_CHILD(vast, RNODE_ARYPTN(node)->pre_args), rest, - NEW_CHILD(ast, RNODE_ARYPTN(node)->post_args)); + NEW_CHILD(vast, RNODE_ARYPTN(node)->post_args)); } case NODE_FNDPTN: { - VALUE pre_rest = rest_arg(ast, RNODE_FNDPTN(node)->pre_rest_arg); - VALUE post_rest = rest_arg(ast, RNODE_FNDPTN(node)->post_rest_arg); + VALUE pre_rest = rest_arg(vast, RNODE_FNDPTN(node)->pre_rest_arg); + VALUE post_rest = rest_arg(vast, RNODE_FNDPTN(node)->post_rest_arg); return rb_ary_new_from_args(4, - NEW_CHILD(ast, RNODE_FNDPTN(node)->nd_pconst), + NEW_CHILD(vast, RNODE_FNDPTN(node)->nd_pconst), pre_rest, - NEW_CHILD(ast, RNODE_FNDPTN(node)->args), + NEW_CHILD(vast, RNODE_FNDPTN(node)->args), post_rest); } case NODE_HSHPTN: { VALUE kwrest = RNODE_HSHPTN(node)->nd_pkwrestarg == NODE_SPECIAL_NO_REST_KEYWORD ? ID2SYM(rb_intern("NODE_SPECIAL_NO_REST_KEYWORD")) : - NEW_CHILD(ast, RNODE_HSHPTN(node)->nd_pkwrestarg); + NEW_CHILD(vast, RNODE_HSHPTN(node)->nd_pkwrestarg); return rb_ary_new_from_args(3, - NEW_CHILD(ast, RNODE_HSHPTN(node)->nd_pconst), - NEW_CHILD(ast, RNODE_HSHPTN(node)->nd_pkwargs), + NEW_CHILD(vast, RNODE_HSHPTN(node)->nd_pconst), + NEW_CHILD(vast, RNODE_HSHPTN(node)->nd_pkwargs), kwrest); } case NODE_LINE: @@ -717,7 +684,7 @@ node_children(rb_ast_t *ast, const NODE *node) case NODE_ENCODING: return rb_ary_new_from_args(1, rb_node_encoding_val(node)); case NODE_ERROR: - return rb_ary_new_from_node_args(ast, 0); + return rb_ary_new_from_node_args(vast, 0); case NODE_ARGS_AUX: case NODE_LAST: break; @@ -732,7 +699,7 @@ ast_node_children(rb_execution_context_t *ec, VALUE self) struct ASTNodeData *data; TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); - return node_children(data->ast, data->node); + return node_children(data->vast, data->node); } static VALUE @@ -774,10 +741,37 @@ 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_ast_t *ast; + 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); + ast = rb_ruby_ast_data_get(data->vast); + + parser_tokens = ast->node_buffer->tokens; + if (parser_tokens == NULL) { + return Qnil; + } - return rb_ast_tokens(data->ast); + 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 @@ -804,10 +798,12 @@ static VALUE ast_node_script_lines(rb_execution_context_t *ec, VALUE self) { struct ASTNodeData *data; + rb_ast_t *ast; TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); - VALUE ret = data->ast->body.script_lines; - if (!RB_TYPE_P(ret, T_ARRAY)) return Qnil; - return ret; + ast = rb_ruby_ast_data_get(data->vast); + rb_parser_ary_t *ret = ast->body.script_lines; + if (!ret || FIXNUM_P((VALUE)ret)) return Qnil; + return rb_parser_build_script_lines_from(ret); } #include "ast.rbinc" 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 039a15148d2c43..20f121cdf492f0 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 4db203503cad1e..48688103089933 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -628,7 +628,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 @@ -695,10 +695,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 @@ -721,11 +721,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 @@ -743,7 +743,7 @@ def check obj1 else raise 'unreachable' end -} +RUBY # move example2: Array assert_equal '[0, 1]', %q{ @@ -946,7 +946,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 @@ -957,13 +957,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{ @@ -1150,7 +1149,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 @@ -1162,10 +1161,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 @@ -1174,10 +1173,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 @@ -1188,7 +1187,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{ @@ -1241,7 +1240,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| @@ -1251,10 +1250,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' @@ -1325,7 +1324,7 @@ def /(other) } Ractor.shareable?(a) -} +RUBY # Ractor.make_shareable(obj) doesn't freeze shareable objects assert_equal 'true', %q{ @@ -1425,11 +1424,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 } @@ -1503,7 +1502,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) @@ -1662,7 +1661,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{ @@ -1793,3 +1792,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 7240e5ce8f449e..31bb626690286d 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -185,7 +185,7 @@ def carray(iter) end def cstring(iter) - string = "" + string = "".dup string.sum(iter.times { def string.sum(_) = :sum }) end @@ -1250,7 +1250,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 @@ -1395,7 +1395,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 @@ -1404,7 +1404,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: @@ -1904,7 +1904,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" @@ -1914,7 +1914,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{ @@ -2048,7 +2048,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 @@ -2091,7 +2091,7 @@ def to_int 0 end - def ccall = "a".setbyte(self, 98) + def ccall = "a".dup.setbyte(self, 98) ccall @caller.first.split("'").last @@ -2317,6 +2317,19 @@ def foo(obj) foo(Foo) } +# Test EP == BP invalidation with moving ISEQs +assert_equal 'ok', %q{ + def entry + ok = proc { :ok } # set #entry as an EP-escaping ISEQ + [nil].reverse_each do # avoid exiting the JIT frame on the constant + GC.compact # move #entry ISEQ + end + ok # should be read off of escaped EP + end + + entry.call +} + # invokesuper edge case assert_equal '[:A, [:A, :B]]', %q{ class B @@ -4167,7 +4180,7 @@ def foo assert_equal 'Hello World', %q{ def bar args = ["Hello "] - greeting = "World" + greeting = +"World" greeting.insert(0, *args) greeting end @@ -4678,3 +4691,171 @@ 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 +} + +# regression test for invalidating an empty block +assert_equal '0', %q{ + def foo = (* = 1).pred + + foo # compile it + + class Integer + def to_ary = [] # invalidate + end + + foo # try again +} unless rjit_enabled? # doesn't work on RJIT + +# 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 +} + +# test String#stebyte with arguments that need conversion +assert_equal "abc", %q{ + str = +"a00" + def change_bytes(str, one, two) + str.setbyte(one, "b".ord) + str.setbyte(2, two) + end + + to_int_1 = Object.new + to_int_99 = Object.new + def to_int_1.to_int = 1 + def to_int_99.to_int = 99 + + change_bytes(str, to_int_1, to_int_99) + str +} + +# test --yjit-verify-ctx for arrays with a singleton class +assert_equal "ok", %q{ + class Array + def foo + self.singleton_class.define_method(:first) { :ok } + first + end + end + + def test = [].foo + + test +} + +assert_equal '["raised", "Module", "Object"]', %q{ + def foo(obj) + obj.superclass.name + end + + ret = [] + + begin + foo(Class.allocate) + rescue TypeError + ret << 'raised' + end + + ret += [foo(Class), foo(Class.new)] +} diff --git a/class.c b/class.c index 11894fccea8b48..5cce99e334d8b1 100644 --- a/class.c +++ b/class.c @@ -29,12 +29,18 @@ #include "internal/variable.h" #include "ruby/st.h" #include "vm_core.h" +#include "yjit.h" /* Flags of T_CLASS * - * 2: RCLASS_SUPERCLASSES_INCLUDE_SELF - * The RCLASS_SUPERCLASSES contains the class as the last element. - * This means that this class owns the RCLASS_SUPERCLASSES list. + * 0: RCLASS_IS_ROOT + * The class has been added to the VM roots. Will always be marked and pinned. + * This is done for classes defined from C to allow storing them in global variables. + * 1: RUBY_FL_SINGLETON + * This class is a singleton class. + * 2: RCLASS_SUPERCLASSES_INCLUDE_SELF + * The RCLASS_SUPERCLASSES contains the class as the last element. + * This means that this class owns the RCLASS_SUPERCLASSES list. * if !SHAPE_IN_BASIC_FLAGS * 4-19: SHAPE_FLAG_MASK * Shape ID for the class. @@ -54,6 +60,9 @@ /* Flags of T_MODULE * + * 0: RCLASS_IS_ROOT + * The class has been added to the VM roots. Will always be marked and pinned. + * This is done for classes defined from C to allow storing them in global variables. * 1: RMODULE_ALLOCATED_BUT_NOT_INITIALIZED * Module has not been initialized. * 2: RCLASS_SUPERCLASSES_INCLUDE_SELF @@ -217,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) @@ -259,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) @@ -338,7 +347,7 @@ rb_check_inheritable(VALUE super) rb_raise(rb_eTypeError, "superclass must be an instance of Class (given an instance of %"PRIsVALUE")", rb_obj_class(super)); } - if (RBASIC(super)->flags & FL_SINGLETON) { + if (RCLASS_SINGLETON_P(super)) { rb_raise(rb_eTypeError, "can't make subclass of singleton class"); } if (super == rb_cClass) { @@ -424,7 +433,7 @@ class_init_copy_check(VALUE clone, VALUE orig) if (RCLASS_SUPER(clone) != 0 || clone == rb_cBasicObject) { rb_raise(rb_eTypeError, "already initialized class"); } - if (FL_TEST(orig, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(orig)) { rb_raise(rb_eTypeError, "can't copy singleton class"); } } @@ -542,7 +551,7 @@ rb_mod_init_copy(VALUE clone, VALUE orig) RCLASS_EXT(clone)->cloned = true; RCLASS_EXT(orig)->cloned = true; - if (!FL_TEST(CLASS_OF(clone), FL_SINGLETON)) { + if (!RCLASS_SINGLETON_P(CLASS_OF(clone))) { RBASIC_SET_CLASS(clone, rb_singleton_class_clone(orig)); rb_singleton_class_attached(METACLASS_OF(clone), (VALUE)clone); } @@ -648,7 +657,7 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) // attached to an object other than `obj`. In which case `obj` does not have // a material singleton class attached yet and there is no singleton class // to clone. - if (!(FL_TEST(klass, FL_SINGLETON) && RCLASS_ATTACHED_OBJECT(klass) == obj)) { + if (!(RCLASS_SINGLETON_P(klass) && RCLASS_ATTACHED_OBJECT(klass) == obj)) { // nothing to clone return klass; } @@ -699,7 +708,7 @@ rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) void rb_singleton_class_attached(VALUE klass, VALUE obj) { - if (FL_TEST(klass, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(klass)) { RCLASS_SET_ATTACHED_OBJECT(klass, obj); } } @@ -724,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 @@ -733,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) @@ -782,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) @@ -797,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; @@ -810,7 +820,7 @@ boot_defclass(const char *name, VALUE super) ID id = rb_intern(name); rb_const_set((rb_cObject ? rb_cObject : obj), id, obj); - rb_vm_add_root_module(obj); + rb_vm_register_global_object(obj); return obj; } @@ -892,7 +902,7 @@ Init_class_hierarchy(void) { rb_cBasicObject = boot_defclass("BasicObject", 0); rb_cObject = boot_defclass("Object", rb_cBasicObject); - rb_gc_register_mark_object(rb_cObject); + rb_vm_register_global_object(rb_cObject); /* resolve class name ASAP for order-independence */ rb_set_class_path_string(rb_cObject, rb_cObject, rb_fstring_lit("Object")); @@ -917,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) @@ -951,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) @@ -986,14 +996,14 @@ rb_define_class(const char *name, VALUE super) } /* Class may have been defined in Ruby and not pin-rooted */ - rb_vm_add_root_module(klass); + rb_vm_register_global_object(klass); return klass; } if (!super) { rb_raise(rb_eArgError, "no super class for '%s'", name); } klass = rb_define_class_id(id, super); - rb_vm_add_root_module(klass); + rb_vm_register_global_object(klass); rb_const_set(rb_cObject, id, klass); rb_class_inherited(super, klass); @@ -1043,7 +1053,7 @@ VALUE rb_define_class_id_under(VALUE outer, ID id, VALUE super) { VALUE klass = rb_define_class_id_under_no_pin(outer, id, super); - rb_vm_add_root_module(klass); + rb_vm_register_global_object(klass); return klass; } @@ -1097,11 +1107,11 @@ rb_define_module(const char *name) name, rb_obj_class(module)); } /* Module may have been defined in Ruby and not pin-rooted */ - rb_vm_add_root_module(module); + rb_vm_register_global_object(module); return module; } module = rb_module_new(); - rb_vm_add_root_module(module); + rb_vm_register_global_object(module); rb_const_set(rb_cObject, id, module); return module; @@ -1126,13 +1136,13 @@ rb_define_module_id_under(VALUE outer, ID id) outer, rb_id2str(id), rb_obj_class(module)); } /* Module may have been defined in Ruby and not pin-rooted */ - rb_gc_register_mark_object(module); + rb_vm_register_global_object(module); return module; } module = rb_module_new(); rb_const_set(outer, id, module); rb_set_class_path_string(module, outer, rb_id2str(id)); - rb_gc_register_mark_object(module); + rb_vm_register_global_object(module); return module; } @@ -1605,7 +1615,7 @@ class_descendants_recursive(VALUE klass, VALUE v) { struct subclass_traverse_data *data = (struct subclass_traverse_data *) v; - if (BUILTIN_TYPE(klass) == T_CLASS && !FL_TEST(klass, FL_SINGLETON)) { + if (BUILTIN_TYPE(klass) == T_CLASS && !RCLASS_SINGLETON_P(klass)) { if (data->buffer && data->count < data->maxcount && !rb_objspace_garbage_object_p(klass)) { // assumes that this does not cause GC as long as the length does not exceed the capacity rb_ary_push(data->buffer, klass); @@ -1710,7 +1720,7 @@ rb_class_subclasses(VALUE klass) VALUE rb_class_attached_object(VALUE klass) { - if (!FL_TEST(klass, FL_SINGLETON)) { + if (!RCLASS_SINGLETON_P(klass)) { rb_raise(rb_eTypeError, "'%"PRIsVALUE"' is not a singleton class", klass); } @@ -1813,7 +1823,7 @@ static bool particular_class_p(VALUE mod) { if (!mod) return false; - if (FL_TEST(mod, FL_SINGLETON)) return true; + if (RCLASS_SINGLETON_P(mod)) return true; if (BUILTIN_TYPE(mod) == T_ICLASS) return true; return false; } @@ -2090,19 +2100,19 @@ rb_obj_singleton_methods(int argc, const VALUE *argv, VALUE obj) int recur = TRUE; if (rb_check_arity(argc, 0, 1)) recur = RTEST(argv[0]); - if (RB_TYPE_P(obj, T_CLASS) && FL_TEST(obj, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(obj)) { rb_singleton_class(obj); } klass = CLASS_OF(obj); origin = RCLASS_ORIGIN(klass); me_arg.list = st_init_numtable(); me_arg.recur = recur; - if (klass && FL_TEST(klass, FL_SINGLETON)) { + if (klass && RCLASS_SINGLETON_P(klass)) { if ((mtbl = RCLASS_M_TBL(origin)) != 0) rb_id_table_foreach(mtbl, method_entry_i, &me_arg); klass = RCLASS_SUPER(klass); } if (recur) { - while (klass && (FL_TEST(klass, FL_SINGLETON) || RB_TYPE_P(klass, T_ICLASS))) { + while (klass && (RCLASS_SINGLETON_P(klass) || RB_TYPE_P(klass, T_ICLASS))) { if (klass != origin && (mtbl = RCLASS_M_TBL(klass)) != 0) rb_id_table_foreach(mtbl, method_entry_i, &me_arg); klass = RCLASS_SUPER(klass); } @@ -2206,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 @@ -2236,13 +2246,16 @@ 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"); } } klass = METACLASS_OF(obj); - if (!(FL_TEST(klass, FL_SINGLETON) && + if (!(RCLASS_SINGLETON_P(klass) && RCLASS_ATTACHED_OBJECT(klass) == obj)) { klass = rb_make_metaclass(obj, klass); } @@ -2256,21 +2269,21 @@ void rb_freeze_singleton_class(VALUE x) { /* should not propagate to meta-meta-class, and so on */ - if (!(RBASIC(x)->flags & FL_SINGLETON)) { + if (!RCLASS_SINGLETON_P(x)) { VALUE klass = RBASIC_CLASS(x); if (klass && // no class when hidden from ObjectSpace FL_TEST(klass, (FL_SINGLETON|FL_FREEZE)) == FL_SINGLETON) { - OBJ_FREEZE_RAW(klass); + OBJ_FREEZE(klass); } } } -/*! - * 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) @@ -2281,7 +2294,7 @@ rb_singleton_class_get(VALUE obj) return rb_special_singleton_class(obj); } klass = METACLASS_OF(obj); - if (!FL_TEST(klass, FL_SINGLETON)) return Qnil; + if (!RCLASS_SINGLETON_P(klass)) return Qnil; if (RCLASS_ATTACHED_OBJECT(klass) != obj) return Qnil; return klass; } diff --git a/common.mk b/common.mk index 9aefa02105419d..dc2a033c860e72 100644 --- a/common.mk +++ b/common.mk @@ -105,7 +105,6 @@ PRISM_FILES = prism/api_node.$(OBJEXT) \ prism/util/pm_list.$(OBJEXT) \ prism/util/pm_memchr.$(OBJEXT) \ prism/util/pm_newline_list.$(OBJEXT) \ - prism/util/pm_state_stack.$(OBJEXT) \ prism/util/pm_string.$(OBJEXT) \ prism/util/pm_string_list.$(OBJEXT) \ prism/util/pm_strncasecmp.$(OBJEXT) \ @@ -217,6 +216,11 @@ srcs: $(srcdir)/lib/prism/dsl.rb $(srcdir)/lib/prism/dsl.rb: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/lib/prism/dsl.rb.erb $(Q) $(BASERUBY) $(PRISM_SRCDIR)/templates/template.rb lib/prism/dsl.rb $(srcdir)/lib/prism/dsl.rb +main: $(srcdir)/lib/prism/inspect_visitor.rb +srcs: $(srcdir)/lib/prism/inspect_visitor.rb +$(srcdir)/lib/prism/inspect_visitor.rb: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/lib/prism/inspect_visitor.rb.erb + $(Q) $(BASERUBY) $(PRISM_SRCDIR)/templates/template.rb lib/prism/inspect_visitor.rb $(srcdir)/lib/prism/inspect_visitor.rb + main: $(srcdir)/lib/prism/mutation_compiler.rb srcs: $(srcdir)/lib/prism/mutation_compiler.rb $(srcdir)/lib/prism/mutation_compiler.rb: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/lib/prism/mutation_compiler.rb.erb @@ -227,6 +231,11 @@ srcs: $(srcdir)/lib/prism/node.rb $(srcdir)/lib/prism/node.rb: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/lib/prism/node.rb.erb $(Q) $(BASERUBY) $(PRISM_SRCDIR)/templates/template.rb lib/prism/node.rb $(srcdir)/lib/prism/node.rb +main: $(srcdir)/lib/prism/reflection.rb +srcs: $(srcdir)/lib/prism/reflection.rb +$(srcdir)/lib/prism/reflection.rb: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/lib/prism/reflection.rb.erb + $(Q) $(BASERUBY) $(PRISM_SRCDIR)/templates/template.rb lib/prism/reflection.rb $(srcdir)/lib/prism/reflection.rb + main: $(srcdir)/lib/prism/serialize.rb srcs: $(srcdir)/lib/prism/serialize.rb $(srcdir)/lib/prism/serialize.rb: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/lib/prism/serialize.rb.erb @@ -245,6 +254,14 @@ srcs: prism/ast.h prism/ast.h: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/include/prism/ast.h.erb $(Q) $(BASERUBY) $(PRISM_SRCDIR)/templates/template.rb include/prism/ast.h $@ +srcs: prism/diagnostic.c +prism/diagnostic.c: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/src/diagnostic.c.erb + $(Q) $(BASERUBY) $(PRISM_SRCDIR)/templates/template.rb src/diagnostic.c $@ + +srcs: prism/diagnostic.h +prism/diagnostic.h: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/include/prism/diagnostic.h.erb + $(Q) $(BASERUBY) $(PRISM_SRCDIR)/templates/template.rb include/prism/diagnostic.h $@ + srcs: prism/node.c prism/node.c: $(PRISM_SRCDIR)/config.yml $(PRISM_SRCDIR)/templates/template.rb $(PRISM_SRCDIR)/templates/src/node.c.erb $(Q) $(BASERUBY) $(PRISM_SRCDIR)/templates/template.rb src/node.c $@ @@ -267,7 +284,7 @@ EXPORTOBJS = $(DLNOBJ) \ $(COMMONOBJS) OBJS = $(EXPORTOBJS) builtin.$(OBJEXT) -ALLOBJS = $(NORMALMAINOBJ) $(MINIOBJS) $(COMMONOBJS) $(INITOBJS) +ALLOBJS = $(OBJS) $(MINIOBJS) $(INITOBJS) $(MAINOBJ) GOLFOBJS = goruby.$(OBJEXT) @@ -329,7 +346,7 @@ YJIT_RUSTC_ARGS = --crate-name=yjit \ '--out-dir=$(CARGO_TARGET_DIR)/release/' \ $(top_srcdir)/yjit/src/lib.rs -all: $(SHOWFLAGS) main docs +all: $(SHOWFLAGS) main main: $(SHOWFLAGS) exts $(ENCSTATIC:static=lib)encs @$(NULLCMD) @@ -399,7 +416,7 @@ configure-ext: $(EXTS_MK) build-ext: $(EXTS_MK) $(Q)$(MAKE) -f $(EXTS_MK) $(mflags) libdir="$(libdir)" LIBRUBY_EXTS=$(LIBRUBY_EXTS) \ EXTENCS="$(ENCOBJS)" BASERUBY="$(BASERUBY)" MINIRUBY="$(MINIRUBY)" \ - UPDATE_LIBRARIES=no $(EXTSTATIC) + $(EXTSTATIC) $(Q)$(MAKE) $(EXTS_NOTE) exts-note: $(EXTS_MK) @@ -463,8 +480,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 $@ @@ -473,9 +488,9 @@ docs: srcs-doc $(DOCTARGETS) pkgconfig-data: $(ruby_pc) $(ruby_pc): $(srcdir)/template/ruby.pc.in config.status -install-all: docs pre-install-all do-install-all post-install-all +install-all: pre-install-all do-install-all post-install-all pre-install-all:: all pre-install-local pre-install-ext pre-install-gem pre-install-doc -do-install-all: pre-install-all +do-install-all: pre-install-all $(DOT_WAIT) docs $(INSTRUBY) --make="$(MAKE)" $(INSTRUBY_ARGS) --install=all $(INSTALL_DOC_OPTS) post-install-all:: post-install-local post-install-ext post-install-gem post-install-doc @$(NULLCMD) @@ -710,12 +725,12 @@ clear-installed-list: PHONY clean: clean-ext clean-enc clean-golf clean-docs clean-extout clean-local clean-platform clean-spec clean-local:: clean-runnable - $(Q)$(RM) $(OBJS) $(MINIOBJS) $(INITOBJS) $(MAINOBJ) $(LIBRUBY_A) $(LIBRUBY_SO) $(LIBRUBY) $(LIBRUBY_ALIASES) + $(Q)$(RM) $(ALLOBJS) $(LIBRUBY_A) $(LIBRUBY_SO) $(LIBRUBY) $(LIBRUBY_ALIASES) $(Q)$(RM) $(PROGRAM) $(WPROGRAM) miniruby$(EXEEXT) dmyext.$(OBJEXT) dmyenc.$(OBJEXT) $(ARCHFILE) .*.time $(Q)$(RM) y.tab.c y.output encdb.h transdb.h config.log rbconfig.rb $(ruby_pc) $(COROUTINE_H:/Context.h=/.time) $(Q)$(RM) probes.h probes.$(OBJEXT) probes.stamp ruby-glommed.$(OBJEXT) ruby.imp ChangeLog $(STATIC_RUBY)$(EXEEXT) $(Q)$(RM) GNUmakefile.old Makefile.old $(arch)-fake.rb bisect.sh $(ENC_TRANS_D) builtin_binary.inc - $(Q)$(RM) $(PRISM_BUILD_DIR)/.time $(PRISM_BUILD_DIR)/*/.time + $(Q)$(RM) $(PRISM_BUILD_DIR)/.time $(PRISM_BUILD_DIR)/*/.time yjit_exit_locations.dump -$(Q)$(RMALL) yjit/target -$(Q) $(RMDIR) enc/jis enc/trans enc $(COROUTINE_H:/Context.h=) coroutine yjit \ $(PRISM_BUILD_DIR)/*/ $(PRISM_BUILD_DIR) tmp \ @@ -724,9 +739,11 @@ 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 $(EXTOUT)/common/$(arch) + -$(Q)$(RMALL) exe/ clean-ext:: PHONY clean-golf: PHONY $(Q)$(RM) $(GORUBY)$(EXEEXT) $(GOLFOBJS) @@ -945,12 +962,16 @@ PRECHECK_TEST_ALL = yes-test-all-precheck test-all: $(TEST_RUNNABLE)-test-all yes-test-all: $(PRECHECK_TEST_ALL) $(ACTIONS_GROUP) - $(gnumake_recursive)$(Q)$(exec) $(RUNRUBY) "$(TESTSDIR)/runner.rb" --ruby="$(RUNRUBY)" \ + $(gnumake_recursive)$(Q)$(exec) $(RUNRUBY) -r$(tooldir)/lib/_tmpdir \ + "$(TESTSDIR)/runner.rb" --ruby="$(RUNRUBY)" \ $(TEST_EXCLUDES) $(TESTOPTS) $(TESTS) $(ACTIONS_ENDGROUP) TESTS_BUILD = mkmf no-test-all: PHONY - $(gnumake_recursive)$(MINIRUBY) -I"$(srcdir)/lib" "$(TESTSDIR)/runner.rb" $(TESTOPTS) $(TESTS_BUILD) + $(ACTIONS_GROUP) + $(gnumake_recursive)$(MINIRUBY) -I"$(srcdir)/lib" -r$(tooldir)/lib/_tmpdir \ + "$(TESTSDIR)/runner.rb" $(TESTOPTS) $(TESTS_BUILD) + $(ACTIONS_ENDGROUP) test-almost: test-all yes-test-almost: yes-test-all @@ -992,11 +1013,20 @@ test-spec: $(TEST_RUNNABLE)-test-spec yes-test-spec: yes-test-spec-precheck $(ACTIONS_GROUP) $(gnumake_recursive)$(Q) \ - $(RUNRUBY) -r./$(arch)-fake -r$(tooldir)/rubyspec_temp \ + $(RUNRUBY) -r./$(arch)-fake -r$(tooldir)/lib/_tmpdir \ $(srcdir)/spec/mspec/bin/mspec run -B $(srcdir)/spec/default.mspec $(MSPECOPT) $(SPECOPTS) $(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)/lib/_tmpdir \ + $(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 @@ -1585,7 +1615,7 @@ test-bundled-gems-spec: $(TEST_RUNNABLE)-test-bundled-gems-spec yes-test-bundled-gems-spec: yes-test-spec-precheck $(PREPARE_BUNDLED_GEMS) $(ACTIONS_GROUP) $(gnumake_recursive)$(Q) \ - $(RUNRUBY) -r./$(arch)-fake -r$(tooldir)/rubyspec_temp \ + $(RUNRUBY) -r./$(arch)-fake -r$(tooldir)/lib/_tmpdir \ $(srcdir)/spec/mspec/bin/mspec run -B $(srcdir)/spec/bundled_gems.mspec $(MSPECOPT) $(SPECOPTS) $(ACTIONS_ENDGROUP) no-test-bundled-gems-spec: @@ -2234,7 +2264,6 @@ ast.$(OBJEXT): $(top_srcdir)/internal/variable.h ast.$(OBJEXT): $(top_srcdir)/internal/vm.h ast.$(OBJEXT): $(top_srcdir)/internal/warnings.h ast.$(OBJEXT): $(top_srcdir)/prism/defines.h -ast.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h ast.$(OBJEXT): $(top_srcdir)/prism/encoding.h ast.$(OBJEXT): $(top_srcdir)/prism/node.h ast.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -2251,7 +2280,6 @@ ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -2433,6 +2461,7 @@ ast.$(OBJEXT): {$(VPATH)}node.h ast.$(OBJEXT): {$(VPATH)}onigmo.h ast.$(OBJEXT): {$(VPATH)}oniguruma.h ast.$(OBJEXT): {$(VPATH)}prism/ast.h +ast.$(OBJEXT): {$(VPATH)}prism/diagnostic.h ast.$(OBJEXT): {$(VPATH)}prism/version.h ast.$(OBJEXT): {$(VPATH)}prism_compile.h ast.$(OBJEXT): {$(VPATH)}ruby_assert.h @@ -2671,7 +2700,6 @@ builtin.$(OBJEXT): $(top_srcdir)/internal/variable.h builtin.$(OBJEXT): $(top_srcdir)/internal/vm.h builtin.$(OBJEXT): $(top_srcdir)/internal/warnings.h builtin.$(OBJEXT): $(top_srcdir)/prism/defines.h -builtin.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h builtin.$(OBJEXT): $(top_srcdir)/prism/encoding.h builtin.$(OBJEXT): $(top_srcdir)/prism/node.h builtin.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -2688,7 +2716,6 @@ builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -2870,6 +2897,7 @@ builtin.$(OBJEXT): {$(VPATH)}node.h builtin.$(OBJEXT): {$(VPATH)}onigmo.h builtin.$(OBJEXT): {$(VPATH)}oniguruma.h builtin.$(OBJEXT): {$(VPATH)}prism/ast.h +builtin.$(OBJEXT): {$(VPATH)}prism/diagnostic.h builtin.$(OBJEXT): {$(VPATH)}prism/version.h builtin.$(OBJEXT): {$(VPATH)}prism_compile.h builtin.$(OBJEXT): {$(VPATH)}ruby_assert.h @@ -3089,6 +3117,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 @@ -3292,6 +3321,7 @@ compile.$(OBJEXT): $(top_srcdir)/internal/imemo.h compile.$(OBJEXT): $(top_srcdir)/internal/io.h compile.$(OBJEXT): $(top_srcdir)/internal/numeric.h compile.$(OBJEXT): $(top_srcdir)/internal/object.h +compile.$(OBJEXT): $(top_srcdir)/internal/parse.h compile.$(OBJEXT): $(top_srcdir)/internal/rational.h compile.$(OBJEXT): $(top_srcdir)/internal/re.h compile.$(OBJEXT): $(top_srcdir)/internal/ruby_parser.h @@ -3305,7 +3335,6 @@ compile.$(OBJEXT): $(top_srcdir)/internal/variable.h compile.$(OBJEXT): $(top_srcdir)/internal/vm.h compile.$(OBJEXT): $(top_srcdir)/internal/warnings.h compile.$(OBJEXT): $(top_srcdir)/prism/defines.h -compile.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h compile.$(OBJEXT): $(top_srcdir)/prism/encoding.h compile.$(OBJEXT): $(top_srcdir)/prism/node.h compile.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -3322,7 +3351,6 @@ compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -3512,10 +3540,12 @@ compile.$(OBJEXT): {$(VPATH)}onigmo.h compile.$(OBJEXT): {$(VPATH)}oniguruma.h compile.$(OBJEXT): {$(VPATH)}optinsn.inc compile.$(OBJEXT): {$(VPATH)}prism/ast.h +compile.$(OBJEXT): {$(VPATH)}prism/diagnostic.h 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 @@ -3769,7 +3799,6 @@ cont.$(OBJEXT): $(top_srcdir)/internal/variable.h cont.$(OBJEXT): $(top_srcdir)/internal/vm.h cont.$(OBJEXT): $(top_srcdir)/internal/warnings.h cont.$(OBJEXT): $(top_srcdir)/prism/defines.h -cont.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h cont.$(OBJEXT): $(top_srcdir)/prism/encoding.h cont.$(OBJEXT): $(top_srcdir)/prism/node.h cont.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -3786,7 +3815,6 @@ cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -3970,6 +3998,7 @@ cont.$(OBJEXT): {$(VPATH)}node.h cont.$(OBJEXT): {$(VPATH)}onigmo.h cont.$(OBJEXT): {$(VPATH)}oniguruma.h cont.$(OBJEXT): {$(VPATH)}prism/ast.h +cont.$(OBJEXT): {$(VPATH)}prism/diagnostic.h cont.$(OBJEXT): {$(VPATH)}prism/version.h cont.$(OBJEXT): {$(VPATH)}prism_compile.h cont.$(OBJEXT): {$(VPATH)}ractor.h @@ -6537,6 +6566,7 @@ error.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h error.$(OBJEXT): {$(VPATH)}builtin.h error.$(OBJEXT): {$(VPATH)}config.h error.$(OBJEXT): {$(VPATH)}constant.h +error.$(OBJEXT): {$(VPATH)}debug_counter.h error.$(OBJEXT): {$(VPATH)}defines.h error.$(OBJEXT): {$(VPATH)}encoding.h error.$(OBJEXT): {$(VPATH)}error.c @@ -6709,7 +6739,9 @@ error.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h error.$(OBJEXT): {$(VPATH)}thread_native.h error.$(OBJEXT): {$(VPATH)}util.h error.$(OBJEXT): {$(VPATH)}vm_core.h +error.$(OBJEXT): {$(VPATH)}vm_debug.h error.$(OBJEXT): {$(VPATH)}vm_opts.h +error.$(OBJEXT): {$(VPATH)}vm_sync.h error.$(OBJEXT): {$(VPATH)}warning.rbinc error.$(OBJEXT): {$(VPATH)}yjit.h eval.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h @@ -6741,7 +6773,6 @@ eval.$(OBJEXT): $(top_srcdir)/internal/variable.h eval.$(OBJEXT): $(top_srcdir)/internal/vm.h eval.$(OBJEXT): $(top_srcdir)/internal/warnings.h eval.$(OBJEXT): $(top_srcdir)/prism/defines.h -eval.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h eval.$(OBJEXT): $(top_srcdir)/prism/encoding.h eval.$(OBJEXT): $(top_srcdir)/prism/node.h eval.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -6758,7 +6789,6 @@ eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -6944,6 +6974,7 @@ eval.$(OBJEXT): {$(VPATH)}node.h eval.$(OBJEXT): {$(VPATH)}onigmo.h eval.$(OBJEXT): {$(VPATH)}oniguruma.h eval.$(OBJEXT): {$(VPATH)}prism/ast.h +eval.$(OBJEXT): {$(VPATH)}prism/diagnostic.h eval.$(OBJEXT): {$(VPATH)}prism/version.h eval.$(OBJEXT): {$(VPATH)}prism_compile.h eval.$(OBJEXT): {$(VPATH)}probes.dmyh @@ -7222,7 +7253,6 @@ gc.$(OBJEXT): $(top_srcdir)/internal/variable.h gc.$(OBJEXT): $(top_srcdir)/internal/vm.h gc.$(OBJEXT): $(top_srcdir)/internal/warnings.h gc.$(OBJEXT): $(top_srcdir)/prism/defines.h -gc.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h gc.$(OBJEXT): $(top_srcdir)/prism/encoding.h gc.$(OBJEXT): $(top_srcdir)/prism/node.h gc.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -7239,7 +7269,6 @@ gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -7427,6 +7456,7 @@ gc.$(OBJEXT): {$(VPATH)}node.h gc.$(OBJEXT): {$(VPATH)}onigmo.h gc.$(OBJEXT): {$(VPATH)}oniguruma.h gc.$(OBJEXT): {$(VPATH)}prism/ast.h +gc.$(OBJEXT): {$(VPATH)}prism/diagnostic.h gc.$(OBJEXT): {$(VPATH)}prism/version.h gc.$(OBJEXT): {$(VPATH)}prism_compile.h gc.$(OBJEXT): {$(VPATH)}probes.dmyh @@ -7470,6 +7500,7 @@ goruby.$(OBJEXT): $(top_srcdir)/internal/fixnum.h goruby.$(OBJEXT): $(top_srcdir)/internal/gc.h goruby.$(OBJEXT): $(top_srcdir)/internal/imemo.h goruby.$(OBJEXT): $(top_srcdir)/internal/numeric.h +goruby.$(OBJEXT): $(top_srcdir)/internal/parse.h goruby.$(OBJEXT): $(top_srcdir)/internal/rational.h goruby.$(OBJEXT): $(top_srcdir)/internal/ruby_parser.h goruby.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -7479,7 +7510,6 @@ goruby.$(OBJEXT): $(top_srcdir)/internal/variable.h goruby.$(OBJEXT): $(top_srcdir)/internal/vm.h goruby.$(OBJEXT): $(top_srcdir)/internal/warnings.h goruby.$(OBJEXT): $(top_srcdir)/prism/defines.h -goruby.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h goruby.$(OBJEXT): $(top_srcdir)/prism/encoding.h goruby.$(OBJEXT): $(top_srcdir)/prism/node.h goruby.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -7496,7 +7526,6 @@ goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -7679,6 +7708,7 @@ goruby.$(OBJEXT): {$(VPATH)}node.h goruby.$(OBJEXT): {$(VPATH)}onigmo.h goruby.$(OBJEXT): {$(VPATH)}oniguruma.h goruby.$(OBJEXT): {$(VPATH)}prism/ast.h +goruby.$(OBJEXT): {$(VPATH)}prism/diagnostic.h goruby.$(OBJEXT): {$(VPATH)}prism/version.h goruby.$(OBJEXT): {$(VPATH)}prism_compile.h goruby.$(OBJEXT): {$(VPATH)}ruby_assert.h @@ -7723,7 +7753,6 @@ hash.$(OBJEXT): $(top_srcdir)/internal/variable.h hash.$(OBJEXT): $(top_srcdir)/internal/vm.h hash.$(OBJEXT): $(top_srcdir)/internal/warnings.h hash.$(OBJEXT): $(top_srcdir)/prism/defines.h -hash.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h hash.$(OBJEXT): $(top_srcdir)/prism/encoding.h hash.$(OBJEXT): $(top_srcdir)/prism/node.h hash.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -7740,7 +7769,6 @@ hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -7922,6 +7950,7 @@ hash.$(OBJEXT): {$(VPATH)}node.h hash.$(OBJEXT): {$(VPATH)}onigmo.h hash.$(OBJEXT): {$(VPATH)}oniguruma.h hash.$(OBJEXT): {$(VPATH)}prism/ast.h +hash.$(OBJEXT): {$(VPATH)}prism/diagnostic.h hash.$(OBJEXT): {$(VPATH)}prism/version.h hash.$(OBJEXT): {$(VPATH)}prism_compile.h hash.$(OBJEXT): {$(VPATH)}probes.dmyh @@ -8353,6 +8382,7 @@ io.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h io.$(OBJEXT): {$(VPATH)}builtin.h io.$(OBJEXT): {$(VPATH)}config.h io.$(OBJEXT): {$(VPATH)}constant.h +io.$(OBJEXT): {$(VPATH)}debug_counter.h io.$(OBJEXT): {$(VPATH)}defines.h io.$(OBJEXT): {$(VPATH)}dln.h io.$(OBJEXT): {$(VPATH)}encindex.h @@ -8531,7 +8561,9 @@ io.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h io.$(OBJEXT): {$(VPATH)}thread_native.h io.$(OBJEXT): {$(VPATH)}util.h io.$(OBJEXT): {$(VPATH)}vm_core.h +io.$(OBJEXT): {$(VPATH)}vm_debug.h io.$(OBJEXT): {$(VPATH)}vm_opts.h +io.$(OBJEXT): {$(VPATH)}vm_sync.h io_buffer.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h io_buffer.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h io_buffer.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -8759,7 +8791,6 @@ iseq.$(OBJEXT): $(top_srcdir)/internal/variable.h iseq.$(OBJEXT): $(top_srcdir)/internal/vm.h iseq.$(OBJEXT): $(top_srcdir)/internal/warnings.h iseq.$(OBJEXT): $(top_srcdir)/prism/defines.h -iseq.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h iseq.$(OBJEXT): $(top_srcdir)/prism/encoding.h iseq.$(OBJEXT): $(top_srcdir)/prism/node.h iseq.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -8776,7 +8807,6 @@ iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -8963,6 +8993,7 @@ iseq.$(OBJEXT): {$(VPATH)}node.h iseq.$(OBJEXT): {$(VPATH)}onigmo.h iseq.$(OBJEXT): {$(VPATH)}oniguruma.h iseq.$(OBJEXT): {$(VPATH)}prism/ast.h +iseq.$(OBJEXT): {$(VPATH)}prism/diagnostic.h iseq.$(OBJEXT): {$(VPATH)}prism/prism.h iseq.$(OBJEXT): {$(VPATH)}prism/version.h iseq.$(OBJEXT): {$(VPATH)}prism_compile.h @@ -9016,7 +9047,6 @@ load.$(OBJEXT): $(top_srcdir)/internal/variable.h load.$(OBJEXT): $(top_srcdir)/internal/vm.h load.$(OBJEXT): $(top_srcdir)/internal/warnings.h load.$(OBJEXT): $(top_srcdir)/prism/defines.h -load.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h load.$(OBJEXT): $(top_srcdir)/prism/encoding.h load.$(OBJEXT): $(top_srcdir)/prism/node.h load.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -9033,7 +9063,6 @@ load.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -load.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -9216,6 +9245,7 @@ load.$(OBJEXT): {$(VPATH)}node.h load.$(OBJEXT): {$(VPATH)}onigmo.h load.$(OBJEXT): {$(VPATH)}oniguruma.h load.$(OBJEXT): {$(VPATH)}prism/ast.h +load.$(OBJEXT): {$(VPATH)}prism/diagnostic.h load.$(OBJEXT): {$(VPATH)}prism/version.h load.$(OBJEXT): {$(VPATH)}prism_compile.h load.$(OBJEXT): {$(VPATH)}probes.dmyh @@ -10338,6 +10368,7 @@ miniinit.$(OBJEXT): $(top_srcdir)/internal/fixnum.h miniinit.$(OBJEXT): $(top_srcdir)/internal/gc.h miniinit.$(OBJEXT): $(top_srcdir)/internal/imemo.h miniinit.$(OBJEXT): $(top_srcdir)/internal/numeric.h +miniinit.$(OBJEXT): $(top_srcdir)/internal/parse.h miniinit.$(OBJEXT): $(top_srcdir)/internal/rational.h miniinit.$(OBJEXT): $(top_srcdir)/internal/ruby_parser.h miniinit.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -10347,7 +10378,6 @@ miniinit.$(OBJEXT): $(top_srcdir)/internal/variable.h miniinit.$(OBJEXT): $(top_srcdir)/internal/vm.h miniinit.$(OBJEXT): $(top_srcdir)/internal/warnings.h miniinit.$(OBJEXT): $(top_srcdir)/prism/defines.h -miniinit.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h miniinit.$(OBJEXT): $(top_srcdir)/prism/encoding.h miniinit.$(OBJEXT): $(top_srcdir)/prism/node.h miniinit.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -10364,7 +10394,6 @@ miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -10559,6 +10588,7 @@ miniinit.$(OBJEXT): {$(VPATH)}oniguruma.h miniinit.$(OBJEXT): {$(VPATH)}pack.rb miniinit.$(OBJEXT): {$(VPATH)}prelude.rb miniinit.$(OBJEXT): {$(VPATH)}prism/ast.h +miniinit.$(OBJEXT): {$(VPATH)}prism/diagnostic.h miniinit.$(OBJEXT): {$(VPATH)}prism/version.h miniinit.$(OBJEXT): {$(VPATH)}prism_compile.h miniinit.$(OBJEXT): {$(VPATH)}ractor.rb @@ -10790,6 +10820,7 @@ node_dump.$(OBJEXT): $(top_srcdir)/internal/array.h node_dump.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h node_dump.$(OBJEXT): $(top_srcdir)/internal/bignum.h node_dump.$(OBJEXT): $(top_srcdir)/internal/bits.h +node_dump.$(OBJEXT): $(top_srcdir)/internal/class.h node_dump.$(OBJEXT): $(top_srcdir)/internal/compilers.h node_dump.$(OBJEXT): $(top_srcdir)/internal/complex.h node_dump.$(OBJEXT): $(top_srcdir)/internal/fixnum.h @@ -10797,6 +10828,7 @@ node_dump.$(OBJEXT): $(top_srcdir)/internal/gc.h node_dump.$(OBJEXT): $(top_srcdir)/internal/hash.h node_dump.$(OBJEXT): $(top_srcdir)/internal/imemo.h node_dump.$(OBJEXT): $(top_srcdir)/internal/numeric.h +node_dump.$(OBJEXT): $(top_srcdir)/internal/parse.h node_dump.$(OBJEXT): $(top_srcdir)/internal/rational.h node_dump.$(OBJEXT): $(top_srcdir)/internal/ruby_parser.h node_dump.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h @@ -10818,6 +10850,7 @@ node_dump.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h node_dump.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h node_dump.$(OBJEXT): {$(VPATH)}config.h node_dump.$(OBJEXT): {$(VPATH)}constant.h +node_dump.$(OBJEXT): {$(VPATH)}debug_counter.h node_dump.$(OBJEXT): {$(VPATH)}defines.h node_dump.$(OBJEXT): {$(VPATH)}encoding.h node_dump.$(OBJEXT): {$(VPATH)}id.h @@ -10987,7 +11020,9 @@ node_dump.$(OBJEXT): {$(VPATH)}subst.h node_dump.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h node_dump.$(OBJEXT): {$(VPATH)}thread_native.h node_dump.$(OBJEXT): {$(VPATH)}vm_core.h +node_dump.$(OBJEXT): {$(VPATH)}vm_debug.h node_dump.$(OBJEXT): {$(VPATH)}vm_opts.h +node_dump.$(OBJEXT): {$(VPATH)}vm_sync.h numeric.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h numeric.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h numeric.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -11930,7 +11965,6 @@ parser_st.$(OBJEXT): {$(VPATH)}st.c prism/api_node.$(OBJEXT): $(hdrdir)/ruby.h prism/api_node.$(OBJEXT): $(hdrdir)/ruby/ruby.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/defines.h -prism/api_node.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/extension.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/node.h @@ -11948,7 +11982,6 @@ prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12119,6 +12152,7 @@ prism/api_node.$(OBJEXT): {$(VPATH)}onigmo.h prism/api_node.$(OBJEXT): {$(VPATH)}oniguruma.h prism/api_node.$(OBJEXT): {$(VPATH)}prism/api_node.c prism/api_node.$(OBJEXT): {$(VPATH)}prism/ast.h +prism/api_node.$(OBJEXT): {$(VPATH)}prism/diagnostic.h prism/api_node.$(OBJEXT): {$(VPATH)}prism/version.h prism/api_node.$(OBJEXT): {$(VPATH)}st.h prism/api_node.$(OBJEXT): {$(VPATH)}subst.h @@ -12126,7 +12160,6 @@ prism/api_pack.$(OBJEXT): $(hdrdir)/ruby.h prism/api_pack.$(OBJEXT): $(hdrdir)/ruby/ruby.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/api_pack.c prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/defines.h -prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/extension.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/node.h @@ -12144,7 +12177,6 @@ prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/api_pack.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12314,12 +12346,11 @@ prism/api_pack.$(OBJEXT): {$(VPATH)}missing.h prism/api_pack.$(OBJEXT): {$(VPATH)}onigmo.h prism/api_pack.$(OBJEXT): {$(VPATH)}oniguruma.h prism/api_pack.$(OBJEXT): {$(VPATH)}prism/ast.h +prism/api_pack.$(OBJEXT): {$(VPATH)}prism/diagnostic.h prism/api_pack.$(OBJEXT): {$(VPATH)}prism/version.h prism/api_pack.$(OBJEXT): {$(VPATH)}st.h prism/api_pack.$(OBJEXT): {$(VPATH)}subst.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/defines.h -prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/diagnostic.c -prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -12327,17 +12358,16 @@ 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 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 -prism/extension.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/extension.c prism/extension.$(OBJEXT): $(top_srcdir)/prism/extension.h @@ -12356,7 +12386,6 @@ prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12526,11 +12555,11 @@ prism/extension.$(OBJEXT): {$(VPATH)}missing.h prism/extension.$(OBJEXT): {$(VPATH)}onigmo.h prism/extension.$(OBJEXT): {$(VPATH)}oniguruma.h prism/extension.$(OBJEXT): {$(VPATH)}prism/ast.h +prism/extension.$(OBJEXT): {$(VPATH)}prism/diagnostic.h prism/extension.$(OBJEXT): {$(VPATH)}prism/version.h prism/extension.$(OBJEXT): {$(VPATH)}st.h prism/extension.$(OBJEXT): {$(VPATH)}subst.h prism/node.$(OBJEXT): $(top_srcdir)/prism/defines.h -prism/node.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h prism/node.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/node.$(OBJEXT): $(top_srcdir)/prism/node.h prism/node.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -12538,19 +12567,19 @@ prism/node.$(OBJEXT): $(top_srcdir)/prism/pack.h prism/node.$(OBJEXT): $(top_srcdir)/prism/parser.h prism/node.$(OBJEXT): $(top_srcdir)/prism/prism.h prism/node.$(OBJEXT): $(top_srcdir)/prism/regexp.h +prism/node.$(OBJEXT): $(top_srcdir)/prism/static_literals.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h 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 prism/options.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/options.$(OBJEXT): $(top_srcdir)/prism/options.c @@ -12559,26 +12588,23 @@ 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 prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/parser.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h +prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/static_literals.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h 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 -prism/prism.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/node.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -12596,14 +12622,13 @@ prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h 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 prism/regexp.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/encoding.h @@ -12611,6 +12636,7 @@ prism/regexp.$(OBJEXT): $(top_srcdir)/prism/options.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/parser.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/regexp.c prism/regexp.$(OBJEXT): $(top_srcdir)/prism/regexp.h +prism/regexp.$(OBJEXT): $(top_srcdir)/prism/static_literals.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -12618,14 +12644,11 @@ prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -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/diagnostic.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/node.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -12642,13 +12665,12 @@ prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h 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 prism/serialize.$(OBJEXT): {$(VPATH)}prism/version.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/defines.h @@ -12664,7 +12686,6 @@ prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h prism/static_literals.$(OBJEXT): {$(VPATH)}prism/ast.h @@ -12675,7 +12696,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 @@ -12683,16 +12703,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 @@ -12702,7 +12719,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 @@ -12711,53 +12727,42 @@ prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.c prism/util/pm_memchr.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h 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 prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/defines.h -prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/options.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/parser.h +prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/static_literals.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h 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 prism_init.$(OBJEXT): $(hdrdir)/ruby/ruby.h prism_init.$(OBJEXT): $(top_srcdir)/prism/defines.h -prism_init.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h prism_init.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism_init.$(OBJEXT): $(top_srcdir)/prism/extension.h prism_init.$(OBJEXT): $(top_srcdir)/prism/node.h @@ -12775,7 +12780,6 @@ prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -12946,6 +12950,7 @@ prism_init.$(OBJEXT): {$(VPATH)}missing.h prism_init.$(OBJEXT): {$(VPATH)}onigmo.h prism_init.$(OBJEXT): {$(VPATH)}oniguruma.h prism_init.$(OBJEXT): {$(VPATH)}prism/ast.h +prism_init.$(OBJEXT): {$(VPATH)}prism/diagnostic.h prism_init.$(OBJEXT): {$(VPATH)}prism/version.h prism_init.$(OBJEXT): {$(VPATH)}prism_init.c prism_init.$(OBJEXT): {$(VPATH)}st.h @@ -12975,7 +12980,6 @@ proc.$(OBJEXT): $(top_srcdir)/internal/variable.h proc.$(OBJEXT): $(top_srcdir)/internal/vm.h proc.$(OBJEXT): $(top_srcdir)/internal/warnings.h proc.$(OBJEXT): $(top_srcdir)/prism/defines.h -proc.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h proc.$(OBJEXT): $(top_srcdir)/prism/encoding.h proc.$(OBJEXT): $(top_srcdir)/prism/node.h proc.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -12992,7 +12996,6 @@ proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -13173,6 +13176,7 @@ proc.$(OBJEXT): {$(VPATH)}node.h proc.$(OBJEXT): {$(VPATH)}onigmo.h proc.$(OBJEXT): {$(VPATH)}oniguruma.h proc.$(OBJEXT): {$(VPATH)}prism/ast.h +proc.$(OBJEXT): {$(VPATH)}prism/diagnostic.h proc.$(OBJEXT): {$(VPATH)}prism/version.h proc.$(OBJEXT): {$(VPATH)}prism_compile.h proc.$(OBJEXT): {$(VPATH)}proc.c @@ -15459,7 +15463,6 @@ rjit.$(OBJEXT): $(top_srcdir)/internal/variable.h rjit.$(OBJEXT): $(top_srcdir)/internal/vm.h rjit.$(OBJEXT): $(top_srcdir)/internal/warnings.h rjit.$(OBJEXT): $(top_srcdir)/prism/defines.h -rjit.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h rjit.$(OBJEXT): $(top_srcdir)/prism/encoding.h rjit.$(OBJEXT): $(top_srcdir)/prism/node.h rjit.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -15476,7 +15479,6 @@ rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h rjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -15662,6 +15664,7 @@ rjit.$(OBJEXT): {$(VPATH)}node.h rjit.$(OBJEXT): {$(VPATH)}onigmo.h rjit.$(OBJEXT): {$(VPATH)}oniguruma.h rjit.$(OBJEXT): {$(VPATH)}prism/ast.h +rjit.$(OBJEXT): {$(VPATH)}prism/diagnostic.h rjit.$(OBJEXT): {$(VPATH)}prism/version.h rjit.$(OBJEXT): {$(VPATH)}prism_compile.h rjit.$(OBJEXT): {$(VPATH)}ractor.h @@ -15713,7 +15716,6 @@ rjit_c.$(OBJEXT): $(top_srcdir)/internal/variable.h rjit_c.$(OBJEXT): $(top_srcdir)/internal/vm.h rjit_c.$(OBJEXT): $(top_srcdir)/internal/warnings.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/defines.h -rjit_c.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/encoding.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/node.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -15730,7 +15732,6 @@ rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h rjit_c.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -15915,6 +15916,7 @@ rjit_c.$(OBJEXT): {$(VPATH)}node.h rjit_c.$(OBJEXT): {$(VPATH)}onigmo.h rjit_c.$(OBJEXT): {$(VPATH)}oniguruma.h rjit_c.$(OBJEXT): {$(VPATH)}prism/ast.h +rjit_c.$(OBJEXT): {$(VPATH)}prism/diagnostic.h rjit_c.$(OBJEXT): {$(VPATH)}prism/version.h rjit_c.$(OBJEXT): {$(VPATH)}prism_compile.h rjit_c.$(OBJEXT): {$(VPATH)}probes.dmyh @@ -15993,7 +15995,6 @@ ruby.$(OBJEXT): $(top_srcdir)/internal/variable.h ruby.$(OBJEXT): $(top_srcdir)/internal/vm.h ruby.$(OBJEXT): $(top_srcdir)/internal/warnings.h ruby.$(OBJEXT): $(top_srcdir)/prism/defines.h -ruby.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h ruby.$(OBJEXT): $(top_srcdir)/prism/encoding.h ruby.$(OBJEXT): $(top_srcdir)/prism/node.h ruby.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -16010,7 +16011,6 @@ ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -16193,6 +16193,7 @@ ruby.$(OBJEXT): {$(VPATH)}node.h ruby.$(OBJEXT): {$(VPATH)}onigmo.h ruby.$(OBJEXT): {$(VPATH)}oniguruma.h ruby.$(OBJEXT): {$(VPATH)}prism/ast.h +ruby.$(OBJEXT): {$(VPATH)}prism/diagnostic.h ruby.$(OBJEXT): {$(VPATH)}prism/version.h ruby.$(OBJEXT): {$(VPATH)}prism_compile.h ruby.$(OBJEXT): {$(VPATH)}rjit.h @@ -16220,6 +16221,7 @@ ruby_parser.$(OBJEXT): $(top_srcdir)/internal/error.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/fixnum.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/imemo.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/numeric.h +ruby_parser.$(OBJEXT): $(top_srcdir)/internal/parse.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/rational.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/re.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/ruby_parser.h @@ -18435,7 +18437,6 @@ thread.$(OBJEXT): $(top_srcdir)/internal/variable.h thread.$(OBJEXT): $(top_srcdir)/internal/vm.h thread.$(OBJEXT): $(top_srcdir)/internal/warnings.h thread.$(OBJEXT): $(top_srcdir)/prism/defines.h -thread.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h thread.$(OBJEXT): $(top_srcdir)/prism/encoding.h thread.$(OBJEXT): $(top_srcdir)/prism/node.h thread.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -18452,7 +18453,6 @@ thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -18639,6 +18639,7 @@ thread.$(OBJEXT): {$(VPATH)}node.h thread.$(OBJEXT): {$(VPATH)}onigmo.h thread.$(OBJEXT): {$(VPATH)}oniguruma.h thread.$(OBJEXT): {$(VPATH)}prism/ast.h +thread.$(OBJEXT): {$(VPATH)}prism/diagnostic.h thread.$(OBJEXT): {$(VPATH)}prism/version.h thread.$(OBJEXT): {$(VPATH)}prism_compile.h thread.$(OBJEXT): {$(VPATH)}ractor.h @@ -19697,7 +19698,6 @@ vm.$(OBJEXT): $(top_srcdir)/internal/variable.h vm.$(OBJEXT): $(top_srcdir)/internal/vm.h vm.$(OBJEXT): $(top_srcdir)/internal/warnings.h vm.$(OBJEXT): $(top_srcdir)/prism/defines.h -vm.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h vm.$(OBJEXT): $(top_srcdir)/prism/encoding.h vm.$(OBJEXT): $(top_srcdir)/prism/node.h vm.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -19714,7 +19714,6 @@ vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -19900,6 +19899,7 @@ vm.$(OBJEXT): {$(VPATH)}node.h vm.$(OBJEXT): {$(VPATH)}onigmo.h vm.$(OBJEXT): {$(VPATH)}oniguruma.h vm.$(OBJEXT): {$(VPATH)}prism/ast.h +vm.$(OBJEXT): {$(VPATH)}prism/diagnostic.h vm.$(OBJEXT): {$(VPATH)}prism/version.h vm.$(OBJEXT): {$(VPATH)}prism_compile.h vm.$(OBJEXT): {$(VPATH)}probes.dmyh @@ -19956,7 +19956,6 @@ vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/variable.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/vm.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/warnings.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/defines.h -vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/encoding.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/node.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -19973,7 +19972,6 @@ vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -20155,6 +20153,7 @@ vm_backtrace.$(OBJEXT): {$(VPATH)}node.h vm_backtrace.$(OBJEXT): {$(VPATH)}onigmo.h vm_backtrace.$(OBJEXT): {$(VPATH)}oniguruma.h vm_backtrace.$(OBJEXT): {$(VPATH)}prism/ast.h +vm_backtrace.$(OBJEXT): {$(VPATH)}prism/diagnostic.h vm_backtrace.$(OBJEXT): {$(VPATH)}prism/version.h vm_backtrace.$(OBJEXT): {$(VPATH)}prism_compile.h vm_backtrace.$(OBJEXT): {$(VPATH)}ruby_assert.h @@ -20187,7 +20186,6 @@ vm_dump.$(OBJEXT): $(top_srcdir)/internal/variable.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/vm.h vm_dump.$(OBJEXT): $(top_srcdir)/internal/warnings.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/defines.h -vm_dump.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/encoding.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/node.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -20204,7 +20202,6 @@ vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -20384,6 +20381,7 @@ vm_dump.$(OBJEXT): {$(VPATH)}node.h vm_dump.$(OBJEXT): {$(VPATH)}onigmo.h vm_dump.$(OBJEXT): {$(VPATH)}oniguruma.h vm_dump.$(OBJEXT): {$(VPATH)}prism/ast.h +vm_dump.$(OBJEXT): {$(VPATH)}prism/diagnostic.h vm_dump.$(OBJEXT): {$(VPATH)}prism/version.h vm_dump.$(OBJEXT): {$(VPATH)}prism_compile.h vm_dump.$(OBJEXT): {$(VPATH)}procstat_vm.c @@ -20629,7 +20627,6 @@ vm_trace.$(OBJEXT): $(top_srcdir)/internal/variable.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/vm.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/warnings.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/defines.h -vm_trace.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/encoding.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/node.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -20646,7 +20643,6 @@ vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -20829,6 +20825,7 @@ vm_trace.$(OBJEXT): {$(VPATH)}node.h vm_trace.$(OBJEXT): {$(VPATH)}onigmo.h vm_trace.$(OBJEXT): {$(VPATH)}oniguruma.h vm_trace.$(OBJEXT): {$(VPATH)}prism/ast.h +vm_trace.$(OBJEXT): {$(VPATH)}prism/diagnostic.h vm_trace.$(OBJEXT): {$(VPATH)}prism/version.h vm_trace.$(OBJEXT): {$(VPATH)}prism_compile.h vm_trace.$(OBJEXT): {$(VPATH)}ractor.h @@ -21071,7 +21068,6 @@ yjit.$(OBJEXT): $(top_srcdir)/internal/variable.h yjit.$(OBJEXT): $(top_srcdir)/internal/vm.h yjit.$(OBJEXT): $(top_srcdir)/internal/warnings.h yjit.$(OBJEXT): $(top_srcdir)/prism/defines.h -yjit.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h yjit.$(OBJEXT): $(top_srcdir)/prism/encoding.h yjit.$(OBJEXT): $(top_srcdir)/prism/node.h yjit.$(OBJEXT): $(top_srcdir)/prism/options.h @@ -21088,7 +21084,6 @@ yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_integer.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_list.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_memchr.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h -yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_state_stack.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_string_list.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h @@ -21274,6 +21269,7 @@ yjit.$(OBJEXT): {$(VPATH)}node.h yjit.$(OBJEXT): {$(VPATH)}onigmo.h yjit.$(OBJEXT): {$(VPATH)}oniguruma.h yjit.$(OBJEXT): {$(VPATH)}prism/ast.h +yjit.$(OBJEXT): {$(VPATH)}prism/diagnostic.h yjit.$(OBJEXT): {$(VPATH)}prism/version.h yjit.$(OBJEXT): {$(VPATH)}prism_compile.h yjit.$(OBJEXT): {$(VPATH)}probes.dmyh diff --git a/compile.c b/compile.c index 810e206adfa61f..1ee7fdb6aece57 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; @@ -1480,20 +1478,16 @@ new_child_iseq(rb_iseq_t *iseq, const NODE *const node, VALUE name, const rb_iseq_t *parent, enum rb_iseq_type type, int line_no) { rb_iseq_t *ret_iseq; - rb_ast_body_t ast; - - ast.root = node; - ast.frozen_string_literal = -1; - ast.coverage_enabled = -1; - ast.script_lines = ISEQ_BODY(iseq)->variable.script_lines; + VALUE vast = rb_ruby_ast_new(node, NULL); debugs("[new_child_iseq]> ---------------------------------------\n"); int isolated_depth = ISEQ_COMPILE_DATA(iseq)->isolated_depth; - ret_iseq = rb_iseq_new_with_opt(&ast, name, + ret_iseq = rb_iseq_new_with_opt(vast, name, rb_iseq_path(iseq), rb_iseq_realpath(iseq), line_no, parent, isolated_depth ? isolated_depth + 1 : 0, - type, ISEQ_COMPILE_DATA(iseq)->option); + type, ISEQ_COMPILE_DATA(iseq)->option, + ISEQ_BODY(iseq)->variable.script_lines); debugs("[new_child_iseq]< ---------------------------------------\n"); return ret_iseq; } @@ -1925,9 +1919,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; @@ -2002,6 +1993,22 @@ iseq_set_arguments_keywords(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, return arg_size; } +static void +iseq_set_use_block(rb_iseq_t *iseq) +{ + struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); + if (!body->param.flags.use_block) { + body->param.flags.use_block = 1; + + rb_vm_t *vm = GET_VM(); + + if (!vm->unused_block_warning_strict) { + st_data_t key = (st_data_t)rb_intern_str(body->location.label); // String -> ID + st_insert(vm->unused_block_warning_table, key, 1); + } + } +} + static int iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *const node_args) { @@ -2103,6 +2110,7 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons if (block_id) { body->param.block_start = arg_size++; body->param.flags.has_block = TRUE; + iseq_set_use_block(iseq); } iseq_calc_param_size(iseq); @@ -3192,7 +3200,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 +3218,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 +3910,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 +3927,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 +3941,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 +3961,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 +3976,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 +4011,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 +4351,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 +4390,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 +4516,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 +4596,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 +4644,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 +4719,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 +4743,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 +4778,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 +5064,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 +5129,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 +5154,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 +5178,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 +5804,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: @@ -5925,6 +5931,7 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, ADD_INSN(ret, line_node, putnil); ADD_INSN3(ret, line_node, defined, INT2FIX(DEFINED_YIELD), 0, PUSH_VAL(DEFINED_YIELD)); + iseq_set_use_block(ISEQ_BODY(iseq)->local_iseq); return; case NODE_BACK_REF: @@ -6381,8 +6388,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 +6403,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 +7228,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 +8458,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 +8480,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 +8629,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; } @@ -8643,6 +8642,9 @@ compile_builtin_attr(rb_iseq_t *iseq, const NODE *node) else if (strcmp(RSTRING_PTR(string), "inline_block") == 0) { ISEQ_BODY(iseq)->builtin_attrs |= BUILTIN_ATTR_INLINE_BLOCK; } + else if (strcmp(RSTRING_PTR(string), "use_block") == 0) { + iseq_set_use_block(iseq); + } else { goto unknown_arg; } @@ -8676,9 +8678,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; } @@ -8749,18 +8748,14 @@ compile_builtin_mandatory_only_method(rb_iseq_t *iseq, const NODE *node, const N scope_node.nd_body = mandatory_node(iseq, node); scope_node.nd_args = &args_node; - rb_ast_body_t ast = { - .root = RNODE(&scope_node), - .frozen_string_literal = -1, - .coverage_enabled = -1, - .script_lines = ISEQ_BODY(iseq)->variable.script_lines, - }; + VALUE vast = rb_ruby_ast_new(RNODE(&scope_node), NULL); ISEQ_BODY(iseq)->mandatory_only_iseq = - rb_iseq_new_with_opt(&ast, rb_iseq_base_label(iseq), + rb_iseq_new_with_opt(vast, rb_iseq_base_label(iseq), rb_iseq_path(iseq), rb_iseq_realpath(iseq), nd_line(line_node), NULL, 0, - ISEQ_TYPE_METHOD, ISEQ_COMPILE_DATA(iseq)->option); + ISEQ_TYPE_METHOD, ISEQ_COMPILE_DATA(iseq)->option, + ISEQ_BODY(iseq)->variable.script_lines); ALLOCV_END(idtmp); return COMPILE_OK; @@ -8917,20 +8912,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 +9004,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 +9037,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 +9072,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 +9101,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 +9119,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 +9250,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 +9295,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 +9309,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 */ @@ -9483,9 +9377,11 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i unsigned int flag = 0; struct rb_callinfo_kwarg *keywords = NULL; const rb_iseq_t *parent_block = ISEQ_COMPILE_DATA(iseq)->current_block; + int use_block = 1; INIT_ANCHOR(args); ISEQ_COMPILE_DATA(iseq)->current_block = NULL; + if (type == NODE_SUPER) { VALUE vargc = setup_args(iseq, args, RNODE_SUPER(node)->nd_args, &flag, &keywords); CHECK(!NIL_P(vargc)); @@ -9493,6 +9389,10 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i if ((flag & VM_CALL_ARGS_BLOCKARG) && (flag & VM_CALL_KW_SPLAT) && !(flag & VM_CALL_KW_SPLAT_MUT)) { ADD_INSN(args, node, splatkw); } + + if (flag & VM_CALL_ARGS_BLOCKARG) { + use_block = 0; + } } else { /* NODE_ZSUPER */ @@ -9586,6 +9486,10 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i } } + if (use_block && parent_block == NULL) { + iseq_set_use_block(ISEQ_BODY(iseq)->local_iseq); + } + flag |= VM_CALL_SUPER | VM_CALL_FCALL; if (type == NODE_ZSUPER) flag |= VM_CALL_ZSUPER; ADD_INSN(ret, node, putself); @@ -9629,6 +9533,7 @@ compile_yield(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i ADD_SEQ(ret, args); ADD_INSN1(ret, node, invokeblock, new_callinfo(iseq, 0, FIX2INT(argc), flag, keywords, FALSE)); + iseq_set_use_block(ISEQ_BODY(iseq)->local_iseq); if (popped) { ADD_INSN(ret, node, pop); @@ -9814,8 +9719,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 +9766,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 +9805,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 +9826,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(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 +10369,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 +10381,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 +10538,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 +10597,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 +10632,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 +11544,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); @@ -11514,6 +11767,10 @@ rb_iseq_build_from_ary(rb_iseq_t *iseq, VALUE misc, VALUE locals, VALUE params, ISEQ_BODY(iseq)->param.flags.ambiguous_param0 = TRUE; } + if (Qtrue == rb_hash_aref(params, SYM(use_block))) { + ISEQ_BODY(iseq)->param.flags.use_block = TRUE; + } + if (int_param(&i, params, SYM(kwrest))) { struct rb_iseq_param_keyword *keyword = (struct rb_iseq_param_keyword *)ISEQ_BODY(iseq)->param.keyword; if (keyword == NULL) { @@ -11863,9 +12120,9 @@ ibf_load_id(const struct ibf_load *load, const ID id_index) return 0; } VALUE sym = ibf_load_object(load, id_index); - if (rb_type_p(sym, T_FIXNUM)) { + if (rb_integer_type_p(sym)) { /* Load hidden local variables as indexes */ - return FIX2INT(sym); + return NUM2ULONG(sym); } return rb_sym2id(sym); } @@ -12393,7 +12650,7 @@ ibf_dump_local_table(struct ibf_dump *dump, const rb_iseq_t *iseq) VALUE v = ibf_dump_id(dump, body->local_table[i]); if (v == 0) { /* Dump hidden local variables as indexes, so load_from_binary will work with them */ - v = ibf_dump_object(dump, ULONG2NUM(size-i+1)); + v = ibf_dump_object(dump, ULONG2NUM(body->local_table[i])); } table[i] = v; } @@ -12706,7 +12963,10 @@ ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq) (body->param.flags.has_block << 6) | (body->param.flags.ambiguous_param0 << 7) | (body->param.flags.accepts_no_kwarg << 8) | - (body->param.flags.ruby2_keywords << 9); + (body->param.flags.ruby2_keywords << 9) | + (body->param.flags.anon_rest << 10) | + (body->param.flags.anon_kwrest << 11) | + (body->param.flags.use_block << 12); #if IBF_ISEQ_ENABLE_LOCAL_BUFFER # define IBF_BODY_OFFSET(x) (x) @@ -12922,6 +13182,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) load_body->param.flags.ruby2_keywords = (param_flags >> 9) & 1; load_body->param.flags.anon_rest = (param_flags >> 10) & 1; load_body->param.flags.anon_kwrest = (param_flags >> 11) & 1; + load_body->param.flags.use_block = (param_flags >> 12) & 1; load_body->param.size = param_size; load_body->param.lead_num = param_lead_num; load_body->param.opt_num = param_opt_num; @@ -13943,6 +14204,8 @@ ibf_load_setup_bytes(struct ibf_load *load, VALUE loader_obj, const char *bytes, static void ibf_load_setup(struct ibf_load *load, VALUE loader_obj, VALUE str) { + StringValue(str); + if (RSTRING_LENINT(str) < (int)sizeof(struct ibf_header)) { rb_raise(rb_eRuntimeError, "broken binary format"); } diff --git a/complex.c b/complex.c index 92245bc9e83bdf..ff278f80fae3a6 100644 --- a/complex.c +++ b/complex.c @@ -397,7 +397,7 @@ nucomp_s_new_internal(VALUE klass, VALUE real, VALUE imag) RCOMPLEX_SET_REAL(obj, real); RCOMPLEX_SET_IMAG(obj, imag); - OBJ_FREEZE_RAW((VALUE)obj); + OBJ_FREEZE((VALUE)obj); return (VALUE)obj; } @@ -1717,7 +1717,7 @@ nucomp_loader(VALUE self, VALUE a) RCOMPLEX_SET_REAL(dat, rb_ivar_get(a, id_i_real)); RCOMPLEX_SET_IMAG(dat, rb_ivar_get(a, id_i_imag)); - OBJ_FREEZE_RAW(self); + OBJ_FREEZE(self); return self; } @@ -2723,7 +2723,7 @@ Init_Complex(void) f_complex_new_bang2(rb_cComplex, ZERO, ONE)); #if !USE_FLONUM - rb_gc_register_mark_object(RFLOAT_0 = DBL2NUM(0.0)); + rb_vm_register_global_object(RFLOAT_0 = DBL2NUM(0.0)); #endif rb_provide("complex.so"); /* for backward compatibility */ diff --git a/configure.ac b/configure.ac index e816e0ea4e5a32..c487128e3188c2 100644 --- a/configure.ac +++ b/configure.ac @@ -38,6 +38,7 @@ m4_include([tool/m4/ruby_replace_type.m4])dnl m4_include([tool/m4/ruby_require_funcs.m4])dnl m4_include([tool/m4/ruby_rm_recursive.m4])dnl m4_include([tool/m4/ruby_setjmp_type.m4])dnl +m4_include([tool/m4/ruby_shared_gc.m4])dnl m4_include([tool/m4/ruby_stack_grow_direction.m4])dnl m4_include([tool/m4/ruby_thread.m4])dnl m4_include([tool/m4/ruby_try_cflags.m4])dnl @@ -75,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 @@ -84,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) @@ -277,7 +278,7 @@ AC_CHECK_TOOLS([STRIP], [gstrip strip], [:]) # nm errors with Rust's LLVM bitcode when Rust uses a newer LLVM version than nm. # In case we're working with llvm-nm, tell it to not worry about the bitcode. -AS_IF([${NM} --help | grep -q 'llvm-bc'], [NM="$NM --no-llvm-bc"]) +AS_IF([${NM} --help 2>&1 | grep -q 'llvm-bc'], [NM="$NM --no-llvm-bc"]) AS_IF([test ! $rb_test_CFLAGS], [AS_UNSET(CFLAGS)]); AS_UNSET(rb_test_CFLAGS) AS_IF([test ! $rb_test_CXXFLAGS], [AS_UNSET(CXXFLAGS)]); AS_UNSET(rb_save_CXXFLAGS) @@ -426,15 +427,22 @@ AS_CASE(["$build_os"], # default spec. # Xcode linker warns for deprecated architecture and wrongly # installed TBD files. - CC_WRAPPER="" CC_NO_WRAPPER="$CC" + AC_MSG_CHECKING(for $CC linker warning) + suppress_ld_waring=no echo 'int main(void) {return 0;}' > conftest.c - AS_IF([$CC -framework Foundation -o conftest conftest.c 2>&1 | - grep -e '^ld: warning: ignoring duplicate libraries:' \ - -e '^ld: warning: text-based stub file' >/dev/null], [ - CC_WRAPPER=`cd -P "${tooldir}" && pwd`/darwin-cc - CC="$CC_WRAPPER $CC" + AS_IF([$CC -framework Foundation -o conftest -ggdb3 conftest.c 2>&1 | + grep \ + -e '^ld: warning: ignoring duplicate libraries:' \ + -e '^ld: warning: text-based stub file' \ + -e '^ld: warning: -multiply_defined is obsolete' \ + -e "^warning: '\.debug_macinfo'" \ + -e '^note: while processing' \ + >/dev/null], [ + suppress_ld_waring=yes ]) rm -fr conftest* + test $suppress_ld_waring = yes && warnflags="${warnflags:+${warnflags} }-Wl,-w" + AC_MSG_RESULT($suppress_ld_waring) ]) AS_CASE(["$target_os"], [wasi*], [ @@ -1110,7 +1118,7 @@ main() AC_CHECK_HEADERS(crt_externs.h, [], [], [ #include ]) - cleanlibs='$(TARGET_SO).dSYM' + cleanlibs='$(TARGET_SO:=.dSYM)' ], [solaris*], [ LIBS="-lm $LIBS" ac_cv_func_vfork=no @@ -1432,7 +1440,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 @@ -1767,7 +1775,7 @@ AC_CACHE_CHECK(for function name string predefined identifier, [AS_CASE(["$target_os"],[openbsd*],[ rb_cv_function_name_string=__func__ ],[ - rb_cv_function_name_string=no + rb_cv_function_name_string=no RUBY_WERROR_FLAG([ for func in __func__ __FUNCTION__; do AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], @@ -1775,7 +1783,8 @@ AC_CACHE_CHECK(for function name string predefined identifier, [rb_cv_function_name_string=$func break]) done - ])])] + ]) + ])] ) AS_IF([test "$rb_cv_function_name_string" != no], [ AC_DEFINE_UNQUOTED(RUBY_FUNCTION_NAME_STRING, [$rb_cv_function_name_string]) @@ -2099,7 +2108,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) @@ -2973,7 +2981,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]), [ @@ -3751,6 +3759,7 @@ AS_IF([test x"$gcov" = xyes], [ ]) RUBY_SETJMP_TYPE +RUBY_SHARED_GC } [begin]_group "installation section" && { @@ -4657,6 +4666,7 @@ config_summary "target OS" "$target_os" config_summary "compiler" "$CC" config_summary "with thread" "$THREAD_MODEL" config_summary "with coroutine" "$coroutine_type" +config_summary "with shared GC" "$with_shared_gc" config_summary "enable shared libs" "$ENABLE_SHARED" config_summary "dynamic library ext" "$DLEXT" config_summary "CFLAGS" "$cflags" diff --git a/cont.c b/cont.c index 5a805cc1ac846a..8f222dfef8bc95 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 @@ -2572,11 +2569,10 @@ rb_fiber_start(rb_fiber_t *fiber) void rb_threadptr_root_fiber_setup(rb_thread_t *th) { - rb_fiber_t *fiber = ruby_mimmalloc(sizeof(rb_fiber_t)); + rb_fiber_t *fiber = ruby_mimcalloc(1, sizeof(rb_fiber_t)); if (!fiber) { rb_bug("%s", strerror(errno)); /* ... is it possible to call rb_bug here? */ } - MEMZERO(fiber, rb_fiber_t, 1); fiber->cont.type = FIBER_CONTEXT; fiber->cont.saved_ec.fiber_ptr = fiber; fiber->cont.saved_ec.thread_ptr = th; @@ -3230,7 +3226,13 @@ rb_fiber_s_yield(int argc, VALUE *argv, VALUE klass) static VALUE fiber_raise(rb_fiber_t *fiber, VALUE exception) { - if (FIBER_SUSPENDED_P(fiber) && !fiber->yielding) { + if (fiber == fiber_current()) { + rb_exc_raise(exception); + } + else if (fiber->resuming_fiber) { + return fiber_raise(fiber->resuming_fiber, exception); + } + else if (FIBER_SUSPENDED_P(fiber) && !fiber->yielding) { return fiber_transfer_kw(fiber, -1, &exception, RB_NO_KEYWORDS); } else { @@ -3268,6 +3270,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..d24e3c3eb5f5c3 100644 --- a/darray.h +++ b/darray.h @@ -6,7 +6,6 @@ #include #include "internal/bits.h" -#include "internal/gc.h" // Type for a dynamic array. Use to declare a dynamic array. // It is a pointer so it fits in st_table nicely. Designed @@ -45,16 +44,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 +74,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) @@ -135,60 +124,18 @@ rb_darray_capa(const void *ary) static inline void rb_darray_free(void *ary) { - rb_darray_meta_t *meta = 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))); + xfree(ary); } /* 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 = xrealloc(meta, new_capa * element_size + header_size); if (meta == NULL) { /* First allocation. Initialize size. On subsequence allocations @@ -209,8 +156,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 +166,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 +178,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 = xcalloc(array_size * element_size + header_size, 1); 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/defs/gmake.mk b/defs/gmake.mk index 71b6c87b0d8a1c..c914b39690d50f 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -193,7 +193,7 @@ $(SCRIPTBINDIR): $(Q) mkdir $@ .PHONY: commit -COMMIT_PREPARE := $(filter-out commit do-commit,$(MAKECMDGOALS)) up +COMMIT_PREPARE := $(subst :,\:,$(filter-out commit do-commit,$(MAKECMDGOALS))) up commit: pre-commit $(DOT_WAIT) do-commit $(DOT_WAIT) post_commit pre-commit: $(COMMIT_PREPARE) @@ -469,6 +469,10 @@ benchmark/%: miniruby$(EXEEXT) update-benchmark-driver PHONY --executables="built-ruby::$(BENCH_RUBY) --disable-gem" \ $(srcdir)/$@ $(BENCH_OPTS) $(OPTS) +clean-local:: TARGET_SO = $(PROGRAM) $(WPROGRAM) $(LIBRUBY_SO) $(STATIC_RUBY) miniruby goruby +clean-local:: + -$(Q)$(RMALL) $(cleanlibs) + clean-srcs-ext:: $(Q)$(RM) $(patsubst $(srcdir)/%,%,$(EXT_SRCS)) @@ -509,10 +513,12 @@ ifneq ($(POSTLINK),) endif $(Q) $(RMALL) $@.* -rubyspec-capiext: $(patsubst %.c,$(RUBYSPEC_CAPIEXT)/%.$(DLEXT),$(notdir $(wildcard $(srcdir)/$(RUBYSPEC_CAPIEXT)/*.c))) +RUBYSPEC_CAPIEXT_SO := $(patsubst %.c,$(RUBYSPEC_CAPIEXT)/%.$(DLEXT),$(notdir $(wildcard $(srcdir)/$(RUBYSPEC_CAPIEXT)/*.c))) +rubyspec-capiext: $(RUBYSPEC_CAPIEXT_SO) @ $(NULLCMD) ifeq ($(ENABLE_SHARED),yes) +ruby: $(if $(LIBRUBY_SO_UPDATE),$(RUBYSPEC_CAPIEXT_SO)) exts: rubyspec-capiext endif 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/dir.rb b/dir.rb index 632c49eee93bab..42b475ab2cd82c 100644 --- a/dir.rb +++ b/dir.rb @@ -408,6 +408,7 @@ def self.[](*args, base: nil, sort: true) # specifies that patterns may match short names if they exist; Windows only. # def self.glob(pattern, _flags = 0, flags: _flags, base: nil, sort: true) + Primitive.attr! :use_block Primitive.dir_s_glob(pattern, flags, base, sort) end end diff --git a/dln.c b/dln.c index db6ea5aa0fb03d..1009f7806fd206 100644 --- a/dln.c +++ b/dln.c @@ -194,7 +194,6 @@ dln_strerror(char *message, size_t size) } return message; } -#define dln_strerror() dln_strerror(message, sizeof message) #elif defined USE_DLN_DLOPEN static const char * dln_strerror(void) @@ -339,16 +338,13 @@ dln_disable_dlclose(void) #endif #if defined(_WIN32) || defined(USE_DLN_DLOPEN) -static void * -dln_open(const char *file) +void * +dln_open(const char *file, char *error, size_t size) { static const char incompatible[] = "incompatible library version"; - const char *error = NULL; void *handle; #if defined(_WIN32) - char message[1024]; - /* Convert the file path to wide char */ WCHAR *winfile = rb_w32_mbstr_to_wstr(CP_UTF8, file, -1, NULL); if (!winfile) { @@ -360,15 +356,15 @@ dln_open(const char *file) free(winfile); if (!handle) { - error = dln_strerror(); - goto failed; + strlcpy(error, dln_strerror(error, size), size); + return NULL; } # if defined(RUBY_EXPORT) if (!rb_w32_check_imported(handle, rb_libruby_handle())) { FreeLibrary(handle); - error = incompatible; - goto failed; + strlcpy(error, incompatible, size); + return NULL; } # endif @@ -387,8 +383,8 @@ dln_open(const char *file) /* Load file */ handle = dlopen(file, RTLD_LAZY|RTLD_GLOBAL); if (handle == NULL) { - error = dln_strerror(); - goto failed; + strlcpy(error, dln_strerror(), size); + return NULL; } # if defined(RUBY_EXPORT) @@ -410,11 +406,15 @@ dln_open(const char *file) libruby_name = tmp; } dlclose(handle); + if (libruby_name) { - dln_loaderror("linked to incompatible %s - %s", libruby_name, file); + snprintf(error, size, "linked to incompatible %s - %s", libruby_name, file); + } + else { + strlcpy(error, incompatible, size); } - error = incompatible; - goto failed; + + return NULL; } } } @@ -422,12 +422,9 @@ dln_open(const char *file) #endif return handle; - - failed: - dln_loaderror("%s - %s", error, file); } -static void * +void * dln_sym(void *handle, const char *symbol) { #if defined(_WIN32) @@ -446,7 +443,7 @@ dln_sym_func(void *handle, const char *symbol) const char *error; #if defined(_WIN32) char message[1024]; - error = dln_strerror(); + error = dln_strerror(message, sizeof(message)); #elif defined(USE_DLN_DLOPEN) const size_t errlen = strlen(error = dln_strerror()) + 1; error = memcpy(ALLOCA_N(char, errlen), error, errlen); @@ -501,7 +498,12 @@ void * dln_load(const char *file) { #if defined(_WIN32) || defined(USE_DLN_DLOPEN) - void *handle = dln_open(file); + char error[1024]; + void *handle = dln_open(file, error, sizeof(error)); + + if (handle == NULL) { + dln_loaderror("%s - %s", error, file); + } #ifdef RUBY_DLN_CHECK_ABI typedef unsigned long long abi_version_number; diff --git a/dln.h b/dln.h index d624bb6611d207..25cd946acfaad3 100644 --- a/dln.h +++ b/dln.h @@ -25,6 +25,7 @@ RUBY_SYMBOL_EXPORT_BEGIN char *dln_find_exe_r(const char*,const char*,char*,size_t DLN_FIND_EXTRA_ARG_DECL); char *dln_find_file_r(const char*,const char*,char*,size_t DLN_FIND_EXTRA_ARG_DECL); void *dln_load(const char*); +void *dln_open(const char *file, char *error, size_t size); void *dln_symbol(void*,const char*); RUBY_SYMBOL_EXPORT_END 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/dmydln.c b/dmydln.c index 35824ebec8b832..84e8a2b381f244 100644 --- a/dmydln.c +++ b/dmydln.c @@ -20,3 +20,12 @@ dln_symbol(void *handle, const char *symbol) UNREACHABLE_RETURN(NULL); } + +void* +dln_open(const char *library, char *error, size_t size) +{ + static const char *error_str = "this executable file can't load extension libraries"; + strlcpy(error, error_str, size); + return NULL; +} + diff --git a/doc/.document b/doc/.document index 3a790727d7be92..8174394f48c0cd 100644 --- a/doc/.document +++ b/doc/.document @@ -9,4 +9,4 @@ rdoc regexp rjit yjit -command_line +ruby 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/command_line/field_processing.md b/doc/command_line/field_processing.md deleted file mode 100644 index 4b5a46077805f2..00000000000000 --- a/doc/command_line/field_processing.md +++ /dev/null @@ -1,132 +0,0 @@ -## Field Processing - -Ruby supports field processing. - -This means that when certain command-line options are given, -the invoked Ruby program can process input line-by-line. - -### About the Examples - -Examples here assume that file `desiderata.txt` exists: - -``` -$ cat desiderata.txt -Go placidly amid the noise and the haste, -and remember what peace there may be in silence. -As far as possible, without surrender, -be on good terms with all persons. -``` - -The examples also use command-line option `-e`, -which passes the Ruby code to be executed on the command line itself: - -```sh -$ ruby -e 'puts "Hello, World."' -``` - -### Option `-n` - -Option `-n` runs your program in a Kernel#gets loop: - -``` -while gets - # Your Ruby code. -end -``` - -Note that `gets` reads the next line and sets global variable `$_` -to the last read line: - -```sh -$ ruby -n -e 'puts $_' desiderata.txt -Go placidly amid the noise and the haste, -and remember what peace there may be in silence. -As far as possible, without surrender, -be on good terms with all persons. -``` - -### Option `-p` - -Option `-p` is like option `-n`, but also prints each line: - -```sh -$ ruby -p -e 'puts $_.size' desiderata.txt -42 -Go placidly amid the noise and the haste, -49 -and remember what peace there may be in silence. -39 -As far as possible, without surrender, -35 -be on good terms with all persons. -``` - -### Option `-a` - -Option `-a`, when given with either of options `-n` or `-p`, -splits the string at `$_` into an array of strings at `$F`: - -```sh -$ ruby -an -e 'p $F' desiderata.txt -["Go", "placidly", "amid", "the", "noise", "and", "the", "haste,"] -["and", "remember", "what", "peace", "there", "may", "be", "in", "silence."] -["As", "far", "as", "possible,", "without", "surrender,"] -["be", "on", "good", "terms", "with", "all", "persons."] -``` - -For the splitting, -the default record separator is `$/`, -and the default field separator is `$;`. - -### Option `-F` - -Option `-F`, when given with option `-a`, -specifies that its argument is to be the input field separator to be used for splitting: - -```sh -$ ruby -an -Fs -e 'p $F' desiderata.txt -["Go placidly amid the noi", "e and the ha", "te,\n"] -["and remember what peace there may be in ", "ilence.\n"] -["A", " far a", " po", "", "ible, without ", "urrender,\n"] -["be on good term", " with all per", "on", ".\n"] -``` - -The argument may be a regular expression: - -``` -$ ruby -an -F'[.,]\s*' -e 'p $F' desiderata.txt -["Go placidly amid the noise and the haste"] -["and remember what peace there may be in silence"] -["As far as possible", "without surrender"] -["be on good terms with all persons"] -``` - -### Option `-l` - -Option `-l`, when given with option `-n` or `-p`, -modifies line-ending processing by: - -- Setting global variable output record separator `$\` - input record separator `$/`; - this affects line-oriented output (such a that from Kernel#puts). -- Calling String#chop! on each line read. - -Without option `-l` (unchopped): - -```sh -$ ruby -n -e 'p $_' desiderata.txt -"Go placidly amid the noise and the haste,\n" -"and remember what peace there may be in silence.\n" -"As far as possible, without surrender,\n" -"be on good terms with all persons.\n" -``` - -With option `-l' (chopped): - -```sh -$ ruby -ln -e 'p $_' desiderata.txt -"Go placidly amid the noise and the haste," -"and remember what peace there may be in silence." -"As far as possible, without surrender," -"be on good terms with all persons." -``` diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index 18464458896b74..96cee40cb4b951 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -89,18 +89,25 @@ ../configure --prefix="${HOME}/.rubies/ruby-master" ``` - - If you are frequently building Ruby, add the `--disable-install-doc` flag to not build documentation which will speed up the build process. - - Also `-C` (or `--config-cache`) would reduce time to configure from the next time. 5. Build Ruby: ``` shell - make install + make ``` 6. [Run tests](testing_ruby.md) to confirm your build succeeded. +7. Install Ruby: + + ``` shell + make install + ``` + + - If you need to run `make install` with `sudo` and want to avoid document generation with different permissions, you can use + `make SUDO=sudo install`. + ### Unexplainable Build Errors If you are having unexplainable build errors, after saving all your work, try running `git clean -xfd` in the source root to remove all git ignored local files. If you are working from a source directory that's been updated several times, you may have temporary build artifacts from previous releases which can cause build failures. @@ -181,12 +188,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/extension.ja.rdoc b/doc/extension.ja.rdoc index 48699ac620e399..2f7856f3d439df 100644 --- a/doc/extension.ja.rdoc +++ b/doc/extension.ja.rdoc @@ -784,6 +784,11 @@ RUBY_TYPED_WB_PROTECTED :: GC}[rdoc-ref:@Appendix+D.+-E4-B8-96-E4-BB-A3-E5-88-A5GC] も参照してください. +このマクロは例外を発生させる可能性があることに注意してくださ +い. ラップされる sval が,解放する必要があるリソース (割り +当てられたメモリ,外部ライブラリからのハンドルなど) を保持し +ている場合は,rb_protect を使用する必要があります. + Cの構造体の割当と対応するオブジェクトの生成を同時に行うマク ロとして以下のものが提供されています. diff --git a/doc/extension.rdoc b/doc/extension.rdoc index 01ac140e6971f2..ba59d107ab4c84 100644 --- a/doc/extension.rdoc +++ b/doc/extension.rdoc @@ -759,7 +759,12 @@ RUBY_TYPED_FROZEN_SHAREABLE :: If this flag is not set, the object can not become a shareable object by Ractor.make_shareable() method. -You can allocate and wrap the structure in one step. +Note that this macro can raise an exception. If sval to be wrapped +holds a resource needs to be released (e.g., allocated memory, handle +from an external library, and etc), you will have to use rb_protect. + +You can allocate and wrap the structure in one step, in more +preferable manner. TypedData_Make_Struct(klass, type, data_type, sval) @@ -768,6 +773,10 @@ the structure, which is also allocated. This macro works like: (sval = ZALLOC(type), TypedData_Wrap_Struct(klass, data_type, sval)) +However, you should use this macro instead of "allocation then wrap" +like the above code if it is simply allocated, because the latter can +raise a NoMemoryError and sval will be memory leaked in that case. + Arguments klass and data_type work like their counterparts in TypedData_Wrap_Struct(). A pointer to the allocated structure will be assigned to sval, which should be a pointer of the type specified. 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/rdoc/markup_reference.rb b/doc/rdoc/markup_reference.rb index 17da68bb1d01ca..bfc84abd5a8cc1 100644 --- a/doc/rdoc/markup_reference.rb +++ b/doc/rdoc/markup_reference.rb @@ -641,9 +641,9 @@ # The file content is shifted to have the same indentation as the colon # at the start of the directive. # -# The file is searched for in the directories -# given with the --include command-line option, -# or by default in the current directory. +# The file is searched for in the directory containing the current file, +# and then in each of the directories given with the --include +# command-line option. # # For C code, the directive may appear in a stand-alone comment # diff --git a/doc/ruby/option_dump.md b/doc/ruby/option_dump.md new file mode 100644 index 00000000000000..00d0ec77d56af3 --- /dev/null +++ b/doc/ruby/option_dump.md @@ -0,0 +1,297 @@ +# Option `--dump` + +For other argument values, +see {Option --dump}[options_md.html#label--dump-3A+Dump+Items]. + +For the examples here, we use this program: + +```sh +$ cat t.rb +puts 'Foo' +``` + +The supported dump items: + +- `insns`: Instruction sequences: + + ```sh + $ ruby --dump=insns t.rb + == disasm: #@t.rb:1 (1,0)-(1,10)> (catch: FALSE) + 0000 putself ( 1)[Li] + 0001 putstring "Foo" + 0003 opt_send_without_block + 0005 leave + ``` + +- `parsetree`: {Abstract syntax tree}[https://en.wikipedia.org/wiki/Abstract_syntax_tree] + (AST): + + ```sh + $ ruby --dump=parsetree t.rb + ########################################################### + ## Do NOT use this node dump for any purpose other than ## + ## debug and research. Compatibility is not guaranteed. ## + ########################################################### + + # @ NODE_SCOPE (line: 1, location: (1,0)-(1,10)) + # +- nd_tbl: (empty) + # +- nd_args: + # | (null node) + # +- nd_body: + # @ NODE_FCALL (line: 1, location: (1,0)-(1,10))* + # +- nd_mid: :puts + # +- nd_args: + # @ NODE_LIST (line: 1, location: (1,5)-(1,10)) + # +- nd_alen: 1 + # +- nd_head: + # | @ NODE_STR (line: 1, location: (1,5)-(1,10)) + # | +- nd_lit: "Foo" + # +- nd_next: + # (null node) + ``` + +- `parsetree_with_comment`: AST with comments: + + ```sh + $ ruby --dump=parsetree_with_comment t.rb + ########################################################### + ## Do NOT use this node dump for any purpose other than ## + ## debug and research. Compatibility is not guaranteed. ## + ########################################################### + + # @ NODE_SCOPE (line: 1, location: (1,0)-(1,10)) + # | # new scope + # | # format: [nd_tbl]: local table, [nd_args]: arguments, [nd_body]: body + # +- nd_tbl (local table): (empty) + # +- nd_args (arguments): + # | (null node) + # +- nd_body (body): + # @ NODE_FCALL (line: 1, location: (1,0)-(1,10))* + # | # function call + # | # format: [nd_mid]([nd_args]) + # | # example: foo(1) + # +- nd_mid (method id): :puts + # +- nd_args (arguments): + # @ NODE_LIST (line: 1, location: (1,5)-(1,10)) + # | # list constructor + # | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen]) + # | # example: [1, 2, 3] + # +- nd_alen (length): 1 + # +- nd_head (element): + # | @ NODE_STR (line: 1, location: (1,5)-(1,10)) + # | | # string literal + # | | # format: [nd_lit] + # | | # example: 'foo' + # | +- nd_lit (literal): "Foo" + # +- nd_next (next element): + # (null node) + ``` + +- `yydebug`: Debugging information from yacc parser generator: + + ```sh + $ ruby --dump=yydebug t.rb + Starting parse + Entering state 0 + Reducing stack by rule 1 (line 1295): + lex_state: NONE -> BEG at line 1296 + vtable_alloc:12392: 0x0000558453df1a00 + vtable_alloc:12393: 0x0000558453df1a60 + cmdarg_stack(push): 0 at line 12406 + cond_stack(push): 0 at line 12407 + -> $$ = nterm $@1 (1.0-1.0: ) + Stack now 0 + Entering state 2 + Reading a token: + lex_state: BEG -> CMDARG at line 9049 + Next token is token "local variable or method" (1.0-1.4: puts) + Shifting token "local variable or method" (1.0-1.4: puts) + Entering state 35 + Reading a token: Next token is token "string literal" (1.5-1.6: ) + Reducing stack by rule 742 (line 5567): + $1 = token "local variable or method" (1.0-1.4: puts) + -> $$ = nterm operation (1.0-1.4: ) + Stack now 0 2 + Entering state 126 + Reducing stack by rule 78 (line 1794): + $1 = nterm operation (1.0-1.4: ) + -> $$ = nterm fcall (1.0-1.4: ) + Stack now 0 2 + Entering state 80 + Next token is token "string literal" (1.5-1.6: ) + Reducing stack by rule 292 (line 2723): + cmdarg_stack(push): 1 at line 2737 + -> $$ = nterm $@16 (1.4-1.4: ) + Stack now 0 2 80 + Entering state 235 + Next token is token "string literal" (1.5-1.6: ) + Shifting token "string literal" (1.5-1.6: ) + Entering state 216 + Reducing stack by rule 607 (line 4706): + -> $$ = nterm string_contents (1.6-1.6: ) + Stack now 0 2 80 235 216 + Entering state 437 + Reading a token: Next token is token "literal content" (1.6-1.9: "Foo") + Shifting token "literal content" (1.6-1.9: "Foo") + Entering state 503 + Reducing stack by rule 613 (line 4802): + $1 = token "literal content" (1.6-1.9: "Foo") + -> $$ = nterm string_content (1.6-1.9: ) + Stack now 0 2 80 235 216 437 + Entering state 507 + Reducing stack by rule 608 (line 4716): + $1 = nterm string_contents (1.6-1.6: ) + $2 = nterm string_content (1.6-1.9: ) + -> $$ = nterm string_contents (1.6-1.9: ) + Stack now 0 2 80 235 216 + Entering state 437 + Reading a token: + lex_state: CMDARG -> END at line 7276 + Next token is token "terminator" (1.9-1.10: ) + Shifting token "terminator" (1.9-1.10: ) + Entering state 508 + Reducing stack by rule 590 (line 4569): + $1 = token "string literal" (1.5-1.6: ) + $2 = nterm string_contents (1.6-1.9: ) + $3 = token "terminator" (1.9-1.10: ) + -> $$ = nterm string1 (1.5-1.10: ) + Stack now 0 2 80 235 + Entering state 109 + Reducing stack by rule 588 (line 4559): + $1 = nterm string1 (1.5-1.10: ) + -> $$ = nterm string (1.5-1.10: ) + Stack now 0 2 80 235 + Entering state 108 + Reading a token: + lex_state: END -> BEG at line 9200 + Next token is token '\n' (1.10-1.10: ) + Reducing stack by rule 586 (line 4541): + $1 = nterm string (1.5-1.10: ) + -> $$ = nterm strings (1.5-1.10: ) + Stack now 0 2 80 235 + Entering state 107 + Reducing stack by rule 307 (line 2837): + $1 = nterm strings (1.5-1.10: ) + -> $$ = nterm primary (1.5-1.10: ) + Stack now 0 2 80 235 + Entering state 90 + Next token is token '\n' (1.10-1.10: ) + Reducing stack by rule 261 (line 2553): + $1 = nterm primary (1.5-1.10: ) + -> $$ = nterm arg (1.5-1.10: ) + Stack now 0 2 80 235 + Entering state 220 + Next token is token '\n' (1.10-1.10: ) + Reducing stack by rule 270 (line 2586): + $1 = nterm arg (1.5-1.10: ) + -> $$ = nterm arg_value (1.5-1.10: ) + Stack now 0 2 80 235 + Entering state 221 + Next token is token '\n' (1.10-1.10: ) + Reducing stack by rule 297 (line 2779): + $1 = nterm arg_value (1.5-1.10: ) + -> $$ = nterm args (1.5-1.10: ) + Stack now 0 2 80 235 + Entering state 224 + Next token is token '\n' (1.10-1.10: ) + Reducing stack by rule 772 (line 5626): + -> $$ = nterm none (1.10-1.10: ) + Stack now 0 2 80 235 224 + Entering state 442 + Reducing stack by rule 296 (line 2773): + $1 = nterm none (1.10-1.10: ) + + -> $$ = nterm opt_block_arg (1.10-1.10: ) + Stack now 0 2 80 235 224 + Entering state 441 + Reducing stack by rule 288 (line 2696): + $1 = nterm args (1.5-1.10: ) + $2 = nterm opt_block_arg (1.10-1.10: ) + -> $$ = nterm call_args (1.5-1.10: ) + Stack now 0 2 80 235 + Entering state 453 + Reducing stack by rule 293 (line 2723): + $1 = nterm $@16 (1.4-1.4: ) + $2 = nterm call_args (1.5-1.10: ) + cmdarg_stack(pop): 0 at line 2754 + -> $$ = nterm command_args (1.4-1.10: ) + Stack now 0 2 80 + Entering state 333 + Next token is token '\n' (1.10-1.10: ) + Reducing stack by rule 79 (line 1804): + $1 = nterm fcall (1.0-1.4: ) + $2 = nterm command_args (1.4-1.10: ) + -> $$ = nterm command (1.0-1.10: ) + Stack now 0 2 + Entering state 81 + Next token is token '\n' (1.10-1.10: ) + Reducing stack by rule 73 (line 1770): + $1 = nterm command (1.0-1.10: ) + -> $$ = nterm command_call (1.0-1.10: ) + Stack now 0 2 + Entering state 78 + Reducing stack by rule 51 (line 1659): + $1 = nterm command_call (1.0-1.10: ) + -> $$ = nterm expr (1.0-1.10: ) + Stack now 0 2 + Entering state 75 + Next token is token '\n' (1.10-1.10: ) + Reducing stack by rule 39 (line 1578): + $1 = nterm expr (1.0-1.10: ) + -> $$ = nterm stmt (1.0-1.10: ) + Stack now 0 2 + Entering state 73 + Next token is token '\n' (1.10-1.10: ) + Reducing stack by rule 8 (line 1354): + $1 = nterm stmt (1.0-1.10: ) + -> $$ = nterm top_stmt (1.0-1.10: ) + Stack now 0 2 + Entering state 72 + Reducing stack by rule 5 (line 1334): + $1 = nterm top_stmt (1.0-1.10: ) + -> $$ = nterm top_stmts (1.0-1.10: ) + Stack now 0 2 + Entering state 71 + Next token is token '\n' (1.10-1.10: ) + Shifting token '\n' (1.10-1.10: ) + Entering state 311 + Reducing stack by rule 769 (line 5618): + $1 = token '\n' (1.10-1.10: ) + -> $$ = nterm term (1.10-1.10: ) + Stack now 0 2 71 + Entering state 313 + Reducing stack by rule 770 (line 5621): + $1 = nterm term (1.10-1.10: ) + -> $$ = nterm terms (1.10-1.10: ) + Stack now 0 2 71 + Entering state 314 + Reading a token: Now at end of input. + Reducing stack by rule 759 (line 5596): + $1 = nterm terms (1.10-1.10: ) + -> $$ = nterm opt_terms (1.10-1.10: ) + Stack now 0 2 71 + Entering state 312 + Reducing stack by rule 3 (line 1321): + $1 = nterm top_stmts (1.0-1.10: ) + $2 = nterm opt_terms (1.10-1.10: ) + -> $$ = nterm top_compstmt (1.0-1.10: ) + Stack now 0 2 + Entering state 70 + Reducing stack by rule 2 (line 1295): + $1 = nterm $@1 (1.0-1.0: ) + $2 = nterm top_compstmt (1.0-1.10: ) + vtable_free:12426: p->lvtbl->args(0x0000558453df1a00) + vtable_free:12427: p->lvtbl->vars(0x0000558453df1a60) + cmdarg_stack(pop): 0 at line 12428 + cond_stack(pop): 0 at line 12429 + -> $$ = nterm program (1.0-1.10: ) + Stack now 0 + Entering state 1 + Now at end of input. + Shifting token "end-of-input" (1.10-1.10: ) + Entering state 3 + Stack now 0 1 3 + Cleanup: popping token "end-of-input" (1.10-1.10: ) + Cleanup: popping nterm program (1.0-1.10: ) + ``` + diff --git a/doc/ruby/options.md b/doc/ruby/options.md new file mode 100644 index 00000000000000..143c8925f12fde --- /dev/null +++ b/doc/ruby/options.md @@ -0,0 +1,723 @@ +# Ruby Command-Line Options + +## About the Examples + +Some examples here use command-line option `-e`, +which passes the Ruby code to be executed on the command line itself: + +```sh +$ ruby -e 'puts "Hello, World."' +``` + +Some examples here assume that file `desiderata.txt` exists: + +``` +$ cat desiderata.txt +Go placidly amid the noise and the haste, +and remember what peace there may be in silence. +As far as possible, without surrender, +be on good terms with all persons. +``` + +## Options + +### `-0`: \Set `$/` (Input Record Separator) + +Option `-0` defines the input record separator `$/` +for the invoked Ruby program. + +The optional argument to the option must be octal digits, +each in the range `0..7`; +these digits are prefixed with digit `0` to form an octal value. + +If no argument is given, the input record separator is `0x00`. + +If an argument is given, it must immediately follow the option +(no intervening whitespace or equal-sign character `'='`); +argument values: + +- `0`: the input record separator is `''`; + see {Special Line Separator Values}[rdoc-ref:IO@Special+Line+Separator+Values]. +- In range `(1..0377)`: + the input record separator `$/` is set to the character value of the argument. +- Any other octal value: the input record separator is `nil`. + +Examples: + +```sh +$ ruby -0 -e 'p $/' +"\x00" +ruby -00 -e 'p $/' +"" +$ ruby -012 -e 'p $/' +"\n" +$ ruby -015 -e 'p $/' +"\r" +$ ruby -0377 -e 'p $/' +"\xFF" +$ ruby -0400 -e 'p $/' +nil +``` + +See also: + +- {Option -a}[rdoc-ref:ruby/options.md@a-3A+Split+Input+Lines+into+Fields]: + Split input lines into fields. +- {Option -F}[rdoc-ref:ruby/options.md@F-3A+Set+Input+Field+Separator]: + \Set input field separator. +- {Option -l}[rdoc-ref:ruby/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: + \Set output record separator; chop lines. +- {Option -n}[rdoc-ref:ruby/options.md@n-3A+Run+Program+in+gets+Loop]: + Run program in `gets` loop. +- {Option -p}[rdoc-ref:ruby/options.md@p-3A+-n-2C+with+Printing]: + `-n`, with printing. + +### `-a`: Split Input Lines into Fields + +Option `-a`, when given with either of options `-n` or `-p`, +splits the string at `$_` into an array of strings at `$F`: + +```sh +$ ruby -an -e 'p $F' desiderata.txt +["Go", "placidly", "amid", "the", "noise", "and", "the", "haste,"] +["and", "remember", "what", "peace", "there", "may", "be", "in", "silence."] +["As", "far", "as", "possible,", "without", "surrender,"] +["be", "on", "good", "terms", "with", "all", "persons."] +``` + +For the splitting, +the default record separator is `$/`, +and the default field separator is `$;`. + +See also: + +- {Option -0}[rdoc-ref:ruby/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: + \Set `$/` (input record separator). +- {Option -F}[rdoc-ref:ruby/options.md@F-3A+Set+Input+Field+Separator]: + \Set input field separator. +- {Option -l}[rdoc-ref:ruby/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: + \Set output record separator; chop lines. +- {Option -n}[rdoc-ref:ruby/options.md@n-3A+Run+Program+in+gets+Loop]: + Run program in `gets` loop. +- {Option -p}[rdoc-ref:ruby/options.md@p-3A+-n-2C+with+Printing]: + `-n`, with printing. + +### `-c`: Check Syntax + +Option `-c` specifies that the specified Ruby program +should be checked for syntax, but not actually executed: + +``` +$ ruby -e 'puts "Foo"' +Foo +$ ruby -c -e 'puts "Foo"' +Syntax OK +``` + +### `-C`: \Set Working Directory + +The argument to option `-C` specifies a working directory +for the invoked Ruby program; +does not change the working directory for the current process: + +```sh +$ basename `pwd` +ruby +$ ruby -C lib -e 'puts File.basename(Dir.pwd)' +lib +$ basename `pwd` +ruby +``` + +Whitespace between the option and its argument may be omitted. + +### `-d`: \Set `$DEBUG` to `true` + +Some code in (or called by) the Ruby program may include statements or blocks +conditioned by the global variable `$DEBUG` (e.g., `if $DEBUG`); +these commonly write to `$stdout` or `$stderr`. + +The default value for `$DEBUG` is `false`; +option `-d` sets it to `true`: + +```sh +$ ruby -e 'p $DEBUG' +false +$ ruby -d -e 'p $DEBUG' +true +``` + +Option `--debug` is an alias for option `-d`. + +### `-e`: Execute Given Ruby Code + +Option `-e` requires an argument, which is Ruby code to be executed; +the option may be given more than once: + +``` +$ ruby -e 'puts "Foo"' -e 'puts "Bar"' +Foo +Bar +``` + +Whitespace between the option and its argument may be omitted. + +The command may include other options, +but should not include arguments (which, if given, are ignored). + +### `-E`: \Set Default Encodings + +Option `-E` requires an argument, which specifies either the default external encoding, +or both the default external and internal encodings for the invoked Ruby program: + +``` +# No option -E. +$ ruby -e 'p [Encoding::default_external, Encoding::default_internal]' +[#, nil] +# Option -E with default external encoding. +$ ruby -E cesu-8 -e 'p [Encoding::default_external, Encoding::default_internal]' +[#, nil] +# Option -E with default external and internal encodings. +$ ruby -E utf-8:cesu-8 -e 'p [Encoding::default_external, Encoding::default_internal]' +[#, #] +``` + +Whitespace between the option and its argument may be omitted. + +See also: + +- {Option --external-encoding}[options_md.html#label--external-encoding-3A+Set+Default+External+Encoding]: + \Set default external encoding. +- {Option --internal-encoding}[options_md.html#label--internal-encoding-3A+Set+Default+Internal+Encoding]: + \Set default internal encoding. + +Option `--encoding` is an alias for option `-E`. + +### `-F`: \Set Input Field Separator + +Option `-F`, when given with option `-a`, +specifies that its argument is to be the input field separator to be used for splitting: + +```sh +$ ruby -an -Fs -e 'p $F' desiderata.txt +["Go placidly amid the noi", "e and the ha", "te,\n"] +["and remember what peace there may be in ", "ilence.\n"] +["A", " far a", " po", "", "ible, without ", "urrender,\n"] +["be on good term", " with all per", "on", ".\n"] +``` + +The argument may be a regular expression: + +``` +$ ruby -an -F'[.,]\s*' -e 'p $F' desiderata.txt +["Go placidly amid the noise and the haste"] +["and remember what peace there may be in silence"] +["As far as possible", "without surrender"] +["be on good terms with all persons"] +``` + +The argument must immediately follow the option +(no intervening whitespace or equal-sign character `'='`). + +See also: + +- {Option -0}[rdoc-ref:ruby/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: + \Set `$/` (input record separator). +- {Option -a}[rdoc-ref:ruby/options.md@a-3A+Split+Input+Lines+into+Fields]: + Split input lines into fields. +- {Option -l}[rdoc-ref:ruby/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: + \Set output record separator; chop lines. +- {Option -n}[rdoc-ref:ruby/options.md@n-3A+Run+Program+in+gets+Loop]: + Run program in `gets` loop. +- {Option -p}[rdoc-ref:ruby/options.md@p-3A+-n-2C+with+Printing]: + `-n`, with printing. + +### `-h`: Print Short Help Message + +Option `-h` prints a short help message +that includes single-hyphen options (e.g. `-I`), +and largely omits double-hyphen options (e.g., `--version`). + +Arguments and additional options are ignored. + +For a longer help message, use option `--help`. + +### `-i`: \Set \ARGF In-Place Mode + +Option `-i` sets the \ARGF in-place mode for the invoked Ruby program; +see ARGF#inplace_mode=: + +``` +$ ruby -e 'p ARGF.inplace_mode' +nil +$ ruby -i -e 'p ARGF.inplace_mode' +"" +$ ruby -i.bak -e 'p ARGF.inplace_mode' +".bak" +``` + +### `-I`: Add to `$LOAD_PATH` + +The argument to option `-I` specifies a directory +to be added to the array in global variable `$LOAD_PATH`; +the option may be given more than once: + +```sh +$ pushd /tmp +$ ruby -e 'p $LOAD_PATH.size' +8 +$ ruby -I my_lib -I some_lib -e 'p $LOAD_PATH.size' +10 +$ ruby -I my_lib -I some_lib -e 'p $LOAD_PATH.take(2)' +["/tmp/my_lib", "/tmp/some_lib"] +$ popd +``` + +Whitespace between the option and its argument may be omitted. + +### `-l`: \Set Output Record Separator; Chop Lines + +Option `-l`, when given with option `-n` or `-p`, +modifies line-ending processing by: + +- Setting global variable output record separator `$\` + to the current value of input record separator `$/`; + this affects line-oriented output (such a the output from Kernel#puts). +- Calling String#chop! on each line read. + +Without option `-l` (unchopped): + +```sh +$ ruby -n -e 'p $_' desiderata.txt +"Go placidly amid the noise and the haste,\n" +"and remember what peace there may be in silence.\n" +"As far as possible, without surrender,\n" +"be on good terms with all persons.\n" +``` + +With option `-l' (chopped): + +```sh +$ ruby -ln -e 'p $_' desiderata.txt +"Go placidly amid the noise and the haste," +"and remember what peace there may be in silence." +"As far as possible, without surrender," +"be on good terms with all persons." +``` + +See also: + +- {Option -0}[rdoc-ref:ruby/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: + \Set `$/` (input record separator). +- {Option -a}[rdoc-ref:ruby/options.md@a-3A+Split+Input+Lines+into+Fields]: + Split input lines into fields. +- {Option -F}[rdoc-ref:ruby/options.md@F-3A+Set+Input+Field+Separator]: + \Set input field separator. +- {Option -n}[rdoc-ref:ruby/options.md@n-3A+Run+Program+in+gets+Loop]: + Run program in `gets` loop. +- {Option -p}[rdoc-ref:ruby/options.md@p-3A+-n-2C+with+Printing]: + `-n`, with printing. + +### `-n`: Run Program in `gets` Loop + +Option `-n` runs your program in a Kernel#gets loop: + +``` +while gets + # Your Ruby code. +end +``` + +Note that `gets` reads the next line and sets global variable `$_` +to the last read line: + +```sh +$ ruby -n -e 'puts $_' desiderata.txt +Go placidly amid the noise and the haste, +and remember what peace there may be in silence. +As far as possible, without surrender, +be on good terms with all persons. +``` + +See also: + +- {Option -0}[rdoc-ref:ruby/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: + \Set `$/` (input record separator). +- {Option -a}[rdoc-ref:ruby/options.md@a-3A+Split+Input+Lines+into+Fields]: + Split input lines into fields. +- {Option -F}[rdoc-ref:ruby/options.md@F-3A+Set+Input+Field+Separator]: + \Set input field separator. +- {Option -l}[rdoc-ref:ruby/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: + \Set output record separator; chop lines. +- {Option -p}[rdoc-ref:ruby/options.md@p-3A+-n-2C+with+Printing]: + `-n`, with printing. + +### `-p`: `-n`, with Printing + +Option `-p` is like option `-n`, but also prints each line: + +```sh +$ ruby -p -e 'puts $_.size' desiderata.txt +42 +Go placidly amid the noise and the haste, +49 +and remember what peace there may be in silence. +39 +As far as possible, without surrender, +35 +be on good terms with all persons. +``` + +See also: + +- {Option -0}[rdoc-ref:ruby/options.md@0-3A+Set+-24-2F+-28Input+Record+Separator-29]: + \Set `$/` (input record separator). +- {Option -a}[rdoc-ref:ruby/options.md@a-3A+Split+Input+Lines+into+Fields]: + Split input lines into fields. +- {Option -F}[rdoc-ref:ruby/options.md@F-3A+Set+Input+Field+Separator]: + \Set input field separator. +- {Option -l}[rdoc-ref:ruby/options.md@l-3A+Set+Output+Record+Separator-3B+Chop+Lines]: + \Set output record separator; chop lines. +- {Option -n}[rdoc-ref:ruby/options.md@n-3A+Run+Program+in+gets+Loop]: + Run program in `gets` loop. + +### `-r`: Require Library + +The argument to option `-r` specifies a library to be required +before executing the Ruby program; +the option may be given more than once: + +```sh +$ ruby -e 'p defined?(JSON); p defined?(CSV)' +nil +nil +$ ruby -r CSV -r JSON -e 'p defined?(JSON); p defined?(CSV)' +"constant" +"constant" +``` + +Whitespace between the option and its argument may be omitted. + +### `-s`: Define Global Variable + +Option `-s` specifies that a "custom option" is to define a global variable +in the invoked Ruby program: + +- The custom option must appear _after_ the program name. +- The custom option must begin with single hyphen (e.g., `-foo`), + not two hyphens (e.g., `--foo`). +- The name of the global variable is based on the option name: + global variable `$foo` for custom option`-foo`. +- The value of the global variable is the string option argument if given, + `true` otherwise. + +More than one custom option may be given: + +``` +$ cat t.rb +p [$foo, $bar] +$ ruby t.rb +[nil, nil] +$ ruby -s t.rb -foo=baz +["baz", nil] +$ ruby -s t.rb -foo +[true, nil] +$ ruby -s t.rb -foo=baz -bar=bat +["baz", "bat"] +``` + +The option may not be used with +{option -e}[rdoc-ref:ruby/options.md@e-3A+Execute+Given+Ruby+Code] + +### `-S`: Search Directories in `ENV['PATH']` + +Option `-S` specifies that the Ruby interpreter +is to search (if necessary) the directories whose paths are in the program's +`PATH` environment variable; +the program is executed in the shell's current working directory +(not necessarily in the directory where the program is found). + +This example uses adds path `'tmp/'` to the `PATH` environment variable: + +```sh +$ export PATH=/tmp:$PATH +$ echo "puts File.basename(Dir.pwd)" > /tmp/t.rb +$ ruby -S t.rb +ruby +``` + +### `-v`: Print Version; \Set `$VERBOSE` + +Options `-v` prints the Ruby version and sets global variable `$VERBOSE`: + +``` +$ ruby -e 'p $VERBOSE' +false +$ ruby -v -e 'p $VERBOSE' +ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x64-mingw-ucrt] +true +``` + +### `-w`: Synonym for `-W1` + +Option `-w` (lowercase letter) is equivalent to option `-W1` (uppercase letter). + +### `-W`: \Set \Warning Policy + +Any Ruby code can create a warning message by calling method Kernel#warn; +methods in the Ruby core and standard libraries can also create warning messages. +Such a message may be printed on `$stderr` +(or not, depending on certain settings). + +Option `-W` helps determine whether a particular warning message +will be written, +by setting the initial value of global variable `$-W`: + +- `-W0`: Sets `$-W` to `0` (silent; no warnings). +- `-W1`: Sets `$-W` to `1` (moderate verbosity). +- `-W2`: Sets `$-W` to `2` (high verbosity). +- `-W`: Same as `-W2` (high verbosity). +- Option not given: Same as `-W1` (moderate verbosity). + +The value of `$-W`, in turn, determines which warning messages (if any) +are to be printed to `$stdout` (see Kernel#warn): + +```sh +$ ruby -W1 -e 'p $foo' +nil +$ ruby -W2 -e 'p $foo' +-e:1: warning: global variable '$foo' not initialized +nil +``` + +Ruby code may also define warnings for certain categories; +these are the default settings for the defined categories: + +``` +Warning[:experimental] # => true +Warning[:deprecated] # => false +Warning[:performance] # => false +``` + +They may also be set: +``` +Warning[:experimental] = false +Warning[:deprecated] = true +Warning[:performance] = true +``` + +You can suppress a category by prefixing `no-` to the category name: + +``` +$ ruby -W:no-experimental -e 'p IO::Buffer.new' +# +``` + +### `-x`: Execute Ruby Code Found in Text + +Option `-x` executes a Ruby program whose code is embedded +in other, non-code, text: + +The ruby code: + +- Begins after the first line beginning with `'#!` and containing string `'ruby'`. +- Ends before any one of: + + - End-of-file. + - A line consisting of `'__END__'`, + - Character `Ctrl-D` or `Ctrl-Z`. + +Example: + +```sh +$ cat t.txt +Leading garbage. +#!ruby +puts File.basename(Dir.pwd) +__END__ +Trailing garbage. + +$ ruby -x t.txt +ruby +``` + +The optional argument specifies the directory where the text file +is to be found; +the Ruby code is executed in that directory: + +```sh +$ cp t.txt /tmp/ +$ ruby -x/tmp t.txt +tmp +$ + +``` + +If an argument is given, it must immediately follow the option +(no intervening whitespace or equal-sign character `'='`). + +### `--backtrace-limit`: \Set Backtrace Limit + +Option `--backtrace-limit` sets a limit on the number of entries +to be displayed in a backtrace. + +See Thread::Backtrace.limit. + +### `--copyright`: Print Ruby Copyright + +Option `--copyright` prints a copyright message: + +```sh +$ ruby --copyright +ruby - Copyright (C) 1993-2024 Yukihiro Matsumoto +``` + +### `--debug`: Alias for `-d` + +Option `--debug` is an alias for +{option -d}[rdoc-ref:ruby/options.md@d-3A+Set+-24DEBUG+to+true]. + +### `--disable`: Disable Features + +Option `--disable` specifies features to be disabled; +the argument is a comma-separated list of the features to be disabled: + +```sh +ruby --disable=gems,rubyopt t.rb +``` + +The supported features: + +- `gems`: Rubygems (default: enabled). +- `did_you_mean`: [`did_you_mean`](https://github.com/ruby/did_you_mean) (default: enabled). +- `rubyopt`: `RUBYOPT` environment variable (default: enabled). +- `frozen-string-literal`: Freeze all string literals (default: disabled). +- `jit`: JIT compiler (default: disabled). + +See also {option --enable}[options_md.html#label--enable-3A+Enable+Features]. + +### `--dump`: Dump Items + +Option `--dump` specifies items to be dumped; +the argument is a comma-separated list of the items. + +Some of the argument values cause the command to behave as if a different +option was given: + +- `--dump=copyright`: + Same as {option \-\-copyright}[options_md.html#label--copyright-3A+Print+Ruby+Copyright]. +- `--dump=help`: + Same as {option \-\-help}[options_md.html#label--help-3A+Print+Help+Message]. +- `--dump=syntax`: + Same as {option -c}[rdoc-ref:ruby/options.md@c-3A+Check+Syntax]. +- `--dump=usage`: + Same as {option -h}[rdoc-ref:ruby/options.md@h-3A+Print+Short+Help+Message]. +- `--dump=version`: + Same as {option \-\-version}[options_md.html#label--version-3A+Print+Ruby+Version]. + +For other argument values and examples, +see {Option --dump}[option_dump_md.html]. + +### `--enable`: Enable Features + +Option `--enable` specifies features to be enabled; +the argument is a comma-separated list of the features to be enabled. + +```sh +ruby --enable=gems,rubyopt t.rb +``` + +For the features, +see {option --disable}[options_md.html#label--disable-3A+Disable+Features]. + +### `--encoding`: Alias for `-E`. + +Option `--encoding` is an alias for +{option -E}[rdoc-ref:ruby/options.md@E-3A+Set+Default+Encodings]. + +### `--external-encoding`: \Set Default External \Encoding + +Option `--external-encoding` +sets the default external encoding for the invoked Ruby program; +for values of +encoding+, +see {Encoding: Names and Aliases}[rdoc-ref:encodings.rdoc@Names+and+Aliases]. + +```sh +$ ruby -e 'puts Encoding::default_external' +UTF-8 +$ ruby --external-encoding=cesu-8 -e 'puts Encoding::default_external' +CESU-8 +``` + +### `--help`: Print Help Message + +Option `--help` prints a long help message. + +Arguments and additional options are ignored. + +For a shorter help message, use option `-h`. + +### `--internal-encoding`: \Set Default Internal \Encoding + +Option `--internal-encoding` +sets the default internal encoding for the invoked Ruby program; +for values of +encoding+, +see {Encoding: Names and Aliases}[rdoc-ref:encodings.rdoc@Names+and+Aliases]. + +```sh +$ ruby -e 'puts Encoding::default_internal.nil?' +true +$ ruby --internal-encoding=cesu-8 -e 'puts Encoding::default_internal' +CESU-8 +``` + +### `--verbose`: \Set `$VERBOSE` + +Option `--verbose` sets global variable `$VERBOSE` to `true` +and disables input from `$stdin`. + +### `--version`: Print Ruby Version + +Option `--version` prints the version of the Ruby interpreter, then exits. + +## Experimental Options + +These options are experimental in the current Ruby release, +and may be modified or withdrawn in later releases. + +### `--jit` + +Option `-jit` enables JIT compilation with the default option. + +#### `--jit-debug` + +Option `--jit-debug` enables JIT debugging (very slow); +adds compiler flags if given. + +#### `--jit-max-cache=num` + +Option `--jit-max-cache=num` sets the maximum number of methods +to be JIT-ed in a cache; default: 100). + +#### `--jit-min-calls=num` + +Option `jit-min-calls=num` sets the minimum number of calls to trigger JIT +(for testing); default: 10000). + +#### `--jit-save-temps` + +Option `--jit-save-temps` saves JIT temporary files in $TMP or /tmp (for testing). + +#### `--jit-verbose` + +Option `--jit-verbose` prints JIT logs of level `num` or less +to `$stderr`; default: 0. + +#### `--jit-wait` + +Option `--jit-wait` waits until JIT compilation finishes every time (for testing). + +#### `--jit-warnings` + +Option `--jit-warnings` enables printing of JIT warnings. + 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/Makefile.in b/enc/Makefile.in index 660069f6ea3a93..6920bc9520435a 100644 --- a/enc/Makefile.in +++ b/enc/Makefile.in @@ -72,6 +72,7 @@ WORKDIRS = @WORKDIRS@ NULLCMD = @NULLCMD@ RM = @RM@ +RMALL = @RMALL@ RMDIR = @RMDIR@ RMDIRS = @RMDIRS@ MAKEDIRS = @MAKEDIRS@ @@ -83,6 +84,9 @@ all: make-workdir: $(Q)$(MAKEDIRS) $(WORKDIRS) +.PHONY: encs all modencs libencs enc libenc trans libtrans srcs +.PHONY: clean distclean realclean clean-srcs + clean: distclean: clean diff --git a/enc/depend b/enc/depend index 12ddbc223a9afe..128203d5ecda48 100644 --- a/enc/depend +++ b/enc/depend @@ -35,6 +35,7 @@ ENCSOS =<%ENCS.map {|e|%> $(ENCSODIR)/<%=e%>.$(DLEXT) \ <%}%> # ENCCLEANLIBS = <%=cleanlibs.map {|clean| clean.gsub(/\$\*(\.\w+)?/) {"$(ENCOBJS#{$1 ? ":.#{CONFIG["OBJEXT"]}=#{$1}" : ""})"} + .gsub(/\$\(\KTARGET_SO(?=[:\)])/) {"ENCSOS"} }.join(" ")%> ENCCLEANOBJS = <%=cleanobjs.map {|clean| clean.gsub(/\$\*(\.\w+)?/) {"$(ENCOBJS#{$1 ? ":.#{CONFIG["OBJEXT"]}=#{$1}" : ""})"} @@ -51,6 +52,7 @@ TRANSSOS =<%TRANS.map {|e|%> $(ENCSODIR)/<%=e%>.$(DLEXT) \ <%}%> # TRANSCLEANLIBS = <%=cleanlibs.map {|clean| clean.gsub(/\$\*(\.\w+)?/) {"$(TRANSOBJS#{$1 ? ":.#{CONFIG["OBJEXT"]}=#{$1}" : ""})"} + .gsub(/\$\(\KTARGET_SO(?=[:\)])/) {"TRANSSOS"} }.join(" ")%> TRANSCLEANOBJS = <%=cleanobjs.map {|clean| clean.gsub(/\$\*(\.\w+)?/) {"$(TRANSOBJS#{$1 ? ":.#{CONFIG["OBJEXT"]}=#{$1}" : ""})"} @@ -151,7 +153,7 @@ enc/trans/transdb.$(OBJEXT): transdb.h clean: % %w[$(ENCSOS) $(LIBENC) $(ENCOBJS) $(ENCCLEANOBJS) $(ENCCLEANLIBS) $(TRANSSOS) $(LIBTRANS) $(TRANSOBJS) $(TRANSCLEANOBJS) $(TRANSCLEANLIBS) $(ENC_TRANS_D) $(ENC_TRANS_SO_D)].each do |clean| - $(Q)$(RM) <%=pathrep[clean]%> + $(Q)$(RMALL) <%=pathrep[clean]%> % end % unless inplace $(Q)$(RM) enc/unicode/*/casefold.h enc/unicode/*/name2ctype.h 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/encinit.c.erb b/enc/encinit.c.erb index 120408f8e331b9..3662ba200d5b14 100644 --- a/enc/encinit.c.erb +++ b/enc/encinit.c.erb @@ -1,3 +1,6 @@ +/* Automatically generated from <%= erb.filename %> + * Do not edit<%# directly%>. + */ /* Copyright 2012 Google Inc. Some Rights Reserved. * Author: yugui@google.com (Yugui Sonoda) */ diff --git a/enc/make_encmake.rb b/enc/make_encmake.rb index 09e19f65514499..96d1944bcba31c 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 @@ -147,10 +147,6 @@ def target_transcoders Dir.mkdir 'enc' rescue Errno::EEXIST end - File.open("enc/encinit.c", "w") {|f| - f.puts "/* Automatically generated from enc/encinit.c.erb" - f.puts " * Do not edit." - f.puts " */" - f.puts tmp - } + require 'tool/lib/output' + Output.new(path: "enc/encinit.c", ifchange: true).write(tmp) end diff --git a/encoding.c b/encoding.c index a0be931c975dff..480cc8bdc63604 100644 --- a/encoding.c +++ b/encoding.c @@ -1015,13 +1015,22 @@ rb_enc_get(VALUE obj) return rb_enc_from_index(rb_enc_get_index(obj)); } +const char * +rb_enc_inspect_name(rb_encoding *enc) +{ + if (enc == global_enc_ascii) { + return "BINARY (ASCII-8BIT)"; + } + return enc->name; +} + static rb_encoding* rb_encoding_check(rb_encoding* enc, VALUE str1, VALUE str2) { if (!enc) rb_raise(rb_eEncCompatError, "incompatible character encodings: %s and %s", - rb_enc_name(rb_enc_get(str1)), - rb_enc_name(rb_enc_get(str2))); + rb_enc_inspect_name(rb_enc_get(str1)), + rb_enc_inspect_name(rb_enc_get(str2))); return enc; } @@ -1263,9 +1272,10 @@ enc_inspect(VALUE self) if (!(enc = DATA_PTR(self)) || rb_enc_from_index(rb_enc_to_index(enc)) != enc) { rb_raise(rb_eTypeError, "broken Encoding"); } + return rb_enc_sprintf(rb_usascii_encoding(), "#<%"PRIsVALUE":%s%s%s>", rb_obj_class(self), - rb_enc_name(enc), + rb_enc_inspect_name(enc), (ENC_DUMMY_P(enc) ? " (dummy)" : ""), rb_enc_autoload_p(enc) ? " (autoload)" : ""); } @@ -1927,7 +1937,7 @@ Init_Encoding(void) list = rb_encoding_list = rb_ary_new2(ENCODING_LIST_CAPA); RBASIC_CLEAR_CLASS(list); - rb_gc_register_mark_object(list); + rb_vm_register_global_object(list); for (i = 0; i < enc_table->count; ++i) { rb_ary_push(list, enc_new(enc_table->list[i].enc)); diff --git a/enumerator.c b/enumerator.c index d2819e40498391..193a865dbc5536 100644 --- a/enumerator.c +++ b/enumerator.c @@ -3536,10 +3536,19 @@ static VALUE enum_product_total_size(VALUE enums) { VALUE total = INT2FIX(1); + VALUE sizes = rb_ary_hidden_new(RARRAY_LEN(enums)); long i; for (i = 0; i < RARRAY_LEN(enums); i++) { VALUE size = enum_size(RARRAY_AREF(enums, i)); + if (size == INT2FIX(0)) { + rb_ary_resize(sizes, 0); + return size; + } + rb_ary_push(sizes, size); + } + for (i = 0; i < RARRAY_LEN(sizes); i++) { + VALUE size = RARRAY_AREF(sizes, i); if (NIL_P(size) || (RB_TYPE_P(size, T_FLOAT) && isinf(NUM2DBL(size)))) { return size; @@ -4562,7 +4571,7 @@ InitVM_Enumerator(void) rb_hash_aset(lazy_use_super_method, sym("uniq"), sym("_enumerable_uniq")); rb_hash_aset(lazy_use_super_method, sym("with_index"), sym("_enumerable_with_index")); rb_obj_freeze(lazy_use_super_method); - rb_gc_register_mark_object(lazy_use_super_method); + rb_vm_register_global_object(lazy_use_super_method); #if 0 /* for RDoc */ rb_define_method(rb_cLazy, "to_a", lazy_to_a, 0); diff --git a/error.c b/error.c index ac579777bebc98..dc032df6512cef 100644 --- a/error.c +++ b/error.c @@ -32,6 +32,7 @@ #endif #include "internal.h" +#include "internal/class.h" #include "internal/error.h" #include "internal/eval.h" #include "internal/hash.h" @@ -251,6 +252,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 @@ -1818,7 +1839,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); @@ -1841,15 +1862,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 @@ -2388,7 +2417,7 @@ name_err_mesg_to_str(VALUE obj) VALUE klass; object: klass = CLASS_OF(obj); - if (RB_TYPE_P(klass, T_CLASS) && FL_TEST(klass, FL_SINGLETON)) { + if (RB_TYPE_P(klass, T_CLASS) && RCLASS_SINGLETON_P(klass)) { s = FAKE_CSTR(&s_str, ""); if (obj == rb_vm_top_self()) { c = FAKE_CSTR(&c_str, "main"); @@ -3412,6 +3441,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); @@ -3830,6 +3860,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 0d74de483a1461..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) @@ -428,7 +427,7 @@ rb_class_modify_check(VALUE klass) if (OBJ_FROZEN(klass)) { const char *desc; - if (FL_TEST(klass, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(klass)) { desc = "object"; klass = RCLASS_ATTACHED_OBJECT(klass); if (!SPECIAL_CONST_P(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-/load/resolve_symbol_target/resolve_symbol_target.h b/ext/-test-/load/resolve_symbol_target/resolve_symbol_target.h index 7d471bf3600fba..847dcb7dd336d7 100644 --- a/ext/-test-/load/resolve_symbol_target/resolve_symbol_target.h +++ b/ext/-test-/load/resolve_symbol_target/resolve_symbol_target.h @@ -1,4 +1,4 @@ #include #include "ruby/internal/dllexport.h" -RUBY_EXTERN VALUE rst_any_method(VALUE); +RUBY_FUNC_EXPORTED VALUE rst_any_method(VALUE); diff --git a/ext/-test-/load/stringify_target/stringify_target.h b/ext/-test-/load/stringify_target/stringify_target.h index 5081f8cbd66df1..d95fb65d7ca7e7 100644 --- a/ext/-test-/load/stringify_target/stringify_target.h +++ b/ext/-test-/load/stringify_target/stringify_target.h @@ -1,4 +1,4 @@ #include #include "ruby/internal/dllexport.h" -RUBY_EXTERN VALUE stt_any_method(VALUE); +RUBY_FUNC_EXPORTED VALUE stt_any_method(VALUE); 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 7e4e2129069ab8..2f76e174d5db84 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! @@ -769,7 +773,6 @@ def mf.macro(name, values, max = 70) end submakeopts << 'EXTLDFLAGS="$(EXTLDFLAGS)"' submakeopts << 'EXTINITS="$(EXTINITS)"' - submakeopts << 'UPDATE_LIBRARIES="$(UPDATE_LIBRARIES)"' submakeopts << 'SHOWFLAGS=' mf.macro "SUBMAKEOPTS", submakeopts mf.macro "NOTE_MESG", %w[$(RUBY) $(top_srcdir)/tool/lib/colorize.rb skip] @@ -825,7 +828,7 @@ def mf.macro(name, values, max = 70) end mf.puts "#{t}:#{pd}\n\t$(Q)#{submake} $(MFLAGS) V=$(V) $(@F)" if clean and clean.begin(1) - mf.puts "\t$(Q)$(RM) $(ext_build_dir)/exts.mk\n\t$(Q)$(RMDIRS) -p $(@D)" + mf.puts "\t$(Q)$(RM) $(ext_build_dir)/exts.mk\n\t$(Q)$(RMDIRS) $(@D)" end end end 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 866a49eff4590e..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 @@ -560,7 +562,7 @@ dump_object(VALUE obj, struct dump_config *dc) } } - if (FL_TEST(obj, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(obj)) { dump_append(dc, ", \"singleton\":true"); } } 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/pathname/extconf.rb b/ext/pathname/extconf.rb index 84e68277aae84e..b4e1617b9e6b7f 100644 --- a/ext/pathname/extconf.rb +++ b/ext/pathname/extconf.rb @@ -1,4 +1,3 @@ # frozen_string_literal: false require 'mkmf' -have_func("rb_file_s_birthtime") create_makefile('pathname') diff --git a/ext/pathname/pathname.c b/ext/pathname/pathname.c index 878f216fb5cad7..cdecb3f897ee68 100644 --- a/ext/pathname/pathname.c +++ b/ext/pathname/pathname.c @@ -479,7 +479,6 @@ path_atime(VALUE self) return rb_funcall(rb_cFile, id_atime, 1, get_strpath(self)); } -#if defined(HAVE_RB_FILE_S_BIRTHTIME) /* * call-seq: * pathname.birthtime -> time @@ -494,10 +493,6 @@ path_birthtime(VALUE self) { return rb_funcall(rb_cFile, id_birthtime, 1, get_strpath(self)); } -#else -/* check at compilation time for `respond_to?` */ -# define path_birthtime rb_f_notimplement -#endif /* * call-seq: diff --git a/ext/pty/extconf.rb b/ext/pty/extconf.rb index ba0c4286fd31d1..da3655cf4d7118 100644 --- a/ext/pty/extconf.rb +++ b/ext/pty/extconf.rb @@ -13,10 +13,13 @@ have_header("util.h") # OpenBSD openpty util = have_library("util", "openpty") end - if have_func("posix_openpt") or + openpt = have_func("posix_openpt") + if openpt + have_func("ptsname_r") or have_func("ptsname") + end + if openpt or (util or have_func("openpty")) or have_func("_getpty") or - have_func("ptsname") or have_func("ioctl") create_makefile('pty') end diff --git a/ext/pty/pty.c b/ext/pty/pty.c index 8dca8ba281dde6..5ca55e41d67744 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_POSIX_OPENPT) || 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..08a9c4860b74df 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" @@ -18,15 +17,40 @@ ID id_warn, id_warning, id_gets, id_assoc; +enum lex_type { + lex_type_str, + lex_type_io, + lex_type_generic, +}; + struct ripper { rb_parser_t *p; + enum lex_type type; + union { + struct lex_pointer_string ptr_str; + VALUE val; + } data; }; static void ripper_parser_mark2(void *ptr) { struct ripper *r = (struct ripper*)ptr; - if (r->p) ripper_parser_mark(r->p); + if (r->p) { + ripper_parser_mark(r->p); + + switch (r->type) { + case lex_type_str: + rb_gc_mark(r->data.ptr_str.str); + break; + case lex_type_io: + rb_gc_mark(r->data.val); + break; + case lex_type_generic: + rb_gc_mark(r->data.val); + break; + } + } } static void @@ -55,8 +79,9 @@ static const rb_data_type_t parser_data_type = { }; static VALUE -ripper_lex_get_generic(struct parser_params *p, VALUE src) +ripper_lex_get_generic(struct parser_params *p, rb_parser_input_data input, int line_count) { + VALUE src = (VALUE)input; VALUE line = rb_funcallv_public(src, id_gets, 0, 0); if (!NIL_P(line) && !RB_TYPE_P(line, T_STRING)) { rb_raise(rb_eTypeError, @@ -80,11 +105,18 @@ ripper_compile_error(struct parser_params *p, const char *fmt, ...) } static VALUE -ripper_lex_io_get(struct parser_params *p, VALUE src) +ripper_lex_io_get(struct parser_params *p, rb_parser_input_data input, int line_count) { + VALUE src = (VALUE)input; return rb_io_gets(src); } +static VALUE +ripper_lex_get_str(struct parser_params *p, rb_parser_input_data input, int line_count) +{ + return rb_parser_lex_get_str((struct lex_pointer_string *)input); +} + static VALUE ripper_s_allocate(VALUE klass) { @@ -94,7 +126,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 @@ -155,7 +188,7 @@ ripper_parser_encoding(VALUE vparser) { struct parser_params *p = ripper_parser_params(vparser, false); - return rb_ruby_parser_encoding(p); + return rb_enc_from_encoding(rb_ruby_parser_encoding(p)); } /* @@ -294,26 +327,38 @@ parser_dedent_string(VALUE self, VALUE input, VALUE width) static VALUE ripper_initialize(int argc, VALUE *argv, VALUE self) { + struct ripper *r; struct parser_params *p; VALUE src, fname, lineno; - VALUE (*gets)(struct parser_params*,VALUE); - VALUE input, sourcefile_string; + rb_parser_lex_gets_func *gets; + VALUE sourcefile_string; const char *sourcefile; int sourceline; + rb_parser_input_data input; p = ripper_parser_params(self, false); + TypedData_Get_Struct(self, struct ripper, &parser_data_type, r); rb_scan_args(argc, argv, "12", &src, &fname, &lineno); if (RB_TYPE_P(src, T_FILE)) { gets = ripper_lex_io_get; + r->type = lex_type_io; + r->data.val = src; + input = (rb_parser_input_data)src; } else if (rb_respond_to(src, id_gets)) { gets = ripper_lex_get_generic; + r->type = lex_type_generic; + r->data.val = src; + input = (rb_parser_input_data)src; } else { StringValue(src); - gets = rb_ruby_ripper_lex_get_str; + gets = ripper_lex_get_str; + r->type = lex_type_str; + r->data.ptr_str.str = src; + r->data.ptr_str.ptr = 0; + input = (rb_parser_input_data)&r->data.ptr_str; } - input = src; if (NIL_P(fname)) { fname = STR_NEW2("(ripper)"); OBJ_FREEZE(fname); diff --git a/ext/ripper/ripper_init.h b/ext/ripper/ripper_init.h index e9e7bc7e5fdbf4..664bb7bce368fc 100644 --- a/ext/ripper/ripper_init.h +++ b/ext/ripper/ripper_init.h @@ -2,8 +2,6 @@ #define RIPPER_INIT_H extern VALUE rb_ripper_none; -VALUE ripper_get_value(VALUE v); -ID ripper_get_id(VALUE v); PRINTF_ARGS(void ripper_compile_error(struct parser_params*, const char *fmt, ...), 2, 3); #endif /* RIPPER_INIT_H */ 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..820b8228a31fb7 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); @@ -1592,10 +1623,9 @@ strio_read(int argc, VALUE *argv, VALUE self) if (len > rest) len = rest; rb_str_resize(str, len); MEMCPY(RSTRING_PTR(str), RSTRING_PTR(ptr->string) + ptr->pos, char, len); - if (binary) - rb_enc_associate(str, rb_ascii8bit_encoding()); - else + if (!binary) { rb_enc_copy(str, ptr->string); + } } ptr->pos += RSTRING_LEN(str); return str; @@ -1617,28 +1647,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 +1728,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 +1763,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 +1780,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 +1856,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 +1904,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 2ab3f8b2c271cb..5784ce4994c4ac 100644 --- a/gc.c +++ b/gc.c @@ -21,8 +21,6 @@ #include -#define sighandler_t ruby_sighandler_t - #ifndef _WIN32 #include #include @@ -439,8 +437,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 = { @@ -462,8 +458,6 @@ static ruby_gc_params_t gc_params = { GC_OLDMALLOC_LIMIT_MIN, GC_OLDMALLOC_LIMIT_MAX, GC_OLDMALLOC_LIMIT_GROWTH_FACTOR, - - FALSE, }; /* GC_DEBUG: @@ -723,7 +717,6 @@ enum { BITS_SIZE = sizeof(bits_t), BITS_BITLENGTH = ( BITS_SIZE * CHAR_BIT ) }; -#define popcount_bits rb_popcount_intptr struct heap_page_header { struct heap_page *page; @@ -735,11 +728,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 { @@ -827,7 +815,7 @@ typedef struct rb_objspace { } flags; rb_event_flag_t hook_events; - VALUE next_object_id; + unsigned long long next_object_id; rb_size_pool_t size_pools[SIZE_POOL_COUNT]; @@ -904,7 +892,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; @@ -955,7 +942,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; @@ -1141,10 +1128,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 @@ -1160,7 +1143,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 @@ -1309,6 +1291,7 @@ total_freed_objects(rb_objspace_t *objspace) #define gc_mode(objspace) gc_mode_verify((enum gc_mode)(objspace)->flags.mode) #define gc_mode_set(objspace, m) ((objspace)->flags.mode = (unsigned int)gc_mode_verify(m)) +#define gc_needs_major_flags objspace->rgengc.need_major_gc #define is_marking(objspace) (gc_mode(objspace) == gc_mode_marking) #define is_sweeping(objspace) (gc_mode(objspace) == gc_mode_sweeping) @@ -1396,7 +1379,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); @@ -1892,33 +1874,65 @@ calloc1(size_t n) return calloc(1, n); } -rb_objspace_t * -rb_objspace_alloc(void) +static VALUE initial_stress = Qfalse; + +void +rb_gc_initial_stress_set(VALUE flag) { - 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"); - } + initial_stress = flag; +} - for (int i = 0; i < SIZE_POOL_COUNT; i++) { - rb_size_pool_t *size_pool = &size_pools[i]; +static void *rb_gc_impl_objspace_alloc(void); - size_pool->slot_size = (1 << i) * BASE_SLOT_SIZE; +#if USE_SHARED_GC +# include "dln.h" - ccan_list_head_init(&SIZE_POOL_EDEN_HEAP(size_pool)->pages); - ccan_list_head_init(&SIZE_POOL_TOMB_HEAP(size_pool)->pages); +void +ruby_external_gc_init() +{ + char *gc_so_path = getenv("RUBY_GC_LIBRARY_PATH"); + void *handle = NULL; + if (gc_so_path) { + char error[128]; + handle = dln_open(gc_so_path, error, sizeof(error)); + if (!handle) { + rb_bug("ruby_external_gc_init: Shared library %s cannot be opened (%s)", gc_so_path, error); + } } - rb_darray_make_without_gc(&objspace->weak_references, 0); +# define load_external_gc_func(name) do { \ + if (handle) { \ + rb_gc_functions->name = dln_symbol(handle, "rb_gc_impl_" #name); \ + if (!rb_gc_functions->name) { \ + rb_bug("ruby_external_gc_init: " #name " func not exported by library %s", gc_so_path); \ + } \ + } \ + else { \ + rb_gc_functions->name = rb_gc_impl_##name; \ + } \ +} while (0) - dont_gc_on(); + load_external_gc_func(objspace_alloc); - return objspace; +# undef load_external_gc_func } +# define rb_gc_impl_objspace_alloc rb_gc_functions->objspace_alloc +#endif + +rb_objspace_t * +rb_objspace_alloc(void) +{ +#if USE_SHARED_GC + ruby_external_gc_init(); +#endif + return (rb_objspace_t *)rb_gc_impl_objspace_alloc(); +} + +#if USE_SHARED_GC +# undef rb_gc_impl_objspace_alloc +#endif + static void free_stack_chunks(mark_stack_t *); static void mark_stack_free_cache(mark_stack_t *); static void heap_page_free(rb_objspace_t *objspace, struct heap_page *page); @@ -1932,13 +1946,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; @@ -1963,7 +1970,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); } @@ -2118,6 +2125,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) { @@ -2511,7 +2552,7 @@ heap_prepare(rb_objspace_t *objspace, rb_size_pool_t *size_pool, rb_heap_t *heap * sweeping and still don't have a free page, then * gc_sweep_finish_size_pool should allow us to create a new page. */ if (heap->free_pages == NULL && !heap_increment(objspace, size_pool, heap)) { - if (objspace->rgengc.need_major_gc == GPR_FLAG_NONE) { + if (gc_needs_major_flags == GPR_FLAG_NONE) { rb_bug("cannot create a new page after GC"); } else { // Major GC is required, which will allow us to create new page @@ -2653,18 +2694,50 @@ size_pool_slot_size(unsigned char pool_id) return slot_size; } -size_t -rb_size_pool_slot_size(unsigned char pool_id) -{ - return size_pool_slot_size(pool_id); -} - bool 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] = 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) @@ -2754,30 +2827,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) { @@ -2902,11 +2951,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); rb_ractor_newobj_cache_t *cache = &cr->newobj_cache; @@ -2942,25 +2987,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) @@ -3477,8 +3503,8 @@ obj_free(rb_objspace_t *objspace, VALUE obj) } -#define OBJ_ID_INCREMENT (sizeof(RVALUE) / 2) -#define OBJ_ID_INITIAL (OBJ_ID_INCREMENT * 2) +#define OBJ_ID_INCREMENT (sizeof(RVALUE)) +#define OBJ_ID_INITIAL (OBJ_ID_INCREMENT) static int object_id_cmp(st_data_t x, st_data_t y) @@ -3506,17 +3532,44 @@ static const struct st_hash_type object_id_hash_type = { object_id_hash, }; -void -Init_heap(void) +static void * +rb_gc_impl_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. */ heap_page_alloc_use_mmap = INIT_HEAP_PAGE_ALLOC_USE_MMAP; #endif - objspace->next_object_id = INT2FIX(OBJ_ID_INITIAL); + objspace->next_object_id = OBJ_ID_INITIAL; objspace->id_to_obj_tbl = st_init_table(&object_id_hash_type); objspace->obj_to_id_tbl = st_init_numtable(); @@ -3539,14 +3592,7 @@ Init_heap(void) 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 *); @@ -3726,6 +3772,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) { @@ -3737,6 +3784,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; @@ -3763,7 +3811,7 @@ internal_object_p(VALUE obj) break; case T_CLASS: if (!p->as.basic.klass) break; - if (FL_TEST(obj, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(obj)) { return rb_singleton_class_internal_p(obj); } return 0; @@ -4463,25 +4511,28 @@ id2ref(VALUE objid) #define NUM2PTR(x) NUM2ULL(x) #endif rb_objspace_t *objspace = &rb_objspace; - VALUE ptr; - void *p0; objid = rb_to_int(objid); if (FIXNUM_P(objid) || rb_big_size(objid) <= SIZEOF_VOIDP) { - ptr = NUM2PTR(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; + VALUE ptr = NUM2PTR(objid); + if (SPECIAL_CONST_P(ptr)) { + if (ptr == Qtrue) return Qtrue; + if (ptr == Qfalse) return Qfalse; + if (NIL_P(ptr)) return Qnil; + if (FIXNUM_P(ptr)) return ptr; + if (FLONUM_P(ptr)) return ptr; + + if (SYMBOL_P(ptr)) { + // Check that the symbol is valid + if (rb_static_id_valid_p(SYM2ID(ptr))) { + return ptr; + } + else { + rb_raise(rb_eRangeError, "%p is not symbol id value", (void *)ptr); + } + } - ptr = obj_id_to_ref(objid); - if ((ptr % sizeof(RVALUE)) == (4 << 2)) { - ID symid = ptr / sizeof(RVALUE); - p0 = (void *)ptr; - if (!rb_static_id_valid_p(symid)) - rb_raise(rb_eRangeError, "%p is not symbol id value", p0); - return ID2SYM(symid); + rb_raise(rb_eRangeError, "%+"PRIsVALUE" is not id value", rb_int2str(objid, 10)); } } @@ -4496,7 +4547,7 @@ id2ref(VALUE objid) } } - if (rb_int_ge(objid, objspace->next_object_id)) { + if (rb_int_ge(objid, ULL2NUM(objspace->next_object_id))) { rb_raise(rb_eRangeError, "%+"PRIsVALUE" is not id value", rb_int2str(objid, 10)); } else { @@ -4514,19 +4565,13 @@ os_id2ref(VALUE os, VALUE objid) static VALUE rb_find_object_id(VALUE obj, VALUE (*get_heap_object_id)(VALUE)) { - if (STATIC_SYM_P(obj)) { - return (SYM2ID(obj) * sizeof(RVALUE) + (4 << 2)) | FIXNUM_FLAG; - } - else if (FLONUM_P(obj)) { + if (SPECIAL_CONST_P(obj)) { #if SIZEOF_LONG == SIZEOF_VOIDP return LONG2NUM((SIGNED_VALUE)obj); #else return LL2NUM((SIGNED_VALUE)obj); #endif } - else if (SPECIAL_CONST_P(obj)) { - return LONG2NUM((SIGNED_VALUE)obj); - } return get_heap_object_id(obj); } @@ -4544,8 +4589,8 @@ cached_object_id(VALUE obj) else { GC_ASSERT(!FL_TEST(obj, FL_SEEN_OBJ_ID)); - id = objspace->next_object_id; - objspace->next_object_id = rb_int_plus(id, INT2FIX(OBJ_ID_INCREMENT)); + id = ULL2NUM(objspace->next_object_id); + objspace->next_object_id += OBJ_ID_INCREMENT; VALUE already_disabled = rb_gc_disable_no_rest(); st_insert(objspace->obj_to_id_tbl, (st_data_t)obj, (st_data_t)id); @@ -5411,10 +5456,7 @@ gc_sweep_page(rb_objspace_t *objspace, rb_heap_t *heap, struct gc_sweep_context sweep_page->size_pool->total_freed_objects += ctx->freed_slots; if (heap_pages_deferred_final && !finalizing) { - rb_thread_t *th = GET_THREAD(); - if (th) { - gc_finalize_deferred_register(objspace); - } + gc_finalize_deferred_register(objspace); } #if RGENGC_CHECK_MODE @@ -5586,7 +5628,7 @@ gc_sweep_finish_size_pool(rb_objspace_t *objspace, rb_size_pool_t *size_pool) grow_heap = TRUE; } else if (is_growth_heap) { /* Only growth heaps are allowed to start a major GC. */ - objspace->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_NOFREE; + gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE; size_pool->force_major_gc_count++; } } @@ -6395,11 +6437,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 ); @@ -6484,13 +6526,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 @@ -6756,7 +6810,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++; } @@ -7107,7 +7165,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 = GET_EC(); rb_vm_t *vm = rb_ec_vm_ptr(ec); @@ -7157,10 +7214,6 @@ gc_mark_roots(rb_objspace_t *objspace, const char **categoryp) mark_current_machine_context(objspace, ec); /* 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(); @@ -7169,7 +7222,6 @@ gc_mark_roots(rb_objspace_t *objspace, const char **categoryp) rb_gc_mark_global_tbl(); MARK_CHECKPOINT("object_id"); - rb_gc_mark(objspace->next_object_id); mark_tbl_no_pin(objspace, objspace->obj_to_id_tbl); /* Only mark ids */ if (stress_to_class) rb_gc_mark(stress_to_class); @@ -7926,7 +7978,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 @@ -8010,7 +8067,7 @@ gc_marks_finish(rb_objspace_t *objspace) } else { gc_report(1, objspace, "gc_marks_finish: next is full GC!!)\n"); - objspace->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_NOFREE; + gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE; } } } @@ -8026,20 +8083,20 @@ gc_marks_finish(rb_objspace_t *objspace) } if (objspace->rgengc.uncollectible_wb_unprotected_objects > objspace->rgengc.uncollectible_wb_unprotected_objects_limit) { - objspace->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_SHADY; + gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_SHADY; } if (objspace->rgengc.old_objects > objspace->rgengc.old_objects_limit) { - objspace->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_OLDGEN; + gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDGEN; } if (RGENGC_FORCE_MAJOR_GC) { - objspace->rgengc.need_major_gc = GPR_FLAG_MAJOR_BY_FORCE; + gc_needs_major_flags = GPR_FLAG_MAJOR_BY_FORCE; } gc_report(1, objspace, "gc_marks_finish (marks %"PRIdSIZE" objects, " "old %"PRIdSIZE" objects, total %"PRIdSIZE" slots, " "sweep %"PRIdSIZE" slots, increment: %"PRIdSIZE", next GC: %s)\n", objspace->marked_slots, objspace->rgengc.old_objects, heap_eden_total_slots(objspace), sweep_slots, heap_allocatable_pages(objspace), - objspace->rgengc.need_major_gc ? "major" : "minor"); + gc_needs_major_flags ? "major" : "minor"); } rb_ractor_finish_marking(); @@ -8086,7 +8143,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]; } @@ -8109,7 +8166,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) { @@ -8690,27 +8747,12 @@ 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 (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)); + rb_gc_copy_finalizer(dest, obj); } size_t @@ -8761,49 +8803,26 @@ rb_gc_ractor_newobj_cache_clear(rb_ractor_newobj_cache_t *newobj_cache) } } -void -rb_gc_force_recycle(VALUE obj) -{ - /* no-op */ -} - -#ifndef MARK_OBJECT_ARY_BUCKET_SIZE -#define MARK_OBJECT_ARY_BUCKET_SIZE 1024 -#endif - void rb_gc_register_mark_object(VALUE obj) { if (!is_pointer_to_heap(&rb_objspace, (void *)obj)) return; - RB_VM_LOCK_ENTER(); - { - VALUE ary_ary = GET_VM()->mark_object_ary; - VALUE ary = rb_ary_last(0, 0, ary_ary); - - if (NIL_P(ary) || RARRAY_LEN(ary) >= MARK_OBJECT_ARY_BUCKET_SIZE) { - ary = rb_ary_hidden_new(MARK_OBJECT_ARY_BUCKET_SIZE); - rb_ary_push(ary_ary, ary); - } - - rb_ary_push(ary, obj); - } - RB_VM_LOCK_LEAVE(); + rb_vm_register_global_object(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, @@ -8820,17 +8839,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); @@ -8846,8 +8865,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, @@ -8921,7 +8938,7 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark) #if RGENGC_ESTIMATE_OLDMALLOC if (!full_mark) { if (objspace->rgengc.oldmalloc_increase > objspace->rgengc.oldmalloc_increase_limit) { - objspace->rgengc.need_major_gc |= GPR_FLAG_MAJOR_BY_OLDMALLOC; + gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_OLDMALLOC; objspace->rgengc.oldmalloc_increase_limit = (size_t)(objspace->rgengc.oldmalloc_increase_limit * gc_params.oldmalloc_limit_growth_factor); @@ -8932,7 +8949,7 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark) if (0) fprintf(stderr, "%"PRIdSIZE"\t%d\t%"PRIuSIZE"\t%"PRIuSIZE"\t%"PRIdSIZE"\n", rb_gc_count(), - objspace->rgengc.need_major_gc, + gc_needs_major_flags, objspace->rgengc.oldmalloc_increase, objspace->rgengc.oldmalloc_increase_limit, gc_params.oldmalloc_limit_max); @@ -8984,7 +9001,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); @@ -9008,8 +9025,8 @@ gc_start(rb_objspace_t *objspace, unsigned int reason) objspace->flags.immediate_sweep = !(flag & (1<rgengc.need_major_gc) { - reason |= objspace->rgengc.need_major_gc; + if (gc_needs_major_flags) { + reason |= gc_needs_major_flags; do_full_mark = TRUE; } else if (RGENGC_FORCE_MAJOR_GC) { @@ -9017,7 +9034,7 @@ gc_start(rb_objspace_t *objspace, unsigned int reason) do_full_mark = TRUE; } - objspace->rgengc.need_major_gc = GPR_FLAG_NONE; + gc_needs_major_flags = GPR_FLAG_NONE; if (do_full_mark && (reason & GPR_FLAG_MAJOR_MASK) == 0) { reason |= GPR_FLAG_MAJOR_BY_FORCE; /* GC by CAPI, METHOD, and so on. */ @@ -9101,10 +9118,7 @@ gc_start(rb_objspace_t *objspace, unsigned int reason) static void gc_rest(rb_objspace_t *objspace) { - int marking = is_incremental_marking(objspace); - int sweeping = is_lazy_sweeping(objspace); - - if (marking || sweeping) { + if (is_incremental_marking(objspace) || is_lazy_sweeping(objspace)) { unsigned int lock_lev; gc_enter(objspace, gc_enter_event_rest, &lock_lev); @@ -9278,7 +9292,7 @@ gc_enter(rb_objspace_t *objspace, enum gc_enter_event event, unsigned int *lock_ gc_enter_count(event); if (UNLIKELY(during_gc != 0)) rb_bug("during_gc != 0"); - if (RGENGC_CHECK_MODE >= 3) gc_verify_internal_consistency(objspace); + if (RGENGC_CHECK_MODE >= 3 && (dont_gc_val() == 0)) gc_verify_internal_consistency(objspace); during_gc = TRUE; RUBY_DEBUG_LOG("%s (%s)",gc_enter_event_cstr(event), gc_current_status(objspace)); @@ -9380,18 +9394,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); + } } } } @@ -9488,12 +9504,11 @@ gc_is_moveable_obj(rb_objspace_t *objspace, VALUE obj) switch (BUILTIN_TYPE(obj)) { case T_NONE: - case T_NIL: case T_MOVED: case T_ZOMBIE: return FALSE; case T_SYMBOL: - if (DYNAMIC_SYM_P(obj) && (RSYMBOL(obj)->id & ~ID_SCOPE_MASK)) { + if (RSYMBOL(obj)->id & ~ID_SCOPE_MASK) { return FALSE; } /* fall through */ @@ -9577,20 +9592,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)); @@ -9958,9 +9979,6 @@ update_cc_tbl_i(VALUE ccs_ptr, void *data) } for (int i=0; ilen; i++) { - if (gc_object_moved_p(objspace, (VALUE)ccs->entries[i].ci)) { - ccs->entries[i].ci = (struct rb_callinfo *)rb_gc_location((VALUE)ccs->entries[i].ci); - } if (gc_object_moved_p(objspace, (VALUE)ccs->entries[i].cc)) { ccs->entries[i].cc = (struct rb_callcache *)rb_gc_location((VALUE)ccs->entries[i].cc); } @@ -10212,9 +10230,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: @@ -10420,7 +10436,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))); } } @@ -10437,9 +10455,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 { @@ -10555,7 +10575,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); @@ -10604,8 +10624,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); @@ -10739,7 +10759,7 @@ gc_info_decode(rb_objspace_t *objspace, const VALUE hash_or_key, const unsigned SET(major_by, major_by); if (orig_flags == 0) { /* set need_major_by only if flags not set explicitly */ - unsigned int need_major_flags = objspace->rgengc.need_major_gc; + unsigned int need_major_flags = gc_needs_major_flags; need_major_by = (need_major_flags & GPR_FLAG_MAJOR_BY_NOFREE) ? sym_nofree : (need_major_flags & GPR_FLAG_MAJOR_BY_OLDGEN) ? sym_oldgen : @@ -11176,18 +11196,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; } @@ -11693,40 +11709,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) { @@ -12380,6 +12362,34 @@ ruby_mimmalloc(size_t size) return mem; } +void * +ruby_mimcalloc(size_t num, size_t size) +{ + void *mem; +#if CALC_EXACT_MALLOC_SIZE + size += sizeof(struct malloc_obj_info); +#endif + mem = calloc(num, size); +#if CALC_EXACT_MALLOC_SIZE + if (!mem) { + return NULL; + } + else + /* set 0 for consistency of allocated_size/allocations */ + { + struct malloc_obj_info *info = mem; + info->size = 0; +#if USE_GC_MALLOC_OBJ_INFO_DETAILS + info->gen = 0; + info->file = NULL; + info->line = 0; +#endif + mem = info + 1; + } +#endif + return mem; +} + void ruby_mimfree(void *ptr) { @@ -13141,7 +13151,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 @@ -13403,7 +13412,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 * @@ -13418,17 +13426,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 @@ -13466,7 +13463,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 * @@ -13557,7 +13559,8 @@ rb_gcdebug_sentinel(VALUE obj, const char *name) #endif /* GC_DEBUG */ -/* +/* :nodoc: + * * call-seq: * GC.add_stress_to_class(class[, ...]) * @@ -13576,7 +13579,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 f32c45ed8d4dbe..ad979c1c5c4b1e 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 ea9caafc0754b1d6236a490d59e624b53209734a +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 56ae86bb5f4864f5057778bd45b280248b012329 -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 32b0b10e9cc69e..f34f64065b67d1 100644 --- a/hash.c +++ b/hash.c @@ -394,6 +394,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; /* @@ -2956,7 +2959,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 { @@ -3902,19 +3905,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; } @@ -3926,6 +3920,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; } @@ -4162,7 +4159,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){ @@ -4439,7 +4436,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/imemo.c b/imemo.c index 0031b3322ccf8b..1face1ce4ea2fb 100644 --- a/imemo.c +++ b/imemo.c @@ -130,7 +130,7 @@ rb_imemo_memsize(VALUE obj) size_t size = 0; switch (imemo_type(obj)) { case imemo_ast: - size += rb_ast_memsize((rb_ast_t *)obj); + rb_bug("imemo_ast is obsolete"); break; case imemo_callcache: @@ -196,7 +196,6 @@ cc_table_mark_i(ID id, VALUE ccs_ptr, void *data) VM_ASSERT((VALUE)data == ccs->entries[i].cc->klass); VM_ASSERT(vm_cc_check_cme(ccs->entries[i].cc, ccs->cme)); - rb_gc_mark_movable((VALUE)ccs->entries[i].ci); rb_gc_mark_movable((VALUE)ccs->entries[i].cc); } return ID_TABLE_CONTINUE; @@ -274,7 +273,7 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) { switch (imemo_type(obj)) { case imemo_ast: - rb_ast_mark_and_move((rb_ast_t *)obj, reference_updating); + rb_bug("imemo_ast is obsolete"); break; case imemo_callcache: { @@ -514,8 +513,7 @@ rb_imemo_free(VALUE obj) { switch (imemo_type(obj)) { case imemo_ast: - rb_ast_free((rb_ast_t *)obj); - RB_DEBUG_COUNTER_INC(obj_imemo_ast); + rb_bug("imemo_ast is obsolete"); break; case imemo_callcache: 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/encoding/encoding.h b/include/ruby/internal/encoding/encoding.h index dc3e0151f0420c..a680651a818073 100644 --- a/include/ruby/internal/encoding/encoding.h +++ b/include/ruby/internal/encoding/encoding.h @@ -28,6 +28,7 @@ #include "ruby/internal/attr/pure.h" #include "ruby/internal/attr/returns_nonnull.h" #include "ruby/internal/dllexport.h" +#include "ruby/internal/encoding/coderange.h" #include "ruby/internal/value.h" #include "ruby/internal/core/rbasic.h" #include "ruby/internal/fl_type.h" diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index 44b3e8cc0ff319..0a05166784b4e0 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -395,7 +395,7 @@ ruby_fl_type { * 3rd parties. It must be an implementation detail that they should never * know. Might better be hidden. */ - RUBY_FL_SINGLETON = RUBY_FL_USER0, + RUBY_FL_SINGLETON = RUBY_FL_USER1, }; enum { @@ -905,6 +905,10 @@ RB_OBJ_FROZEN(VALUE obj) } } +RUBY_SYMBOL_EXPORT_BEGIN +void rb_obj_freeze_inline(VALUE obj); +RUBY_SYMBOL_EXPORT_END + RBIMPL_ATTR_ARTIFICIAL() /** * This is an implementation detail of RB_OBJ_FREEZE(). 3rd parties need not @@ -915,11 +919,7 @@ RBIMPL_ATTR_ARTIFICIAL() static inline void RB_OBJ_FREEZE_RAW(VALUE obj) { - RB_FL_SET_RAW(obj, RUBY_FL_FREEZE); + rb_obj_freeze_inline(obj); } -RUBY_SYMBOL_EXPORT_BEGIN -void rb_obj_freeze_inline(VALUE obj); -RUBY_SYMBOL_EXPORT_END - #endif /* RBIMPL_FL_TYPE_H */ 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/io/buffer.h b/include/ruby/io/buffer.h index b044db053995bf..e4d98bf0511b12 100644 --- a/include/ruby/io/buffer.h +++ b/include/ruby/io/buffer.h @@ -69,14 +69,10 @@ enum rb_io_buffer_endian { RB_IO_BUFFER_LITTLE_ENDIAN = 4, RB_IO_BUFFER_BIG_ENDIAN = 8, -#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_LITTLE_ENDIAN, -#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#if defined(WORDS_BIGENDIAN) RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_BIG_ENDIAN, -#elif defined(REG_DWORD) && REG_DWORD == REG_DWORD_LITTLE_ENDIAN +#else RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_LITTLE_ENDIAN, -#elif defined(REG_DWORD) && REG_DWORD == REG_DWORD_BIG_ENDIAN - RB_IO_BUFFER_HOST_ENDIAN = RB_IO_BUFFER_BIG_ENDIAN, #endif RB_IO_BUFFER_NETWORK_ENDIAN = RB_IO_BUFFER_BIG_ENDIAN 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/class.h b/internal/class.h index 594f1daea74b27..8a6c95623317b3 100644 --- a/internal/class.h +++ b/internal/class.h @@ -108,6 +108,7 @@ struct RClass_and_rb_classext_t { #define RCLASS_SUPERCLASSES(c) (RCLASS_EXT(c)->superclasses) #define RCLASS_ATTACHED_OBJECT(c) (RCLASS_EXT(c)->as.singleton_class.attached_object) +#define RCLASS_IS_ROOT FL_USER0 #define RICLASS_IS_ORIGIN FL_USER0 #define RCLASS_SUPERCLASSES_INCLUDE_SELF FL_USER2 #define RICLASS_ORIGIN_SHARED_MTBL FL_USER3 @@ -195,10 +196,16 @@ static inline void RCLASS_SET_INCLUDER(VALUE iclass, VALUE klass); VALUE rb_class_inherited(VALUE, VALUE); VALUE rb_keyword_error_new(const char *, VALUE); +static inline bool +RCLASS_SINGLETON_P(VALUE klass) +{ + return RB_TYPE_P(klass, T_CLASS) && FL_TEST_RAW(klass, FL_SINGLETON); +} + static inline rb_alloc_func_t RCLASS_ALLOCATOR(VALUE klass) { - if (FL_TEST_RAW(klass, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(klass)) { return 0; } return RCLASS_EXT(klass)->as.class.allocator; @@ -207,7 +214,7 @@ RCLASS_ALLOCATOR(VALUE klass) static inline void RCLASS_SET_ALLOCATOR(VALUE klass, rb_alloc_func_t allocator) { - assert(!FL_TEST(klass, FL_SINGLETON)); + assert(!RCLASS_SINGLETON_P(klass)); RCLASS_EXT(klass)->as.class.allocator = allocator; } @@ -267,8 +274,7 @@ RCLASS_SET_CLASSPATH(VALUE klass, VALUE classpath, bool permanent) static inline VALUE RCLASS_SET_ATTACHED_OBJECT(VALUE klass, VALUE attached_object) { - assert(BUILTIN_TYPE(klass) == T_CLASS); - assert(FL_TEST_RAW(klass, FL_SINGLETON)); + assert(RCLASS_SINGLETON_P(klass)); RB_OBJ_WRITE(klass, &RCLASS_EXT(klass)->as.singleton_class.attached_object, attached_object); return attached_object; diff --git a/internal/encoding.h b/internal/encoding.h index 11ffa6d83d955f..fe9ea10ec4445e 100644 --- a/internal/encoding.h +++ b/internal/encoding.h @@ -18,6 +18,7 @@ /* encoding.c */ ID rb_id_encoding(void); +const char * rb_enc_inspect_name(rb_encoding *enc); rb_encoding *rb_enc_get_from_index(int index); rb_encoding *rb_enc_check_str(VALUE str1, VALUE str2); int rb_encdb_replicate(const char *alias, const char *orig); 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 c4157b51bcac58..ecc3f11b2cc563 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -16,6 +16,10 @@ #include "ruby/ruby.h" /* for rb_event_flag_t */ #include "vm_core.h" /* for GET_EC() */ +#ifndef USE_SHARED_GC +# define USE_SHARED_GC 0 +#endif + #if defined(__x86_64__) && !defined(_ILP32) && defined(__GNUC__) #define SET_MACHINE_STACK_END(p) __asm__ __volatile__ ("movq\t%%rsp, %0" : "=r" (*(p))) #elif defined(__i386) && defined(__GNUC__) @@ -79,14 +83,6 @@ rb_gc_debug_body(const char *mode, const char *msg, int st, void *ptr) #define RUBY_GC_INFO if(0)printf #endif -#define RUBY_MARK_MOVABLE_UNLESS_NULL(ptr) do { \ - VALUE markobj = (ptr); \ - if (RTEST(markobj)) {rb_gc_mark_movable(markobj);} \ -} while (0) -#define RUBY_MARK_UNLESS_NULL(ptr) do { \ - VALUE markobj = (ptr); \ - if (RTEST(markobj)) {rb_gc_mark(markobj);} \ -} while (0) #define RUBY_FREE_UNLESS_NULL(ptr) if(ptr){ruby_xfree(ptr);(ptr)=NULL;} #if STACK_GROW_DIRECTION > 0 @@ -122,32 +118,15 @@ 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 @@ -212,20 +191,16 @@ 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); +RUBY_ATTR_MALLOC void *ruby_mimcalloc(size_t num, size_t size); void ruby_mimfree(void *ptr); void rb_gc_prepare_heap(void); 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); @@ -238,8 +213,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); @@ -250,6 +226,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/imemo.h b/internal/imemo.h index 673e7e668af6a1..36c07769874877 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -39,7 +39,7 @@ enum imemo_type { imemo_ment = 6, imemo_iseq = 7, imemo_tmpbuf = 8, - imemo_ast = 9, + imemo_ast = 9, // Obsolete due to the universal parser imemo_parser_strterm = 10, imemo_callinfo = 11, imemo_callcache = 12, 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..f06020c73f698f 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; @@ -53,30 +51,32 @@ size_t rb_ruby_parser_memsize(const void *ptr); void rb_ruby_parser_set_options(rb_parser_t *p, int print, int loop, int chomp, int split); rb_parser_t *rb_ruby_parser_set_context(rb_parser_t *p, const struct rb_iseq_struct *base, int main); -void rb_ruby_parser_set_script_lines(rb_parser_t *p, VALUE lines_array); +void rb_ruby_parser_set_script_lines(rb_parser_t *p); void rb_ruby_parser_error_tolerant(rb_parser_t *p); -rb_ast_t* rb_ruby_parser_compile_file_path(rb_parser_t *p, VALUE fname, VALUE file, int start); void rb_ruby_parser_keep_tokens(rb_parser_t *p); -rb_ast_t* rb_ruby_parser_compile_generic(rb_parser_t *p, VALUE (*lex_gets)(VALUE, int), VALUE fname, VALUE input, int start); -rb_ast_t* rb_ruby_parser_compile_string_path(rb_parser_t *p, VALUE f, VALUE s, int line); +typedef VALUE (rb_parser_lex_gets_func)(struct parser_params*, rb_parser_input_data, int); +rb_ast_t *rb_parser_compile(rb_parser_t *p, rb_parser_lex_gets_func *gets, VALUE fname, rb_parser_input_data input, int line); RUBY_SYMBOL_EXPORT_BEGIN -VALUE rb_ruby_parser_encoding(rb_parser_t *p); +rb_encoding *rb_ruby_parser_encoding(rb_parser_t *p); int rb_ruby_parser_end_seen_p(rb_parser_t *p); int rb_ruby_parser_set_yydebug(rb_parser_t *p, int flag); 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); RUBY_SYMBOL_EXPORT_END +#ifndef UNIVERSAL_PARSER +rb_parser_t *rb_ruby_parser_allocate(void); +rb_parser_t *rb_ruby_parser_new(void); +#endif + #ifdef RIPPER void ripper_parser_mark(void *ptr); void ripper_parser_free(void *ptr); @@ -90,7 +90,7 @@ VALUE rb_ruby_parser_debug_output(rb_parser_t *p); void rb_ruby_parser_set_debug_output(rb_parser_t *p, VALUE output); VALUE rb_ruby_parser_parsing_thread(rb_parser_t *p); void rb_ruby_parser_set_parsing_thread(rb_parser_t *p, VALUE parsing_thread); -void rb_ruby_parser_ripper_initialize(rb_parser_t *p, VALUE (*gets)(struct parser_params*,VALUE), VALUE input, VALUE sourcefile_string, const char *sourcefile, int sourceline); +void rb_ruby_parser_ripper_initialize(rb_parser_t *p, rb_parser_lex_gets_func *gets, rb_parser_input_data input, VALUE sourcefile_string, const char *sourcefile, int sourceline); VALUE rb_ruby_parser_result(rb_parser_t *p); rb_encoding *rb_ruby_parser_enc(rb_parser_t *p); VALUE rb_ruby_parser_ruby_sourcefile_string(rb_parser_t *p); @@ -98,13 +98,15 @@ int rb_ruby_parser_ruby_sourceline(rb_parser_t *p); int rb_ruby_parser_lex_state(rb_parser_t *p); void rb_ruby_ripper_parse0(rb_parser_t *p); int rb_ruby_ripper_dedent_string(rb_parser_t *p, VALUE string, int width); -VALUE rb_ruby_ripper_lex_get_str(rb_parser_t *p, VALUE s); int rb_ruby_ripper_initialized_p(rb_parser_t *p); void rb_ruby_ripper_parser_initialize(rb_parser_t *p); 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..76200ae4e477bf 100644 --- a/internal/ruby_parser.h +++ b/internal/ruby_parser.h @@ -5,20 +5,27 @@ #include "internal/bignum.h" #include "internal/compilers.h" #include "internal/complex.h" -#include "internal/imemo.h" +#include "internal/parse.h" #include "internal/rational.h" #include "rubyparser.h" #include "vm.h" +struct lex_pointer_string { + VALUE str; + long ptr; +}; + RUBY_SYMBOL_EXPORT_BEGIN #ifdef UNIVERSAL_PARSER -rb_parser_t *rb_parser_params_allocate(void); +const rb_parser_config_t *rb_ruby_parser_config(void); rb_parser_t *rb_parser_params_new(void); #endif 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_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_parser_lex_get_str(struct lex_pointer_string *ptr_str); VALUE rb_node_str_string_val(const NODE *); VALUE rb_node_sym_string_val(const NODE *); @@ -28,7 +35,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 *); @@ -39,15 +45,18 @@ RUBY_SYMBOL_EXPORT_END VALUE rb_parser_end_seen_p(VALUE); VALUE rb_parser_encoding(VALUE); VALUE rb_parser_set_yydebug(VALUE, VALUE); +VALUE rb_parser_build_script_lines_from(rb_parser_ary_t *script_lines); +void rb_parser_aset_script_lines_for(VALUE path, rb_parser_ary_t *script_lines); void rb_parser_set_options(VALUE, int, int, int, int); -void *rb_parser_load_file(VALUE parser, VALUE name); -void rb_parser_set_script_lines(VALUE vparser, VALUE lines_array); +VALUE rb_parser_load_file(VALUE parser, VALUE name); +void rb_parser_set_script_lines(VALUE vparser); void rb_parser_error_tolerant(VALUE vparser); void rb_parser_keep_tokens(VALUE vparser); -rb_ast_t *rb_parser_compile_string(VALUE, const char*, VALUE, int); -rb_ast_t *rb_parser_compile_file_path(VALUE vparser, VALUE fname, VALUE input, int line); -rb_ast_t *rb_parser_compile_generic(VALUE vparser, VALUE (*lex_gets)(VALUE, int), VALUE fname, VALUE input, int line); +VALUE rb_parser_compile_string(VALUE, const char*, VALUE, int); +VALUE rb_parser_compile_file_path(VALUE vparser, VALUE fname, VALUE input, int line); +VALUE rb_parser_compile_generic(VALUE vparser, rb_parser_lex_gets_func *lex_gets, VALUE fname, VALUE input, int line); +VALUE rb_parser_compile_array(VALUE vparser, VALUE fname, VALUE array, int start); enum lex_state_bits { EXPR_BEG_bit, /* ignore newline, +/- is a sign. */ @@ -88,4 +97,7 @@ enum lex_state_e { EXPR_NONE = 0 }; +VALUE rb_ruby_ast_new(const NODE *const root, rb_parser_ary_t *script_lines); +rb_ast_t *rb_ruby_ast_data_get(VALUE vast); + #endif /* INTERNAL_RUBY_PARSE_H */ 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 cde81a1a25246f..fb37f731147096 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 @@ -77,7 +78,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) @@ -108,6 +109,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 a32a14e0458568..74635e6ad8a6fc 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -45,13 +45,13 @@ VALUE rb_vm_push_frame_fname(struct rb_execution_context_struct *ec, VALUE fname /* vm.c */ VALUE rb_obj_is_thread(VALUE obj); void rb_vm_mark(void *ptr); +void rb_vm_register_global_object(VALUE obj); void rb_vm_each_stack_value(void *ptr, void (*cb)(VALUE, void*), void *ctx); PUREFUNC(VALUE rb_vm_top_self(void)); const void **rb_vm_get_insns_address_table(void); VALUE rb_source_location(int *pline); const char *rb_source_location_cstr(int *pline); void rb_vm_pop_cfunc_frame(void); -int rb_vm_add_root_module(VALUE module); void rb_vm_check_redefinition_by_prepend(VALUE klass); int rb_vm_check_optimizable_mid(VALUE mid); VALUE rb_yield_refine_block(VALUE refinement, VALUE refinements); @@ -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 f77e5f85c81f6d..feff4459437cca 100644 --- a/io.c +++ b/io.c @@ -112,6 +112,7 @@ #include "encindex.h" #include "id.h" #include "internal.h" +#include "internal/class.h" #include "internal/encoding.h" #include "internal/error.h" #include "internal/inits.h" @@ -2276,7 +2277,7 @@ rb_io_writev(VALUE io, int argc, const VALUE *argv) if (argc > 1 && rb_obj_method_arity(io, id_write) == 1) { if (io != rb_ractor_stderr() && RTEST(ruby_verbose)) { VALUE klass = CLASS_OF(io); - char sep = FL_TEST(klass, FL_SINGLETON) ? (klass = io, '.') : '#'; + char sep = RCLASS_SINGLETON_P(klass) ? (klass = io, '.') : '#'; rb_category_warning( RB_WARN_CATEGORY_DEPRECATED, "%+"PRIsVALUE"%c""write is outdated interface" " which accepts just one argument", @@ -14570,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 @@ -15628,7 +15629,7 @@ Init_IO(void) rb_define_hooked_variable("$,", &rb_output_fs, 0, deprecated_str_setter); rb_default_rs = rb_fstring_lit("\n"); /* avoid modifying RS_default */ - rb_gc_register_mark_object(rb_default_rs); + rb_vm_register_global_object(rb_default_rs); rb_rs = rb_default_rs; rb_output_rs = Qnil; rb_define_hooked_variable("$/", &rb_rs, 0, deprecated_str_setter); @@ -15822,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 9e9f60932367d5..364ca63cf3e2e1 100644 --- a/iseq.c +++ b/iseq.c @@ -167,7 +167,7 @@ rb_iseq_free(const rb_iseq_t *iseq) struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); rb_rjit_free_iseq(iseq); /* Notify RJIT */ #if USE_YJIT - rb_yjit_iseq_free(body->yjit_payload); + rb_yjit_iseq_free(iseq); if (FL_TEST_RAW((VALUE)iseq, ISEQ_TRANSLATED)) { RUBY_ASSERT(rb_yjit_live_iseq_count > 0); rb_yjit_live_iseq_count--; @@ -292,6 +292,10 @@ static bool cc_is_active(const struct rb_callcache *cc, bool reference_updating) { if (cc) { + if (cc == rb_vm_empty_cc() || rb_vm_empty_cc_for_super()) { + return false; + } + if (reference_updating) { cc = (const struct rb_callcache *)rb_gc_location((VALUE)cc); } @@ -373,7 +377,7 @@ rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating) rb_rjit_iseq_update_references(body); #endif #if USE_YJIT - rb_yjit_iseq_update_references(body->yjit_payload); + rb_yjit_iseq_update_references(iseq); #endif } else { @@ -538,6 +542,11 @@ iseq_location_setup(rb_iseq_t *iseq, VALUE name, VALUE path, VALUE realpath, int RB_OBJ_WRITE(iseq, &loc->label, name); RB_OBJ_WRITE(iseq, &loc->base_label, name); loc->first_lineno = first_lineno; + + if (ISEQ_BODY(iseq)->local_iseq == iseq && strcmp(RSTRING_PTR(name), "initialize") == 0) { + ISEQ_BODY(iseq)->param.flags.use_block = 1; + } + if (code_location) { loc->node_id = node_id; loc->code_location = *code_location; @@ -720,18 +729,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 = { + .frozen_string_literal = -1, // unspecified }; -static const rb_compile_option_t COMPILE_OPTION_FALSE = {0}; +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) @@ -764,9 +781,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; } @@ -808,41 +827,44 @@ 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; } rb_iseq_t * -rb_iseq_new(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, +rb_iseq_new(const VALUE vast, VALUE name, VALUE path, VALUE realpath, const rb_iseq_t *parent, enum rb_iseq_type type) { - return rb_iseq_new_with_opt(ast, name, path, realpath, 0, parent, - 0, type, &COMPILE_OPTION_DEFAULT); + return rb_iseq_new_with_opt(vast, name, path, realpath, 0, parent, + 0, type, &COMPILE_OPTION_DEFAULT, + Qnil); } static int -ast_line_count(const rb_ast_body_t *ast) +ast_line_count(const VALUE vast) { - if (ast->script_lines == Qfalse) { + rb_ast_t *ast = rb_ruby_ast_data_get(vast); + if (!ast || !ast->body.script_lines) { // this occurs when failed to parse the source code with a syntax error return 0; } - if (RB_TYPE_P(ast->script_lines, T_ARRAY)){ - return (int)RARRAY_LEN(ast->script_lines); + if (!FIXNUM_P((VALUE)ast->body.script_lines)) { + return (int)ast->body.script_lines->len; } - return FIX2INT(ast->script_lines); + return FIX2INT((VALUE)ast->body.script_lines); } static VALUE -iseq_setup_coverage(VALUE coverages, VALUE path, const rb_ast_body_t *ast, int line_offset) +iseq_setup_coverage(VALUE coverages, VALUE path, const VALUE vast, int line_offset) { - int line_count = line_offset + ast_line_count(ast); + int line_count = line_offset + ast_line_count(vast); if (line_count >= 0) { int len = (rb_get_coverage_mode() & COVERAGE_TARGET_ONESHOT_LINES) ? 0 : line_count; @@ -857,22 +879,23 @@ iseq_setup_coverage(VALUE coverages, VALUE path, const rb_ast_body_t *ast, int l } static inline void -iseq_new_setup_coverage(VALUE path, const rb_ast_body_t *ast, int line_offset) +iseq_new_setup_coverage(VALUE path, const VALUE vast, int line_offset) { VALUE coverages = rb_get_coverages(); if (RTEST(coverages)) { - iseq_setup_coverage(coverages, path, ast, line_offset); + iseq_setup_coverage(coverages, path, vast, line_offset); } } rb_iseq_t * -rb_iseq_new_top(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, const rb_iseq_t *parent) +rb_iseq_new_top(const VALUE vast, VALUE name, VALUE path, VALUE realpath, const rb_iseq_t *parent) { - iseq_new_setup_coverage(path, ast, 0); + iseq_new_setup_coverage(path, vast, 0); - return rb_iseq_new_with_opt(ast, name, path, realpath, 0, parent, 0, - ISEQ_TYPE_TOP, &COMPILE_OPTION_DEFAULT); + return rb_iseq_new_with_opt(vast, name, path, realpath, 0, parent, 0, + ISEQ_TYPE_TOP, &COMPILE_OPTION_DEFAULT, + Qnil); } /** @@ -888,13 +911,14 @@ pm_iseq_new_top(pm_scope_node_t *node, VALUE name, VALUE path, VALUE realpath, c } rb_iseq_t * -rb_iseq_new_main(const rb_ast_body_t *ast, VALUE path, VALUE realpath, const rb_iseq_t *parent, int opt) +rb_iseq_new_main(const VALUE vast, VALUE path, VALUE realpath, const rb_iseq_t *parent, int opt) { - iseq_new_setup_coverage(path, ast, 0); + iseq_new_setup_coverage(path, vast, 0); - return rb_iseq_new_with_opt(ast, rb_fstring_lit("
"), + return rb_iseq_new_with_opt(vast, rb_fstring_lit("
"), path, realpath, 0, - parent, 0, ISEQ_TYPE_MAIN, opt ? &COMPILE_OPTION_DEFAULT : &COMPILE_OPTION_FALSE); + parent, 0, ISEQ_TYPE_MAIN, opt ? &COMPILE_OPTION_DEFAULT : &COMPILE_OPTION_FALSE, + Qnil); } /** @@ -912,17 +936,18 @@ pm_iseq_new_main(pm_scope_node_t *node, VALUE path, VALUE realpath, const rb_ise } rb_iseq_t * -rb_iseq_new_eval(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, int first_lineno, const rb_iseq_t *parent, int isolated_depth) +rb_iseq_new_eval(const VALUE vast, VALUE name, VALUE path, VALUE realpath, int first_lineno, const rb_iseq_t *parent, int isolated_depth) { if (rb_get_coverage_mode() & COVERAGE_TARGET_EVAL) { VALUE coverages = rb_get_coverages(); if (RTEST(coverages) && RTEST(path) && !RTEST(rb_hash_has_key(coverages, path))) { - iseq_setup_coverage(coverages, path, ast, first_lineno - 1); + iseq_setup_coverage(coverages, path, vast, first_lineno - 1); } } - return rb_iseq_new_with_opt(ast, name, path, realpath, first_lineno, - parent, isolated_depth, ISEQ_TYPE_EVAL, &COMPILE_OPTION_DEFAULT); + return rb_iseq_new_with_opt(vast, name, path, realpath, first_lineno, + parent, isolated_depth, ISEQ_TYPE_EVAL, &COMPILE_OPTION_DEFAULT, + Qnil); } rb_iseq_t * @@ -948,25 +973,29 @@ iseq_translate(rb_iseq_t *iseq) } rb_iseq_t * -rb_iseq_new_with_opt(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, +rb_iseq_new_with_opt(const VALUE vast, VALUE name, VALUE path, VALUE realpath, int first_lineno, const rb_iseq_t *parent, int isolated_depth, - enum rb_iseq_type type, const rb_compile_option_t *option) + enum rb_iseq_type type, const rb_compile_option_t *option, + VALUE script_lines) { - const NODE *node = ast ? ast->root : 0; + rb_ast_t *ast = rb_ruby_ast_data_get(vast); + rb_ast_body_t *body = ast ? &ast->body : NULL; + const NODE *node = body ? body->root : 0; /* TODO: argument check */ rb_iseq_t *iseq = iseq_alloc(); rb_compile_option_t new_opt; if (!option) option = &COMPILE_OPTION_DEFAULT; - if (ast) { + if (body) { new_opt = *option; - option = set_compile_option_from_ast(&new_opt, ast); + option = set_compile_option_from_ast(&new_opt, body); } - VALUE script_lines = Qnil; - - if (ast && !FIXNUM_P(ast->script_lines) && ast->script_lines) { - script_lines = ast->script_lines; + if (!NIL_P(script_lines)) { + // noop + } + else if (body && !FIXNUM_P((VALUE)body->script_lines) && body->script_lines) { + script_lines = rb_parser_build_script_lines_from(body->script_lines); } else if (parent) { script_lines = ISEQ_BODY(parent)->variable.script_lines; @@ -1000,6 +1029,7 @@ pm_iseq_new_with_opt(pm_scope_node_t *node, VALUE name, VALUE path, VALUE realpa { rb_iseq_t *iseq = iseq_alloc(); ISEQ_BODY(iseq)->prism = true; + ISEQ_BODY(iseq)->param.flags.use_block = true; // unused block warning is not supported yet if (!option) option = &COMPILE_OPTION_DEFAULT; @@ -1144,7 +1174,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; } @@ -1187,9 +1217,10 @@ rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE realpath, VALUE line, V #else # define INITIALIZED /* volatile */ #endif - rb_ast_t *(*parse)(VALUE vparser, VALUE fname, VALUE file, int start); + VALUE (*parse)(VALUE vparser, VALUE fname, VALUE file, int start); int ln; - rb_ast_t *INITIALIZED ast; + VALUE INITIALIZED vast; + rb_ast_t *ast; VALUE name = rb_fstring_lit(""); /* safe results first */ @@ -1205,21 +1236,24 @@ rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE realpath, VALUE line, V } { const VALUE parser = rb_parser_new(); - const rb_iseq_t *outer_scope = rb_iseq_new(NULL, name, name, Qnil, 0, ISEQ_TYPE_TOP); + const rb_iseq_t *outer_scope = rb_iseq_new(Qnil, name, name, Qnil, 0, ISEQ_TYPE_TOP); VALUE outer_scope_v = (VALUE)outer_scope; rb_parser_set_context(parser, outer_scope, FALSE); - rb_parser_set_script_lines(parser, RBOOL(ruby_vm_keep_script_lines)); + if (ruby_vm_keep_script_lines) rb_parser_set_script_lines(parser); RB_GC_GUARD(outer_scope_v); - ast = (*parse)(parser, file, src, ln); + vast = (*parse)(parser, file, src, ln); } - if (!ast->body.root) { + ast = rb_ruby_ast_data_get(vast); + + if (!ast || !ast->body.root) { rb_ast_dispose(ast); rb_exc_raise(GET_EC()->errinfo); } else { - iseq = rb_iseq_new_with_opt(&ast->body, name, file, realpath, ln, - NULL, 0, ISEQ_TYPE_TOP, &option); + iseq = rb_iseq_new_with_opt(vast, name, file, realpath, ln, + NULL, 0, ISEQ_TYPE_TOP, &option, + Qnil); rb_ast_dispose(ast); } @@ -1373,18 +1407,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 }; @@ -1392,14 +1438,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); @@ -1415,6 +1463,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 @@ -1455,26 +1541,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()); } /* @@ -1516,26 +1583,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); } /* @@ -1564,6 +1612,7 @@ iseqw_s_compile_file(int argc, VALUE *argv, VALUE self) VALUE file, opt = Qnil; VALUE parser, f, exc = Qnil, ret; rb_ast_t *ast; + VALUE vast; rb_compile_option_t option; int i; @@ -1582,7 +1631,8 @@ iseqw_s_compile_file(int argc, VALUE *argv, VALUE self) parser = rb_parser_new(); rb_parser_set_context(parser, NULL, FALSE); - ast = (rb_ast_t *)rb_parser_load_file(parser, file); + vast = rb_parser_load_file(parser, file); + ast = rb_ruby_ast_data_get(vast); if (!ast->body.root) exc = GET_EC()->errinfo; rb_io_close(f); @@ -1593,10 +1643,11 @@ iseqw_s_compile_file(int argc, VALUE *argv, VALUE self) make_compile_option(&option, opt); - ret = iseqw_new(rb_iseq_new_with_opt(&ast->body, rb_fstring_lit("
"), + ret = iseqw_new(rb_iseq_new_with_opt(vast, rb_fstring_lit("
"), file, rb_realpath_internal(Qnil, file, 1), - 1, NULL, 0, ISEQ_TYPE_TOP, &option)); + 1, NULL, 0, ISEQ_TYPE_TOP, &option, + Qnil)); rb_ast_dispose(ast); rb_vm_pop_frame(ec); @@ -1723,7 +1774,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); @@ -3159,6 +3212,7 @@ iseq_data_to_ary(const rb_iseq_t *iseq) } if (iseq_body->param.flags.has_kwrest) rb_hash_aset(params, ID2SYM(rb_intern("kwrest")), INT2FIX(keyword->rest_start)); if (iseq_body->param.flags.ambiguous_param0) rb_hash_aset(params, ID2SYM(rb_intern("ambiguous_param0")), Qtrue); + if (iseq_body->param.flags.use_block) rb_hash_aset(params, ID2SYM(rb_intern("use_block")), Qtrue); } /* body */ 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..ed5f940b5744bb 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,26 @@ 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 expands `require "csv"` to `require "#{LIBDIR}/csv.rb"`, + # and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`. + name = feature.delete_prefix(ARCHDIR) + name.delete_prefix!(LIBDIR) + name.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 +138,19 @@ 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. + location = Thread.each_caller_location(2) do |cl| + break cl.path unless cl.base_label == "require" + 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.rb b/lib/bundler.rb index 59a1107bb71d01..5033109db64a7e 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -40,6 +40,7 @@ module Bundler SUDO_MUTEX = Thread::Mutex.new autoload :Checksum, File.expand_path("bundler/checksum", __dir__) + autoload :CLI, File.expand_path("bundler/cli", __dir__) autoload :CIDetector, File.expand_path("bundler/ci_detector", __dir__) autoload :Definition, File.expand_path("bundler/definition", __dir__) autoload :Dependency, File.expand_path("bundler/dependency", __dir__) @@ -165,6 +166,25 @@ def setup(*groups) end end + # Automatically install dependencies if Bundler.settings[:auto_install] exists. + # This is set through config cmd `bundle config set --global auto_install 1`. + # + # Note that this method `nil`s out the global Definition object, so it + # should be called first, before you instantiate anything like an + # `Installer` that'll keep a reference to the old one instead. + def auto_install + return unless settings[:auto_install] + + begin + definition.specs + rescue GemNotFound, GitError + ui.info "Automatically installing missing gems." + reset! + CLI::Install.new({}).run + reset! + end + end + # Setups Bundler environment (see Bundler.setup) if it is not already set, # and loads all gems from groups specified. Unlike ::setup, can be called # multiple times with different groups (if they were allowed by setup). @@ -184,6 +204,7 @@ def setup(*groups) # Bundler.require(:test) # requires second_gem # def require(*groups) + load_plugins setup(*groups).require(*groups) end @@ -560,6 +581,23 @@ def feature_flag @feature_flag ||= FeatureFlag.new(VERSION) end + def load_plugins(definition = Bundler.definition) + return if defined?(@load_plugins_ran) + + Bundler.rubygems.load_plugins + + requested_path_gems = definition.requested_specs.select {|s| s.source.is_a?(Source::Path) } + path_plugin_files = requested_path_gems.map do |spec| + Bundler.rubygems.spec_matches_for_glob(spec, "rubygems_plugin#{Bundler.rubygems.suffix_pattern}") + rescue TypeError + error_message = "#{spec.name} #{spec.version} has an invalid gemspec" + raise Gem::InvalidSpecificationException, error_message + end.flatten + Bundler.rubygems.load_plugin_files(path_plugin_files) + Bundler.rubygems.load_env_plugins + @load_plugins_ran = true + end + def reset! reset_paths! Plugin.reset! diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 36405367620b7b..40f19c7fed1da0 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -5,6 +5,7 @@ module Bundler class CLI < Thor require_relative "cli/common" + require_relative "cli/install" package_name "Bundler" @@ -69,7 +70,7 @@ def initialize(*args) Bundler.settings.set_command_option_if_given :retry, options[:retry] current_cmd = args.last[:current_command].name - auto_install if AUTO_INSTALL_CMDS.include?(current_cmd) + Bundler.auto_install if AUTO_INSTALL_CMDS.include?(current_cmd) rescue UnknownArgumentError => e raise InvalidOption, e.message ensure @@ -114,6 +115,8 @@ def cli_help class_option "verbose", type: :boolean, desc: "Enable verbose output mode", aliases: "-V" def help(cli = nil) + cli = self.class.all_aliases[cli] if self.class.all_aliases[cli] + case cli when "gemfile" then command = "gemfile" when nil then command = "bundle" @@ -347,6 +350,7 @@ def binstubs(*gems) method_option "github", type: :string method_option "branch", type: :string method_option "ref", type: :string + method_option "glob", type: :string, banner: "The location of a dependency's .gemspec, expanded within Ruby (single quotes recommended)" method_option "skip-install", type: :boolean, banner: "Adds gem to the Gemfile but does not install it" method_option "optimistic", type: :boolean, banner: "Adds optimistic declaration of version to gem" method_option "strict", type: :boolean, banner: "Adds strict declaration of version to gem" @@ -682,7 +686,6 @@ def self.reformatted_help_args(args) exec_used = args.index {|a| exec_commands.include? a } command = args.find {|a| bundler_commands.include? a } - command = all_aliases[command] if all_aliases[command] if exec_used && help_used if exec_used + help_used == 1 @@ -735,26 +738,6 @@ def self.deprecated_ext_value?(arguments) private - # Automatically invoke `bundle install` and resume if - # Bundler.settings[:auto_install] exists. This is set through config cmd - # `bundle config set --global auto_install 1`. - # - # Note that this method `nil`s out the global Definition object, so it - # should be called first, before you instantiate anything like an - # `Installer` that'll keep a reference to the old one instead. - def auto_install - return unless Bundler.settings[:auto_install] - - begin - Bundler.definition.specs - rescue GemNotFound, GitError - Bundler.ui.info "Automatically installing missing gems." - Bundler.reset! - invoke :install, [] - Bundler.reset! - end - end - def current_command _, _, config = @_initializer config[:current_command] 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/dependency.rb b/lib/bundler/dependency.rb index 77d7a00362b931..2a4f72fe55a305 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -7,7 +7,7 @@ module Bundler class Dependency < Gem::Dependency attr_reader :autorequire - attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref + attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref, :glob ALL_RUBY_VERSIONS = (18..27).to_a.concat((30..34).to_a).freeze PLATFORM_MAP = { @@ -39,6 +39,7 @@ def initialize(name, version, options = {}, &blk) @github = options["github"] @branch = options["branch"] @ref = options["ref"] + @glob = options["glob"] @platforms = Array(options["platforms"]) @env = options["env"] @should_include = options.fetch("should_include", true) diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb index c4c1b53fa4044c..444ab6fd373d86 100644 --- a/lib/bundler/environment_preserver.rb +++ b/lib/bundler/environment_preserver.rb @@ -19,14 +19,7 @@ class EnvironmentPreserver BUNDLER_PREFIX = "BUNDLER_ORIG_" def self.from_env - new(env_to_hash(ENV), BUNDLER_KEYS) - end - - def self.env_to_hash(env) - to_hash = env.to_hash - return to_hash unless Gem.win_platform? - - to_hash.each_with_object({}) {|(k,v), a| a[k.upcase] = v } + new(ENV.to_hash, BUNDLER_KEYS) end # @param env [Hash] @@ -39,18 +32,7 @@ def initialize(env, keys) # Replaces `ENV` with the bundler environment variables backed up def replace_with_backup - unless Gem.win_platform? - ENV.replace(backup) - return - end - - # Fallback logic for Windows below to workaround - # https://bugs.ruby-lang.org/issues/16798. Can be dropped once all - # supported rubies include the fix for that. - - ENV.clear - - backup.each {|k, v| ENV[k] = v } + ENV.replace(backup) end # @return [Hash] 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/injector.rb b/lib/bundler/injector.rb index cf561c2ee49de3..879b481339281e 100644 --- a/lib/bundler/injector.rb +++ b/lib/bundler/injector.rb @@ -120,9 +120,10 @@ def build_gem_lines(conservative_versioning) github = ", :github => \"#{d.github}\"" unless d.github.nil? branch = ", :branch => \"#{d.branch}\"" unless d.branch.nil? ref = ", :ref => \"#{d.ref}\"" unless d.ref.nil? + glob = ", :glob => \"#{d.glob}\"" unless d.glob.nil? require_path = ", :require => #{convert_autorequire(d.autorequire)}" unless d.autorequire.nil? - %(gem #{name}#{requirement}#{group}#{source}#{path}#{git}#{github}#{branch}#{ref}#{require_path}) + %(gem #{name}#{requirement}#{group}#{source}#{path}#{git}#{github}#{branch}#{ref}#{glob}#{require_path}) end.join("\n") end diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index 018324f8402e8e..72e5602cc31284 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -81,7 +81,7 @@ def run(options) if resolve_if_needed(options) ensure_specs_are_compatible! - load_plugins + Bundler.load_plugins(@definition) options.delete(:jobs) else options[:jobs] = 1 # to avoid the overhead of Bundler::Worker @@ -213,20 +213,6 @@ def installation_parallelization(options) Bundler.settings.processor_count end - def load_plugins - Gem.load_plugins - - requested_path_gems = @definition.requested_specs.select {|s| s.source.is_a?(Source::Path) } - path_plugin_files = requested_path_gems.map do |spec| - Bundler.rubygems.spec_matches_for_glob(spec, "rubygems_plugin#{Bundler.rubygems.suffix_pattern}") - rescue TypeError - error_message = "#{spec.name} #{spec.version} has an invalid gemspec" - raise Gem::InvalidSpecificationException, error_message - end.flatten - Gem.load_plugin_files(path_plugin_files) - Gem.load_env_plugins - end - def ensure_specs_are_compatible! @definition.specs.each do |spec| unless spec.matches_current_ruby? diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index f85d21f9599b83..0b09480f88237f 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" "April 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..fdb86bfd2969a0 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" "April 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..c5899ace1af94f 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" "April 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..08d41b7881eb88 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" "April 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..ca2fb65f59153f 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" "April 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..a78c6b0a18e144 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" "April 2024" "" .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options .SH "SYNOPSIS" @@ -95,8 +95,6 @@ Any periods in the configuration keys must be replaced with two underscores when .SH "LIST OF AVAILABLE KEYS" The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\. .IP "\(bu" 4 -\fBallow_deployment_source_credential_changes\fR (\fBBUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES\fR): When in deployment mode, allow changing the credentials to a gem's source\. Ex: \fBhttps://some\.host\.com/gems/path/\fR \-> \fBhttps://user_name:password@some\.host\.com/gems/path\fR -.IP "\(bu" 4 \fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR): Allow Bundler to use cached data when installing without network access\. .IP "\(bu" 4 \fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR): Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\. diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 587d31dbad22d4..7e5f458fb288a8 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -137,9 +137,6 @@ the environment variable `BUNDLE_LOCAL__RACK`. The following is a list of all configuration keys and their purpose. You can learn more about their operation in [bundle install(1)](bundle-install.1.html). -* `allow_deployment_source_credential_changes` (`BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES`): - When in deployment mode, allow changing the credentials to a gem's source. - Ex: `https://some.host.com/gems/path/` -> `https://user_name:password@some.host.com/gems/path` * `allow_offline_install` (`BUNDLE_ALLOW_OFFLINE_INSTALL`): Allow Bundler to use cached data when installing without network access. * `auto_clean_without_path` (`BUNDLE_AUTO_CLEAN_WITHOUT_PATH`): diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index 467a375f89de92..86be5e41858e0f 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" "April 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..94cbff4cbddbd9 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" "April 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..7aa49e0096c713 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" "April 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..89eb9f1980f6d7 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" "April 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..441ec12735b97b 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" "April 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..287965c06a78c8 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" "April 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..d13f8ddc3be7d3 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" "April 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..086fc88156fc91 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" "April 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..762c99e7c0e021 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" "April 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..93db700091617b 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" "April 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..c0190f91dee7cc 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" "April 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..38bacb67dc3749 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" "April 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..cee9d63155acab 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" "April 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..069966f1b2ef26 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" "April 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..f4e043c363830c 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" "April 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..76a0479bc6f574 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" "April 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..90a664e33112f6 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" "April 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..f9f1fd1fa38f05 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" "April 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..340ebac68779c1 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" "April 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..00ff0153cbc404 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" "April 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..eba3b7df25b0ac 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" "April 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..4bd2bcaf6741f0 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" "April 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..c9478a953e2691 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" "April 2024" "" .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs .SH "SYNOPSIS" diff --git a/lib/bundler/plugin/events.rb b/lib/bundler/plugin/events.rb index bc037d1af507b7..29c05098ae1443 100644 --- a/lib/bundler/plugin/events.rb +++ b/lib/bundler/plugin/events.rb @@ -56,6 +56,30 @@ def self.defined_event?(event) # Includes an Array of Bundler::Dependency objects # GEM_AFTER_INSTALL_ALL = "after-install-all" define :GEM_AFTER_INSTALL_ALL, "after-install-all" + + # @!parse + # A hook called before each individual gem is required + # Includes a Bundler::Dependency. + # GEM_BEFORE_REQUIRE = "before-require" + define :GEM_BEFORE_REQUIRE, "before-require" + + # @!parse + # A hook called after each individual gem is required + # Includes a Bundler::Dependency. + # GEM_AFTER_REQUIRE = "after-require" + define :GEM_AFTER_REQUIRE, "after-require" + + # @!parse + # A hook called before any gems require + # Includes an Array of Bundler::Dependency objects. + # GEM_BEFORE_REQUIRE_ALL = "before-require-all" + define :GEM_BEFORE_REQUIRE_ALL, "before-require-all" + + # @!parse + # A hook called after all gems required + # Includes an Array of Bundler::Dependency objects. + # GEM_AFTER_REQUIRE_ALL = "after-require-all" + define :GEM_AFTER_REQUIRE_ALL, "after-require-all" end end end 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/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index da555681f98aba..494030eab22846 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -156,6 +156,18 @@ def loaded_gem_paths loaded_gem_paths.flatten end + def load_plugins + Gem.load_plugins + end + + def load_plugin_files(plugin_files) + Gem.load_plugin_files(plugin_files) + end + + def load_env_plugins + Gem.load_env_plugins + end + def ui=(obj) Gem::DefaultUserInteraction.ui = obj end diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index ec772cfe7b2e77..54aa30ce0bd0ad 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -41,12 +41,17 @@ def require(*groups) groups.map!(&:to_sym) groups = [:default] if groups.empty? - @definition.dependencies.each do |dep| - # Skip the dependency if it is not in any of the requested groups, or - # not for the current platform, or doesn't match the gem constraints. - next unless (dep.groups & groups).any? && dep.should_include? + dependencies = @definition.dependencies.select do |dep| + # Select the dependency if it is in any of the requested groups, and + # for the current platform, and matches the gem constraints. + (dep.groups & groups).any? && dep.should_include? + end + + Plugin.hook(Plugin::Events::GEM_BEFORE_REQUIRE_ALL, dependencies) + dependencies.each do |dep| required_file = nil + Plugin.hook(Plugin::Events::GEM_BEFORE_REQUIRE, dep) begin # Loop through all the specified autorequires for the @@ -76,7 +81,13 @@ def require(*groups) end end end + + Plugin.hook(Plugin::Events::GEM_AFTER_REQUIRE, dep) end + + Plugin.hook(Plugin::Events::GEM_AFTER_REQUIRE_ALL, dependencies) + + dependencies end def self.definition_method(meth) 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..abbaec978395b1 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -7,7 +7,6 @@ class Settings autoload :Validator, File.expand_path("settings/validator", __dir__) BOOL_KEYS = %w[ - allow_deployment_source_credential_changes allow_offline_install auto_clean_without_path auto_install @@ -492,16 +491,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/setup.rb b/lib/bundler/setup.rb index 7131d150558f66..6010d667428286 100644 --- a/lib/bundler/setup.rb +++ b/lib/bundler/setup.rb @@ -5,6 +5,9 @@ if Bundler::SharedHelpers.in_bundle? require_relative "../bundler" + # try to auto_install first before we get to the `Bundler.ui.silence`, so user knows what is happening + Bundler.auto_install + if STDOUT.tty? || ENV["BUNDLER_FORCE_TTY"] begin Bundler.ui.silence { Bundler.setup } diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 861aa8bbebb687..2e76becb84915a 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -10,14 +10,14 @@ class Rubygems < Source # Ask for X gems per API request API_REQUEST_SIZE = 50 - attr_reader :remotes + attr_accessor :remotes def initialize(options = {}) @options = 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 @@ -96,7 +96,7 @@ def self.from_lock(options) def to_lock out = String.new("GEM\n") remotes.reverse_each do |remote| - out << " remote: #{suppress_configured_credentials remote}\n" + out << " remote: #{remove_auth remote}\n" end out << " specs:\n" end @@ -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 @@ -312,11 +312,7 @@ def remote_names end def credless_remotes - if Bundler.settings[:allow_deployment_source_credential_changes] - remotes.map(&method(:remove_auth)) - else - remotes.map(&method(:suppress_configured_credentials)) - end + remotes.map(&method(:remove_auth)) end def remotes_for_spec(spec) @@ -355,15 +351,6 @@ def normalize_uri(uri) uri end - def suppress_configured_credentials(remote) - remote_nouser = remove_auth(remote) - if remote.userinfo && remote.userinfo == Bundler.settings[remote_nouser] - remote_nouser - else - remote - end - end - def remove_auth(remote) if remote.user || remote.password remote.dup.tap {|uri| uri.user = uri.password = nil }.to_s diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 4419695b7ff1ab..bbaac33a953af9 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 @@ -157,7 +157,11 @@ def dup_with_replaced_sources(replacement_sources) end def map_sources(replacement_sources) - rubygems, git, plugin = [@rubygems_sources, @git_sources, @plugin_sources].map do |sources| + rubygems = @rubygems_sources.map do |source| + replace_rubygems_source(replacement_sources, source) || source + end + + git, plugin = [@git_sources, @plugin_sources].map do |sources| sources.map do |source| replacement_sources.find {|s| s == source } || source end @@ -171,10 +175,19 @@ def map_sources(replacement_sources) end def global_replacement_source(replacement_sources) - replacement_source = replacement_sources.find {|s| s == global_rubygems_source } + replacement_source = replace_rubygems_source(replacement_sources, global_rubygems_source) return global_rubygems_source unless replacement_source - replacement_source.local! + replacement_source.cached! + replacement_source + end + + def replace_rubygems_source(replacement_sources, gemfile_source) + replacement_source = replacement_sources.find {|s| s == gemfile_source } + return unless replacement_source + + # locked sources never include credentials so always prefer remotes from the gemfile + replacement_source.remotes = gemfile_source.remotes replacement_source end diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index 96e1403bf7f1da..2933d284500668 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -65,7 +65,7 @@ def add_extra_platforms!(platforms) platforms.concat(new_platforms) - less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && Bundler.local_platform === platform } + less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && Bundler.local_platform === platform && platform === Bundler.local_platform } platforms.delete(Bundler.local_platform) if less_specific_platform platforms 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..dbb213c90adfd3 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -227,6 +227,12 @@ def to_string return str end + # Returns a string containing the IP address representation in + # cidr notation + def cidr + format("%s/%s", to_s, prefix) + end + # Returns a network byte ordered string form of the IP address. def hton case @family @@ -471,6 +477,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..168595d341558b 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) # @@ -9,7 +10,7 @@ require_relative "irb/init" require_relative "irb/context" -require_relative "irb/command" +require_relative "irb/default_commands" require_relative "irb/ruby-lex" require_relative "irb/statement" @@ -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. +# You can see the hash by typing `IRB.conf`. # -# Details of the entries' meanings are described in the relevant subsections below. +# 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 # -# By default, the first command-line argument (after any options) -# is the path to a Ruby initialization script. +# ### Initialization Script # -# \IRB reads the initialization script and puts its content onto the \IRB shell, +# 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, # 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*`, wheHISTORY_FILEre *n* is one of: # -# You can change the number of commands saved by adding to your configuration file: -# IRB.conf[:SAVE_HISTORY] = _n_, -# where _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. +# During the session, you can use methods `conf.save_history` or +# `conf.save_history=` to retrieve or change the count. # -# === Command Aliases +# ### Command Aliases # -# By default, \IRB defines several command aliases: +# By default, IRB defines several command aliases: # -# irb(main):001> conf.command_aliases -# => {:"$"=>:show_source, :"@"=>:whereami} +# 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. +# +# +# * Giving command-line options: # -# - 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. +# * `--singleline` or `--nomultiline` sets the input method to +# IRB::ReadlineInputMethod. +# * `--nosingleline` or `--multiline` sets the input method to +# IRB::RelineInputMethod. # -# - Giving command-line options: # -# - --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: +# 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. +# * `true` if option `--multiline` was given. +# * `false` if option `--nomultiline` was given. +# * `nil` if neither was given. # -# \Method conf.use_singleline? -# and its synonym conf.use_readline return: # -# - +true+ if option --singleline was given. -# - +false+ if option --nosingleline was given. -# - +nil+ if neither was given. +# Method `conf.use_singleline?` and its synonym `conf.use_readline` return: # -# == Output +# * `true` if option `--singleline` was given. +# * `false` if option `--nosingleline` was given. +# * `nil` if neither was given. # -# 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) +# ## Output # -# By default, \IRB prints (echoes) the values returned by all input commands. +# 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 # -# irb(main):001> x = 'abc' * 100 -# => "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc... # -# - Printed in full (regardless of the length). -# - Suppressed (not printed at all) +# > "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc... +# +# * 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+). +# 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+. +# 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=. # -# === Multiline Output +# During the session, you can change the setting using method +# `conf.inspect_mode=`. # -# By default, \IRB prefixes a newline to a multiline response. +# ### Multiline Output +# +# 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,268 @@ # 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 # -# A prompt's definition can include specifiers for which certain values are substituted: +# #### Prompt Specifiers # -# - %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 %. +# A prompt's definition can include specifiers for which certain values are +# substituted: # -# === Verbosity +# * `%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. NN is a 2-digit number that specifies the number +# of digits of the indentation level (03 will result in 001). +# * `%NNn`: Line number. NN is a 2-digit number that specifies the number +# of digits of the line number (03 will result in 001). +# * `%%`: Literal `%`. # -# 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 +844,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 +879,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 +902,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 +931,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,28 +952,36 @@ 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. - 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 - context.io.reset_history_counter + # + # Irb#eval_input will simply return the input, and we need to pass it to the + # debugger. + input = nil + forced_exit = catch(:IRB_EXIT) do + 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 + context.io.reset_history_counter - begin - eval_input - ensure - context.io.save_history + begin + input = eval_input + ensure + context.io.save_history + end + else + input = eval_input end - else - eval_input + false end + Kernel.exit if forced_exit + if input&.include?("\n") @line_no += input.count("\n") - 1 end @@ -1004,12 +1030,13 @@ 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) + @context.evaluate(statement, line_no) if @context.echo? && !statement.suppresses_echo? if statement.is_assignment? @@ -1065,9 +1092,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 +1119,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 = Command.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 Command.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 +1166,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 +1180,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 +1238,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 +1269,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 +1291,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 +1306,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 +1318,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 +1354,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 +1404,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 @@ -1422,7 +1463,7 @@ def truncate_prompt_main(str) # :nodoc: end def format_prompt(format, ltype, indent, line_no) # :nodoc: - format.gsub(/%([0-9]+)?([a-zA-Z])/) do + format.gsub(/%([0-9]+)?([a-zA-Z%])/) do case $2 when "N" @context.irb_name @@ -1455,7 +1496,7 @@ def format_prompt(format, ltype, indent, line_no) # :nodoc: line_no.to_s end when "%" - "%" + "%" unless $1 end end end @@ -1463,12 +1504,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 +1520,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 +1551,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 +1575,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..f4dd3b2b483d45 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -7,312 +7,17 @@ require_relative "command/base" module IRB # :nodoc: - module Command; end - ExtendCommand = Command + module Command + @commands = {} - # Installs the default irb extensions command bundle. - module ExtendCommandBundle - EXCB = ExtendCommandBundle # :nodoc: + class << self + attr_reader :commands - # See #install_alias_method. - 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_exit, :Exit, "command/exit", - [:exit, OVERRIDE_PRIVATE_ONLY], - [:quit, OVERRIDE_PRIVATE_ONLY], - [:irb_quit, OVERRIDE_PRIVATE_ONLY], - ], - [ - :irb_exit!, :ForceExit, "command/force_exit", - [:exit!, OVERRIDE_PRIVATE_ONLY], - ], - - [ - :irb_current_working_workspace, :CurrentWorkingWorkspace, "command/chws", - [:cwws, NO_OVERRIDE], - [:pwws, NO_OVERRIDE], - [:irb_print_working_workspace, OVERRIDE_ALL], - [:irb_cwws, OVERRIDE_ALL], - [:irb_pwws, OVERRIDE_ALL], - [:irb_current_working_binding, OVERRIDE_ALL], - [:irb_print_working_binding, OVERRIDE_ALL], - [:irb_cwb, OVERRIDE_ALL], - [:irb_pwb, OVERRIDE_ALL], - ], - [ - :irb_change_workspace, :ChangeWorkspace, "command/chws", - [:chws, NO_OVERRIDE], - [:cws, NO_OVERRIDE], - [:irb_chws, OVERRIDE_ALL], - [:irb_cws, OVERRIDE_ALL], - [:irb_change_binding, OVERRIDE_ALL], - [:irb_cb, OVERRIDE_ALL], - [:cb, NO_OVERRIDE], - ], - - [ - :irb_workspaces, :Workspaces, "command/pushws", - [:workspaces, NO_OVERRIDE], - [:irb_bindings, OVERRIDE_ALL], - [:bindings, NO_OVERRIDE], - ], - [ - :irb_push_workspace, :PushWorkspace, "command/pushws", - [:pushws, NO_OVERRIDE], - [:irb_pushws, OVERRIDE_ALL], - [:irb_push_binding, OVERRIDE_ALL], - [:irb_pushb, OVERRIDE_ALL], - [:pushb, NO_OVERRIDE], - ], - [ - :irb_pop_workspace, :PopWorkspace, "command/pushws", - [:popws, NO_OVERRIDE], - [:irb_popws, OVERRIDE_ALL], - [:irb_pop_binding, OVERRIDE_ALL], - [:irb_popb, OVERRIDE_ALL], - [:popb, NO_OVERRIDE], - ], - - [ - :irb_load, :Load, "command/load"], - [ - :irb_require, :Require, "command/load"], - [ - :irb_source, :Source, "command/load", - [:source, NO_OVERRIDE], - ], - - [ - :irb, :IrbCommand, "command/subirb"], - [ - :irb_jobs, :Jobs, "command/subirb", - [:jobs, NO_OVERRIDE], - ], - [ - :irb_fg, :Foreground, "command/subirb", - [:fg, NO_OVERRIDE], - ], - [ - :irb_kill, :Kill, "command/subirb", - [:kill, OVERRIDE_PRIVATE_ONLY], - ], - - [ - :irb_debug, :Debug, "command/debug", - [:debug, NO_OVERRIDE], - ], - [ - :irb_edit, :Edit, "command/edit", - [:edit, NO_OVERRIDE], - ], - [ - :irb_break, :Break, "command/break", - ], - [ - :irb_catch, :Catch, "command/catch", - ], - [ - :irb_next, :Next, "command/next" - ], - [ - :irb_delete, :Delete, "command/delete", - [:delete, NO_OVERRIDE], - ], - [ - :irb_step, :Step, "command/step", - [:step, NO_OVERRIDE], - ], - [ - :irb_continue, :Continue, "command/continue", - [:continue, NO_OVERRIDE], - ], - [ - :irb_finish, :Finish, "command/finish", - [:finish, NO_OVERRIDE], - ], - [ - :irb_backtrace, :Backtrace, "command/backtrace", - [:backtrace, NO_OVERRIDE], - [:bt, NO_OVERRIDE], - ], - [ - :irb_debug_info, :Info, "command/info", - [:info, NO_OVERRIDE], - ], - - [ - :irb_help, :Help, "command/help", - [:help, NO_OVERRIDE], - [:show_cmds, NO_OVERRIDE], - ], - - [ - :irb_show_doc, :ShowDoc, "command/show_doc", - [:show_doc, NO_OVERRIDE], - ], - - [ - :irb_info, :IrbInfo, "command/irb_info" - ], - - [ - :irb_ls, :Ls, "command/ls", - [:ls, NO_OVERRIDE], - ], - - [ - :irb_measure, :Measure, "command/measure", - [:measure, NO_OVERRIDE], - ], - - [ - :irb_show_source, :ShowSource, "command/show_source", - [:show_source, NO_OVERRIDE], - ], - [ - :irb_whereami, :Whereami, "command/whereami", - [:whereami, NO_OVERRIDE], - ], - [ - :irb_history, :History, "command/history", - [:history, NO_OVERRIDE], - [:hist, NO_OVERRIDE], - ] - ] - - - @@commands = [] - - def self.all_commands_info - return @@commands unless @@commands.empty? - user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| - result[target] ||= [] - result[target] << alias_name - end - - @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| - if !defined?(Command) || !Command.const_defined?(cmd_class, false) - require_relative load_file - end - - klass = Command.const_get(cmd_class, false) - aliases = aliases.map { |a| a.first } - - if additional_aliases = user_aliases[cmd_name] - aliases += additional_aliases - end - - display_name = aliases.shift || cmd_name - @@commands << { display_name: display_name, description: klass.description, category: klass.category } - end - - @@commands - end - - # Convert a command name to its implementation class if such command exists - def self.load_command(command) - command = command.to_sym - @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| - next if cmd_name != command && aliases.all? { |alias_name, _| alias_name != command } - - if !defined?(Command) || !Command.const_defined?(cmd_class, false) - require_relative load_file - end - return Command.const_get(cmd_class, false) - end - 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 - - # 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" + # Registers a command with the given name. + # Aliasing is intentionally not supported at the moment. + def register(name, command_class) + @commands[name] = [command_class, []] end 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..b078b482375711 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,7 @@ def initialize(irb_context) attr_reader :irb_context - def execute(*opts) + 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..cb7e0c4873b3a3 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -1,5 +1,6 @@ require 'shellwords' +require_relative "../color" require_relative "../source_finder" module IRB @@ -7,6 +8,8 @@ module IRB module Command class Edit < Base + include RubyArgsExtractor + category "Misc" description 'Open a file or source location.' help_message <<~HELP_MESSAGE @@ -26,19 +29,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/exit.rb b/lib/irb/command/exit.rb index 3109ec16e3a44c..b4436f0343710d 100644 --- a/lib/irb/command/exit.rb +++ b/lib/irb/command/exit.rb @@ -8,10 +8,8 @@ class Exit < Base category "IRB" description "Exit the current irb session." - def execute(*) + def execute(_arg) IRB.irb_exit - rescue UncaughtThrowError - Kernel.exit end end end 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/force_exit.rb b/lib/irb/command/force_exit.rb index c2c5542e2490f7..14086aa849b67d 100644 --- a/lib/irb/command/force_exit.rb +++ b/lib/irb/command/force_exit.rb @@ -8,10 +8,8 @@ class ForceExit < Base category "IRB" description "Exit the current process." - def execute(*) + def execute(_arg) throw :IRB_EXIT, true - rescue UncaughtThrowError - Kernel.exit! end end end diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index 19113dbbfed843..1ed7a7707c0816 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_class = ExtendCommandBundle.load_command(command_name) + if command_name.empty? + help_message + else + if command_class = Command.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 @@ -34,8 +23,10 @@ def execute(command_name = nil) private def help_message - commands_info = IRB::ExtendCommandBundle.all_commands_info + commands_info = IRB::Command.all_commands_info + helper_methods_info = IRB::HelperMethod.all_helper_methods_info commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] } + commands_grouped_by_categories["Helper methods"] = helper_methods_info user_aliases = irb_context.instance_variable_get(:@user_aliases) 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/internal_helpers.rb b/lib/irb/command/internal_helpers.rb new file mode 100644 index 00000000000000..249b5cdedeb080 --- /dev/null +++ b/lib/irb/command/internal_helpers.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module IRB + module Command + # Internal use only, for default command's backward compatibility. + module RubyArgsExtractor # :nodoc: + 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 + 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..1cd3f279d1003e 100644 --- a/lib/irb/command/load.rb +++ b/lib/irb/command/load.rb @@ -10,6 +10,7 @@ module IRB module Command class LoaderCommand < Base + include RubyArgsExtractor include IrbLoader def raise_cmd_argument_error @@ -21,7 +22,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 +36,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 +75,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..cbd9998bc4e601 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -11,6 +11,8 @@ module IRB module Command class Ls < Base + include RubyArgsExtractor + category "Context" description "Show methods, constants, and variables." @@ -20,27 +22,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..f96be20de86bbd 100644 --- a/lib/irb/command/measure.rb +++ b/lib/irb/command/measure.rb @@ -3,6 +3,8 @@ module IRB module Command class Measure < Base + include RubyArgsExtractor + category "Misc" description "`measure` enables the mode to measure processing time. `measure :off` disables it." @@ -10,15 +12,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..8a2188e4ebc365 100644 --- a/lib/irb/command/show_doc.rb +++ b/lib/irb/command/show_doc.rb @@ -3,16 +3,7 @@ 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 + include RubyArgsExtractor category "Context" description "Look up documentation with RI." @@ -31,7 +22,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 +32,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..f4c6f104a2d577 100644 --- a/lib/irb/command/show_source.rb +++ b/lib/irb/command/show_source.rb @@ -7,6 +7,8 @@ module IRB module Command class ShowSource < Base + include RubyArgsExtractor + category "Context" description "Show the source code of a given method, class/module, or constant." @@ -24,18 +26,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..138d61c9307228 100644 --- a/lib/irb/command/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -9,9 +9,7 @@ module IRB module Command class MultiIRBCommand < Base - def execute(*args) - extend_irb_context - end + include RubyArgsExtractor private @@ -36,7 +34,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 +47,7 @@ def execute(*obj) return end - super + extend_irb_context IRB.irb(nil, *obj) end end @@ -53,7 +56,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 +64,7 @@ def execute return end - super + extend_irb_context IRB.JobManager end end @@ -70,7 +73,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 +86,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 +97,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 +110,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..a3d89373c36820 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::Command.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..836b8d26257473 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -585,31 +585,44 @@ def inspect_mode=(opt) @inspect_mode end - def evaluate(line, line_no) # :nodoc: + def evaluate(statement, line_no) # :nodoc: @line_no = line_no result = nil + case statement + when Statement::EmptyInput + return + when Statement::Expression + result = evaluate_expression(statement.code, line_no) + when Statement::Command + result = statement.command_class.execute(self, statement.arg) + end + + set_last_value(result) + end + + def evaluate_expression(code, line_no) # :nodoc: + result = nil if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? IRB.set_measure_callback end if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty? last_proc = proc do - result = workspace.evaluate(line, @eval_path, line_no) + result = workspace.evaluate(code, @eval_path, line_no) end IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item| _name, callback, arg = item proc do - callback.(self, line, line_no, arg) do + callback.(self, code, line_no, arg) do chain.call end end end.call else - result = workspace.evaluate(line, @eval_path, line_no) + result = workspace.evaluate(code, @eval_path, line_no) end - - set_last_value(result) + result end def inspect_last_value # :nodoc: @@ -646,17 +659,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/default_commands.rb b/lib/irb/default_commands.rb new file mode 100644 index 00000000000000..2c515674afe7c3 --- /dev/null +++ b/lib/irb/default_commands.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +require_relative "command" +require_relative "command/internal_helpers" +require_relative "command/context" +require_relative "command/exit" +require_relative "command/force_exit" +require_relative "command/chws" +require_relative "command/pushws" +require_relative "command/subirb" +require_relative "command/load" +require_relative "command/debug" +require_relative "command/edit" +require_relative "command/break" +require_relative "command/catch" +require_relative "command/next" +require_relative "command/delete" +require_relative "command/step" +require_relative "command/continue" +require_relative "command/finish" +require_relative "command/backtrace" +require_relative "command/info" +require_relative "command/help" +require_relative "command/show_doc" +require_relative "command/irb_info" +require_relative "command/ls" +require_relative "command/measure" +require_relative "command/show_source" +require_relative "command/whereami" +require_relative "command/history" + +module IRB + module Command + NO_OVERRIDE = 0 + OVERRIDE_PRIVATE_ONLY = 0x01 + OVERRIDE_ALL = 0x02 + + class << self + # This API is for IRB's internal use only and may change at any time. + # Please do NOT use it. + def _register_with_aliases(name, command_class, *aliases) + @commands[name] = [command_class, aliases] + end + + def all_commands_info + user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| + result[target] ||= [] + result[target] << alias_name + end + + commands.map do |command_name, (command_class, aliases)| + aliases = aliases.map { |a| a.first } + + if additional_aliases = user_aliases[command_name] + aliases += additional_aliases + end + + display_name = aliases.shift || command_name + { + display_name: display_name, + description: command_class.description, + category: command_class.category + } + end + end + + def command_override_policies + @@command_override_policies ||= commands.flat_map do |cmd_name, (cmd_class, aliases)| + [[cmd_name, OVERRIDE_ALL]] + aliases + end.to_h + end + + def 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 command_names + command_override_policies.keys.map(&:to_s) + end + + # Convert a command name to its implementation class if such command exists + def load_command(command) + command = command.to_sym + commands.each do |command_name, (command_class, aliases)| + if command_name == command || aliases.any? { |alias_name, _| alias_name == command } + return command_class + end + end + nil + end + end + + _register_with_aliases(:irb_context, Command::Context, + [:context, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_exit, Command::Exit, + [:exit, OVERRIDE_PRIVATE_ONLY], + [:quit, OVERRIDE_PRIVATE_ONLY], + [:irb_quit, OVERRIDE_PRIVATE_ONLY] + ) + + _register_with_aliases(:irb_exit!, Command::ForceExit, + [:exit!, OVERRIDE_PRIVATE_ONLY] + ) + + _register_with_aliases(:irb_current_working_workspace, Command::CurrentWorkingWorkspace, + [:cwws, NO_OVERRIDE], + [:pwws, NO_OVERRIDE], + [:irb_print_working_workspace, OVERRIDE_ALL], + [:irb_cwws, OVERRIDE_ALL], + [:irb_pwws, OVERRIDE_ALL], + [:irb_current_working_binding, OVERRIDE_ALL], + [:irb_print_working_binding, OVERRIDE_ALL], + [:irb_cwb, OVERRIDE_ALL], + [:irb_pwb, OVERRIDE_ALL], + ) + + _register_with_aliases(:irb_change_workspace, Command::ChangeWorkspace, + [:chws, NO_OVERRIDE], + [:cws, NO_OVERRIDE], + [:irb_chws, OVERRIDE_ALL], + [:irb_cws, OVERRIDE_ALL], + [:irb_change_binding, OVERRIDE_ALL], + [:irb_cb, OVERRIDE_ALL], + [:cb, NO_OVERRIDE], + ) + + _register_with_aliases(:irb_workspaces, Command::Workspaces, + [:workspaces, NO_OVERRIDE], + [:irb_bindings, OVERRIDE_ALL], + [:bindings, NO_OVERRIDE], + ) + + _register_with_aliases(:irb_push_workspace, Command::PushWorkspace, + [:pushws, NO_OVERRIDE], + [:irb_pushws, OVERRIDE_ALL], + [:irb_push_binding, OVERRIDE_ALL], + [:irb_pushb, OVERRIDE_ALL], + [:pushb, NO_OVERRIDE], + ) + + _register_with_aliases(:irb_pop_workspace, Command::PopWorkspace, + [:popws, NO_OVERRIDE], + [:irb_popws, OVERRIDE_ALL], + [:irb_pop_binding, OVERRIDE_ALL], + [:irb_popb, OVERRIDE_ALL], + [:popb, NO_OVERRIDE], + ) + + _register_with_aliases(:irb_load, Command::Load) + _register_with_aliases(:irb_require, Command::Require) + _register_with_aliases(:irb_source, Command::Source, + [:source, NO_OVERRIDE] + ) + + _register_with_aliases(:irb, Command::IrbCommand) + _register_with_aliases(:irb_jobs, Command::Jobs, + [:jobs, NO_OVERRIDE] + ) + _register_with_aliases(:irb_fg, Command::Foreground, + [:fg, NO_OVERRIDE] + ) + _register_with_aliases(:irb_kill, Command::Kill, + [:kill, OVERRIDE_PRIVATE_ONLY] + ) + + _register_with_aliases(:irb_debug, Command::Debug, + [:debug, NO_OVERRIDE] + ) + _register_with_aliases(:irb_edit, Command::Edit, + [:edit, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_break, Command::Break) + _register_with_aliases(:irb_catch, Command::Catch) + _register_with_aliases(:irb_next, Command::Next) + _register_with_aliases(:irb_delete, Command::Delete, + [:delete, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_step, Command::Step, + [:step, NO_OVERRIDE] + ) + _register_with_aliases(:irb_continue, Command::Continue, + [:continue, NO_OVERRIDE] + ) + _register_with_aliases(:irb_finish, Command::Finish, + [:finish, NO_OVERRIDE] + ) + _register_with_aliases(:irb_backtrace, Command::Backtrace, + [:backtrace, NO_OVERRIDE], + [:bt, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_debug_info, Command::Info, + [:info, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_help, Command::Help, + [:help, NO_OVERRIDE], + [:show_cmds, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_show_doc, Command::ShowDoc, + [:show_doc, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_info, Command::IrbInfo) + + _register_with_aliases(:irb_ls, Command::Ls, + [:ls, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_measure, Command::Measure, + [:measure, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_show_source, Command::ShowSource, + [:show_source, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_whereami, Command::Whereami, + [:whereami, NO_OVERRIDE] + ) + + _register_with_aliases(:irb_history, Command::History, + [:history, NO_OVERRIDE], + [:hist, NO_OVERRIDE] + ) + end + + ExtendCommand = Command + + # For backward compatibility, we need to keep this module: + # - As a container of helper methods + # - As a place to register commands with the deprecated def_extend_command method + module ExtendCommandBundle + # For backward compatibility + NO_OVERRIDE = Command::NO_OVERRIDE + OVERRIDE_PRIVATE_ONLY = Command::OVERRIDE_PRIVATE_ONLY + OVERRIDE_ALL = Command::OVERRIDE_ALL + + # Deprecated. Doesn't have any effect. + @EXTEND_COMMANDS = [] + + # Drepcated. Use Command.regiser instead. + def self.def_extend_command(cmd_name, cmd_class, _, *aliases) + Command._register_with_aliases(cmd_name, cmd_class, *aliases) + Command.class_variable_set(:@@command_override_policies, nil) + 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/irb.gemspec b/lib/irb/irb.gemspec index 6ed327a27348f2..b29002f5931df7 100644 --- a/lib/irb/irb.gemspec +++ b/lib/irb/irb.gemspec @@ -42,5 +42,5 @@ Gem::Specification.new do |spec| spec.required_ruby_version = Gem::Requirement.new(">= 2.7") spec.add_dependency "reline", ">= 0.4.2" - spec.add_dependency "rdoc" + spec.add_dependency "rdoc", ">= 4.0.0" end 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/version.rb b/lib/irb/version.rb index d9753d3eb63a8a..9a7b12766bd67e 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -5,7 +5,7 @@ # module IRB # :nodoc: - VERSION = "1.11.2" + VERSION = "1.12.0" @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-02-07" + @LAST_UPDATE_DATE = "2024-03-06" end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 4c3b5e425e1183..d24d1cc38db232 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -6,6 +6,8 @@ require "delegate" +require_relative "helper_method" + IRB::TOPLEVEL_BINDING = binding module IRB # :nodoc: class WorkSpace @@ -108,8 +110,10 @@ def initialize(*main) # IRB.conf[:__MAIN__] attr_reader :main - def load_commands_to_main - main.extend ExtendCommandBundle + def load_helper_methods_to_main + ancestors = class< ParseResult + # Prism::lex_compat(source, **options) -> LexCompat::Result # # Returns a parse result whose value is an array of tokens that closely # resembles the return value of Ripper::lex. The main difference is that the @@ -64,22 +65,6 @@ def self.lex_ripper(source) def self.load(source, serialized) Serialize.load(source, serialized) end - - # :call-seq: - # Prism::parse_failure?(source, **options) -> bool - # - # Returns true if the source parses with errors. - def self.parse_failure?(source, **options) - !parse_success?(source, **options) - end - - # :call-seq: - # Prism::parse_file_failure?(filepath, **options) -> bool - # - # Returns true if the file at filepath parses with errors. - def self.parse_file_failure?(filepath, **options) - !parse_file_success?(filepath, **options) - end end require_relative "prism/node" diff --git a/lib/prism/desugar_compiler.rb b/lib/prism/desugar_compiler.rb index 8d059b0c989bcf..9b62c00df32213 100644 --- a/lib/prism/desugar_compiler.rb +++ b/lib/prism/desugar_compiler.rb @@ -73,6 +73,8 @@ def initialize(node, source, read_class, write_class, *arguments) # Desugar `x += y` to `x = x + y` def compile + operator_loc = node.operator_loc.chop + write_class.new( source, *arguments, @@ -82,8 +84,8 @@ def compile 0, read_class.new(source, *arguments, node.name_loc), nil, - node.operator_loc.slice.chomp("=").to_sym, - node.operator_loc.copy(length: node.operator_loc.length - 1), + operator_loc.slice.to_sym, + operator_loc, nil, ArgumentsNode.new(source, 0, [node.value], node.value.location), nil, diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 2aecd4df863f48..2014ccea31cd69 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -23,15 +23,21 @@ module LibRubyParser # :nodoc: # size_t -> :size_t # void -> :void # - def self.resolve_type(type) + def self.resolve_type(type, callbacks) type = type.strip - type.end_with?("*") ? :pointer : type.delete_prefix("const ").to_sym + + if !type.end_with?("*") + type.delete_prefix("const ").to_sym + else + type = type.delete_suffix("*").rstrip + callbacks.include?(type.to_sym) ? type.to_sym : :pointer + end end # Read through the given header file and find the declaration of each of the # given functions. For each one, define a function with the same name and # signature as the C function. - def self.load_exported_functions_from(header, *functions) + def self.load_exported_functions_from(header, *functions, callbacks) File.foreach(File.expand_path("../../include/#{header}", __dir__)) do |line| # We only want to attempt to load exported functions. next unless line.start_with?("PRISM_EXPORTED_FUNCTION ") @@ -55,24 +61,28 @@ def self.load_exported_functions_from(header, *functions) # Resolve the type of the argument by dropping the name of the argument # first if it is present. - arg_types.map! { |type| resolve_type(type.sub(/\w+$/, "")) } + arg_types.map! { |type| resolve_type(type.sub(/\w+$/, ""), callbacks) } # Attach the function using the FFI library. - attach_function name, arg_types, resolve_type(return_type) + attach_function name, arg_types, resolve_type(return_type, []) end # If we didn't find all of the functions, raise an error. raise "Could not find functions #{functions.inspect}" unless functions.empty? end + callback :pm_parse_stream_fgets_t, [:pointer, :int, :pointer], :pointer + load_exported_functions_from( "prism.h", "pm_version", "pm_serialize_parse", + "pm_serialize_parse_stream", "pm_serialize_parse_comments", "pm_serialize_lex", "pm_serialize_parse_lex", - "pm_parse_success_p" + "pm_parse_success_p", + [:pm_parse_stream_fgets_t] ) load_exported_functions_from( @@ -81,7 +91,8 @@ def self.load_exported_functions_from(header, *functions) "pm_buffer_init", "pm_buffer_value", "pm_buffer_length", - "pm_buffer_free" + "pm_buffer_free", + [] ) load_exported_functions_from( @@ -90,7 +101,8 @@ def self.load_exported_functions_from(header, *functions) "pm_string_free", "pm_string_source", "pm_string_length", - "pm_string_sizeof" + "pm_string_sizeof", + [] ) # This object represents a pm_buffer_t. We only use it as an opaque pointer, @@ -215,13 +227,36 @@ def parse(code, **options) end # Mirror the Prism.parse_file API by using the serialization API. This uses - # native strings instead of Ruby strings because it allows us to use mmap when - # it is available. + # native strings instead of Ruby strings because it allows us to use mmap + # when it is available. def parse_file(filepath, **options) options[:filepath] = filepath LibRubyParser::PrismString.with_file(filepath) { |string| parse_common(string, string.read, options) } end + # Mirror the Prism.parse_stream API by using the serialization API. + def parse_stream(stream, **options) + LibRubyParser::PrismBuffer.with do |buffer| + source = +"" + callback = -> (string, size, _) { + raise "Expected size to be >= 0, got: #{size}" if size <= 0 + + if !(line = stream.gets(size - 1)).nil? + source << line + string.write_string("#{line}\x00", line.bytesize + 1) + end + } + + # In the pm_serialize_parse_stream function it accepts a pointer to the + # IO object as a void* and then passes it through to the callback as the + # third argument, but it never touches it itself. As such, since we have + # access to the IO object already through the closure of the lambda, we + # can pass a null pointer here and not worry. + LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, dump_options(options)) + Prism.load(source, buffer.read) + end + end + # Mirror the Prism.parse_comments API by using the serialization API. def parse_comments(code, **options) LibRubyParser::PrismString.with_string(code) { |string| parse_comments_common(string, code, options) } @@ -251,12 +286,22 @@ def parse_success?(code, **options) LibRubyParser::PrismString.with_string(code) { |string| parse_file_success_common(string, options) } end + # Mirror the Prism.parse_failure? API by using the serialization API. + def parse_failure?(code, **options) + !parse_success?(code, **options) + end + # Mirror the Prism.parse_file_success? API by using the serialization API. def parse_file_success?(filepath, **options) options[:filepath] = filepath LibRubyParser::PrismString.with_file(filepath) { |string| parse_file_success_common(string, options) } end + # Mirror the Prism.parse_file_failure? API by using the serialization API. + def parse_file_failure?(filepath, **options) + !parse_file_success?(filepath, **options) + end + private def dump_common(string, options) # :nodoc: @@ -305,7 +350,7 @@ def parse_lex_common(string, code, options) # :nodoc: node, comments, magic_comments, data_loc, errors, warnings = loader.load_nodes tokens.each { |token,| token.value.force_encoding(loader.encoding) } - ParseResult.new([node, tokens], comments, magic_comments, data_loc, errors, warnings, source) + ParseLexResult.new([node, tokens], comments, magic_comments, data_loc, errors, warnings, source) end end diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index 70cb0652018239..f199af1883c003 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -10,6 +10,23 @@ module Prism # generally lines up. However, there are a few cases that require special # handling. class LexCompat # :nodoc: + # A result class specialized for holding tokens produced by the lexer. + class Result < Prism::Result + # The list of tokens that were produced by the lexer. + attr_reader :value + + # Create a new lex compat result object with the given values. + def initialize(value, comments, magic_comments, data_loc, errors, warnings, source) + @value = value + super(comments, magic_comments, data_loc, errors, warnings, source) + end + + # Implement the hash pattern matching interface for Result. + def deconstruct_keys(keys) + super.merge!(value: value) + end + end + # This is a mapping of prism token types to Ripper token types. This is a # many-to-one mapping because we split up our token types, whereas Ripper # tends to group them. @@ -844,7 +861,7 @@ def result # We sort by location to compare against Ripper's output tokens.sort_by!(&:location) - ParseResult.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, Source.new(source)) + Result.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, Source.new(source)) end end diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb index 4ec7c3014cb464..86745440659680 100644 --- a/lib/prism/node_ext.rb +++ b/lib/prism/node_ext.rb @@ -55,6 +55,7 @@ class StringNode < Node def to_interpolated InterpolatedStringNode.new( source, + frozen? ? InterpolatedStringNodeFlags::FROZEN : 0, opening_loc, [copy(opening_loc: nil, closing_loc: nil, location: content_loc)], closing_loc, diff --git a/lib/prism/node_inspector.rb b/lib/prism/node_inspector.rb deleted file mode 100644 index d77af33c3a611a..00000000000000 --- a/lib/prism/node_inspector.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -module Prism - # This object is responsible for generating the output for the inspect method - # implementations of child nodes. - class NodeInspector # :nodoc: - attr_reader :prefix, :output - - def initialize(prefix = "") - @prefix = prefix - @output = +"" - end - - # Appends a line to the output with the current prefix. - def <<(line) - output << "#{prefix}#{line}" - end - - # This generates a string that is used as the header of the inspect output - # for any given node. - def header(node) - output = +"@ #{node.class.name.split("::").last} (" - output << "location: (#{node.location.start_line},#{node.location.start_column})-(#{node.location.end_line},#{node.location.end_column})" - output << ", newline: true" if node.newline? - output << ")\n" - output - end - - # Generates a string that represents a list of nodes. It handles properly - # using the box drawing characters to make the output look nice. - def list(prefix, nodes) - output = +"(length: #{nodes.length})\n" - last_index = nodes.length - 1 - - nodes.each_with_index do |node, index| - pointer, preadd = (index == last_index) ? ["└── ", " "] : ["├── ", "│ "] - node_prefix = "#{prefix}#{preadd}" - output << node.inspect(NodeInspector.new(node_prefix)).sub(node_prefix, "#{prefix}#{pointer}") - end - - output - end - - # Generates a string that represents a location field on a node. - def location(value) - if value - "(#{value.start_line},#{value.start_column})-(#{value.end_line},#{value.end_column}) = #{value.slice.inspect}" - else - "∅" - end - end - - # Generates a string that represents a child node. - def child_node(node, append) - node.inspect(child_inspector(append)).delete_prefix(prefix) - end - - # Returns a new inspector that can be used to inspect a child node. - def child_inspector(append) - NodeInspector.new("#{prefix}#{append}") - end - - # Returns the output as a string. - def to_str - output - end - end -end diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index 102567f59c82d5..2207a44fe52145 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -87,9 +87,9 @@ def find_line(byte_offset) while left <= right mid = left + (right - left) / 2 - return mid if offsets[mid] == byte_offset + return mid if (offset = offsets[mid]) == byte_offset - if offsets[mid] < byte_offset + if offset < byte_offset left = mid + 1 else right = mid - 1 @@ -161,6 +161,11 @@ def copy(source: self.source, start_offset: self.start_offset, length: self.leng Location.new(source, start_offset, length) end + # Returns a new location that is the result of chopping off the last byte. + def chop + copy(length: length == 0 ? length : length - 1) + end + # Returns a string representation of this location. def inspect "#" @@ -262,7 +267,7 @@ def pretty_print(q) # Returns true if the given other location is equal to this location. def ==(other) - other.is_a?(Location) && + Location === other && other.start_offset == start_offset && other.end_offset == end_offset end @@ -276,13 +281,6 @@ def join(other) Location.new(source, start_offset, other.end_offset - start_offset) end - - # Returns a null location that does not correspond to a source and points to - # the beginning of the file. Useful for when you want a location object but - # do not care where it points. - def self.null - new(nil, 0, 0) # steep:ignore - end end # This represents a comment that was encountered during parsing. It is the @@ -373,6 +371,10 @@ def inspect # This represents an error that was encountered during parsing. class ParseError + # The type of error. This is an _internal_ symbol that is used for + # communicating with translation layers. It is not meant to be public API. + attr_reader :type + # The message associated with this error. attr_reader :message @@ -383,7 +385,8 @@ class ParseError attr_reader :level # Create a new error object with the given message and location. - def initialize(message, location, level) + def initialize(type, message, location, level) + @type = type @message = message @location = location @level = level @@ -391,17 +394,21 @@ def initialize(message, location, level) # Implement the hash pattern matching interface for ParseError. def deconstruct_keys(keys) - { message: message, location: location, level: level } + { type: type, message: message, location: location, level: level } end # Returns a string representation of this error. def inspect - "#" + "#" end end # This represents a warning that was encountered during parsing. class ParseWarning + # The type of warning. This is an _internal_ symbol that is used for + # communicating with translation layers. It is not meant to be public API. + attr_reader :type + # The message associated with this warning. attr_reader :message @@ -412,7 +419,8 @@ class ParseWarning attr_reader :level # Create a new warning object with the given message and location. - def initialize(message, location, level) + def initialize(type, message, location, level) + @type = type @message = message @location = location @level = level @@ -420,24 +428,19 @@ def initialize(message, location, level) # Implement the hash pattern matching interface for ParseWarning. def deconstruct_keys(keys) - { message: message, location: location, level: level } + { type: type, message: message, location: location, level: level } end # Returns a string representation of this warning. def inspect - "#" + "#" end end # This represents the result of a call to ::parse or ::parse_file. It contains - # the AST, any comments that were encounters, and any errors that were - # encountered. - class ParseResult - # The value that was generated by parsing. Normally this holds the AST, but - # it can sometimes how a list of tokens or other results passed back from - # the parser. - attr_reader :value - + # the requested structure, any comments that were encounters, and any errors + # that were encountered. + class Result # The list of comments that were encountered during parsing. attr_reader :comments @@ -458,9 +461,8 @@ class ParseResult # A Source instance that represents the source code that was parsed. attr_reader :source - # Create a new parse result object with the given values. - def initialize(value, comments, magic_comments, data_loc, errors, warnings, source) - @value = value + # Create a new result object with the given values. + def initialize(comments, magic_comments, data_loc, errors, warnings, source) @comments = comments @magic_comments = magic_comments @data_loc = data_loc @@ -469,9 +471,9 @@ def initialize(value, comments, magic_comments, data_loc, errors, warnings, sour @source = source end - # Implement the hash pattern matching interface for ParseResult. + # Implement the hash pattern matching interface for Result. def deconstruct_keys(keys) - { value: value, comments: comments, magic_comments: magic_comments, data_loc: data_loc, errors: errors, warnings: warnings } + { comments: comments, magic_comments: magic_comments, data_loc: data_loc, errors: errors, warnings: warnings } end # Returns the encoding of the source code that was parsed. @@ -492,6 +494,58 @@ def failure? end end + # This is a result specific to the `parse` and `parse_file` methods. + class ParseResult < Result + # The syntax tree that was parsed from the source code. + attr_reader :value + + # Create a new parse result object with the given values. + def initialize(value, comments, magic_comments, data_loc, errors, warnings, source) + @value = value + super(comments, magic_comments, data_loc, errors, warnings, source) + end + + # Implement the hash pattern matching interface for ParseResult. + def deconstruct_keys(keys) + super.merge!(value: value) + end + end + + # This is a result specific to the `lex` and `lex_file` methods. + class LexResult < Result + # The list of tokens that were parsed from the source code. + attr_reader :value + + # Create a new lex result object with the given values. + def initialize(value, comments, magic_comments, data_loc, errors, warnings, source) + @value = value + super(comments, magic_comments, data_loc, errors, warnings, source) + end + + # Implement the hash pattern matching interface for LexResult. + def deconstruct_keys(keys) + super.merge!(value: value) + end + end + + # This is a result specific to the `parse_lex` and `parse_lex_file` methods. + class ParseLexResult < Result + # A tuple of the syntax tree and the list of tokens that were parsed from + # the source code. + attr_reader :value + + # Create a new parse lex result object with the given values. + def initialize(value, comments, magic_comments, data_loc, errors, warnings, source) + @value = value + super(comments, magic_comments, data_loc, errors, warnings, source) + end + + # Implement the hash pattern matching interface for ParseLexResult. + def deconstruct_keys(keys) + super.merge!(value: value) + end + end + # This represents a token from the Ruby source. class Token # The Source object that represents the source this token came from. @@ -541,7 +595,7 @@ def pretty_print(q) # Returns true if the given other token is equal to this token. def ==(other) - other.is_a?(Token) && + Token === other && other.type == type && other.value == value end diff --git a/lib/prism/polyfill/string.rb b/lib/prism/polyfill/string.rb new file mode 100644 index 00000000000000..3fa9b5a0c56abe --- /dev/null +++ b/lib/prism/polyfill/string.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Polyfill for String#unpack1 with the offset parameter. Not all Ruby engines +# have Method#parameters implemented, so we check the arity instead if +# necessary. +if (unpack1 = String.instance_method(:unpack1)).respond_to?(:parameters) ? unpack1.parameters.none? { |_, name| name == :offset } : (unpack1.arity == 1) + String.prepend( + Module.new { + def unpack1(format, offset: 0) # :nodoc: + offset == 0 ? super(format) : self[offset..].unpack1(format) # steep:ignore + end + } + ) +end diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index ad0a2d5fdda4c8..c98cc46bb82dd3 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.26.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] @@ -35,7 +35,7 @@ Gem::Specification.new do |spec| "docs/parser_translation.md", "docs/parsing_rules.md", "docs/releasing.md", - "docs/ripper.md", + "docs/ripper_translation.md", "docs/ruby_api.md", "docs/ruby_parser_translation.md", "docs/serialization.md", @@ -63,7 +63,6 @@ Gem::Specification.new do |spec| "include/prism/util/pm_list.h", "include/prism/util/pm_memchr.h", "include/prism/util/pm_newline_list.h", - "include/prism/util/pm_state_stack.h", "include/prism/util/pm_strncasecmp.h", "include/prism/util/pm_string.h", "include/prism/util/pm_string_list.h", @@ -77,16 +76,18 @@ Gem::Specification.new do |spec| "lib/prism/dot_visitor.rb", "lib/prism/dsl.rb", "lib/prism/ffi.rb", + "lib/prism/inspect_visitor.rb", "lib/prism/lex_compat.rb", "lib/prism/mutation_compiler.rb", "lib/prism/node_ext.rb", - "lib/prism/node_inspector.rb", "lib/prism/node.rb", "lib/prism/pack.rb", "lib/prism/parse_result.rb", "lib/prism/parse_result/comments.rb", "lib/prism/parse_result/newlines.rb", "lib/prism/pattern.rb", + "lib/prism/polyfill/string.rb", + "lib/prism/reflection.rb", "lib/prism/serialize.rb", "lib/prism/translation.rb", "lib/prism/translation/parser.rb", @@ -96,14 +97,50 @@ Gem::Specification.new do |spec| "lib/prism/translation/parser/lexer.rb", "lib/prism/translation/parser/rubocop.rb", "lib/prism/translation/ripper.rb", - "lib/prism/translation/ripper/ripper_compiler.rb", + "lib/prism/translation/ripper/sexp.rb", + "lib/prism/translation/ripper/shim.rb", "lib/prism/translation/ruby_parser.rb", "lib/prism/visitor.rb", + "prism.gemspec", + "rbi/prism.rbi", + "rbi/prism/compiler.rbi", + "rbi/prism/desugar_compiler.rbi", + "rbi/prism/inspect_visitor.rbi", + "rbi/prism/mutation_compiler.rbi", + "rbi/prism/node_ext.rbi", + "rbi/prism/node.rbi", + "rbi/prism/parse_result.rbi", + "rbi/prism/reflection.rbi", + "rbi/prism/translation/parser.rbi", + "rbi/prism/translation/parser/compiler.rbi", + "rbi/prism/translation/parser33.rbi", + "rbi/prism/translation/parser34.rbi", + "rbi/prism/translation/ripper.rbi", + "rbi/prism/translation/ripper/ripper_compiler.rbi", + "rbi/prism/translation/ruby_parser.rbi", + "rbi/prism/visitor.rbi", + "sig/prism.rbs", + "sig/prism/compiler.rbs", + "sig/prism/dispatcher.rbs", + "sig/prism/dot_visitor.rbs", + "sig/prism/dsl.rbs", + "sig/prism/inspect_visitor.rbs", + "sig/prism/mutation_compiler.rbs", + "sig/prism/node_ext.rbs", + "sig/prism/node.rbs", + "sig/prism/pack.rbs", + "sig/prism/parse_result.rbs", + "sig/prism/pattern.rbs", + "sig/prism/reflection.rbs", + "sig/prism/serialize.rbs", + "sig/prism/visitor.rbs", "src/diagnostic.c", "src/encoding.c", "src/node.c", + "src/options.c", "src/pack.c", "src/prettyprint.c", + "src/prism.c", "src/regexp.c", "src/serialize.c", "src/static_literals.c", @@ -115,39 +152,10 @@ Gem::Specification.new do |spec| "src/util/pm_list.c", "src/util/pm_memchr.c", "src/util/pm_newline_list.c", - "src/util/pm_state_stack.c", - "src/util/pm_string.c", "src/util/pm_string_list.c", + "src/util/pm_string.c", "src/util/pm_strncasecmp.c", - "src/util/pm_strpbrk.c", - "src/options.c", - "src/prism.c", - "prism.gemspec", - "sig/prism.rbs", - "sig/prism/compiler.rbs", - "sig/prism/dispatcher.rbs", - "sig/prism/dot_visitor.rbs", - "sig/prism/dsl.rbs", - "sig/prism/mutation_compiler.rbs", - "sig/prism/node.rbs", - "sig/prism/node_ext.rbs", - "sig/prism/pack.rbs", - "sig/prism/parse_result.rbs", - "sig/prism/pattern.rbs", - "sig/prism/serialize.rbs", - "sig/prism/visitor.rbs", - "rbi/prism.rbi", - "rbi/prism/compiler.rbi", - "rbi/prism/desugar_compiler.rbi", - "rbi/prism/mutation_compiler.rbi", - "rbi/prism/node_ext.rbi", - "rbi/prism/node.rbi", - "rbi/prism/parse_result.rbi", - "rbi/prism/translation/parser/compiler.rbi", - "rbi/prism/translation/ripper.rbi", - "rbi/prism/translation/ripper/ripper_compiler.rbi", - "rbi/prism/translation/ruby_parser.rbi", - "rbi/prism/visitor.rbi" + "src/util/pm_strpbrk.c" ] spec.extensions = ["ext/prism/extconf.rb"] diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index fd1302821d0ef1..0d11b8f5668cca 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -9,11 +9,14 @@ module Translation # the parser gem, and overrides the parse* methods to parse with prism and # then translate. class Parser < ::Parser::Base + Diagnostic = ::Parser::Diagnostic # :nodoc: + private_constant :Diagnostic + # The parser gem has a list of diagnostics with a hard-coded set of error # messages. We create our own diagnostic class in order to set our own # error messages. - class Diagnostic < ::Parser::Diagnostic - # The message generated by prism. + class PrismDiagnostic < Diagnostic + # This is the cached message coming from prism. attr_reader :message # Initialize a new diagnostic with the given message and location. @@ -112,20 +115,117 @@ def valid_warning?(warning) true end + # Build a diagnostic from the given prism parse error. + def error_diagnostic(error, offset_cache) + location = error.location + diagnostic_location = build_range(location, offset_cache) + + case error.type + when :argument_block_multi + Diagnostic.new(:error, :block_and_blockarg, {}, diagnostic_location, []) + when :argument_formal_constant + Diagnostic.new(:error, :argument_const, {}, diagnostic_location, []) + when :argument_formal_class + Diagnostic.new(:error, :argument_cvar, {}, diagnostic_location, []) + when :argument_formal_global + Diagnostic.new(:error, :argument_gvar, {}, diagnostic_location, []) + when :argument_formal_ivar + Diagnostic.new(:error, :argument_ivar, {}, diagnostic_location, []) + when :argument_no_forwarding_amp + 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) + Diagnostic.new(:error, :useless_else, {}, diagnostic_location, []) + when :class_name, :module_name + Diagnostic.new(:error, :module_name_const, {}, diagnostic_location, []) + when :class_in_method + Diagnostic.new(:error, :class_in_def, {}, diagnostic_location, []) + when :def_endless_setter + Diagnostic.new(:error, :endless_setter, {}, diagnostic_location, []) + when :embdoc_term + Diagnostic.new(:error, :embedded_document, {}, diagnostic_location, []) + when :incomplete_variable_class, :incomplete_variable_class_3_3_0 + location = location.copy(length: location.length + 1) + diagnostic_location = build_range(location, offset_cache) + + Diagnostic.new(:error, :cvar_name, { name: location.slice }, diagnostic_location, []) + when :incomplete_variable_instance, :incomplete_variable_instance_3_3_0 + location = location.copy(length: location.length + 1) + diagnostic_location = build_range(location, offset_cache) + + Diagnostic.new(:error, :ivar_name, { name: location.slice }, diagnostic_location, []) + when :invalid_variable_global, :invalid_variable_global_3_3_0 + Diagnostic.new(:error, :gvar_name, { name: location.slice }, diagnostic_location, []) + when :module_in_method + Diagnostic.new(:error, :module_in_def, {}, diagnostic_location, []) + when :numbered_parameter_ordinary + Diagnostic.new(:error, :ordinary_param_defined, {}, diagnostic_location, []) + when :numbered_parameter_outer_scope + Diagnostic.new(:error, :numparam_used_in_outer_scope, {}, diagnostic_location, []) + when :parameter_circular + Diagnostic.new(:error, :circular_argument_reference, { var_name: location.slice }, diagnostic_location, []) + when :parameter_name_repeat + 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 + Diagnostic.new(:error, :string_eof, {}, diagnostic_location, []) + when :unexpected_token_ignore + Diagnostic.new(:error, :unexpected_token, { token: location.slice }, diagnostic_location, []) + when :write_target_in_method + Diagnostic.new(:error, :dynamic_const, {}, diagnostic_location, []) + else + PrismDiagnostic.new(error.message, :error, error.type, diagnostic_location) + end + end + + # Build a diagnostic from the given prism parse warning. + def warning_diagnostic(warning, offset_cache) + diagnostic_location = build_range(warning.location, offset_cache) + + case warning.type + when :ambiguous_first_argument_plus + 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 + Diagnostic.new(:warning, :triple_dot_at_eol, {}, diagnostic_location, []) + when :duplicated_hash_key + # skip, parser does this on its own + else + PrismDiagnostic.new(warning.message, :warning, warning.type, diagnostic_location) + end + end + # If there was a error generated during the parse, then raise an # appropriate syntax error. Otherwise return the result. def unwrap(result, offset_cache) result.errors.each do |error| next unless valid_error?(error) - - location = build_range(error.location, offset_cache) - diagnostics.process(Diagnostic.new(error.message, :error, :prism_error, location)) + diagnostics.process(error_diagnostic(error, offset_cache)) end + result.warnings.each do |warning| next unless valid_warning?(warning) - - location = build_range(warning.location, offset_cache) - diagnostics.process(Diagnostic.new(warning.message, :warning, :prism_warning, location)) + diagnostic = warning_diagnostic(warning, offset_cache) + diagnostics.process(diagnostic) if diagnostic end result diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index 1369fe6f81f0d5..a4aaa41d6f76fa 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -116,7 +116,14 @@ def visit_assoc_node(node) builder.pair_keyword([node.key.unescaped, srange(node.key.location)], visit(node.value)) end elsif node.value.is_a?(ImplicitNode) - builder.pair_label([node.key.unescaped, srange(node.key.location)]) + if (value = node.value.value).is_a?(LocalVariableReadNode) + builder.pair_keyword( + [node.key.unescaped, srange(node.key)], + builder.ident([value.name, srange(node.key.value_loc)]).updated(:lvar) + ) + else + builder.pair_label([node.key.unescaped, srange(node.key.location)]) + end elsif node.operator_loc builder.pair(visit(node.key), token(node.operator_loc), visit(node.value)) elsif node.key.is_a?(SymbolNode) && node.key.opening_loc.nil? @@ -399,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))), @@ -694,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))), @@ -800,6 +801,7 @@ def visit_if_node(node) end # 1i + # ^^ def visit_imaginary_node(node) visit_numeric(node, builder.complex([imaginary_value(node), srange(node.location)])) end @@ -837,7 +839,7 @@ def visit_in_node(node) token(node.in_loc), pattern, guard, - srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset || node.location.end_offset, [";", "then"]), + srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, [";", "then"]), visit(node.statements) ) end @@ -887,9 +889,6 @@ def visit_instance_variable_read_node(node) # @foo = 1 # ^^^^^^^^ - # - # @foo, @bar = 1 - # ^^^^ ^^^^ def visit_instance_variable_write_node(node) builder.assign( builder.assignable(builder.ivar(token(node.name_loc))), @@ -948,14 +947,62 @@ 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) + opening = token(node.opening_loc) + + start_offset = node.opening_loc.end_offset + 1 + end_offset = node.parts.first.location.start_offset + + # In the below case, the offsets should be the same: + # + # <<~HEREDOC + # a #{b} + # HEREDOC + # + # But in this case, the end_offset would be greater than the start_offset: + # + # <<~HEREDOC + # #{b} + # HEREDOC + # + # So we need to make sure the result node's heredoc range is correct, without updating the children + result = if start_offset < end_offset + # We need to add a padding string to ensure that the heredoc has correct range for its body + padding_string_node = builder.string_internal(["", srange_offsets(start_offset, end_offset)]) + node_with_correct_location = builder.string_compose(opening, [padding_string_node, *children], closing) + # But the padding string should not be included in the final AST, so we need to update the result's children + node_with_correct_location.updated(:dstr, children) + else + builder.string_compose(opening, children, closing) + end + + return result + 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}" @@ -983,6 +1030,12 @@ def visit_interpolated_x_string_node(node) end end + # -> { it } + # ^^^^^^^^^ + def visit_it_parameters_node(node) + builder.args(nil, [], nil, false) + end + # foo(bar: baz) # ^^^^^^^^ def visit_keyword_hash_node(node) @@ -1002,15 +1055,17 @@ def visit_keyword_rest_parameter_node(node) end # -> {} + # ^^^^^ def visit_lambda_node(node) parameters = node.parameters + implicit_parameters = parameters.is_a?(NumberedParametersNode) || parameters.is_a?(ItParametersNode) builder.block( builder.call_lambda(token(node.operator_loc)), [node.opening, srange(node.opening_loc)], if parameters.nil? builder.args(nil, [], nil, false) - elsif node.parameters.is_a?(NumberedParametersNode) + elsif implicit_parameters visit(node.parameters) else builder.args( @@ -1020,7 +1075,7 @@ def visit_lambda_node(node) false ) end, - node.body&.accept(copy_compiler(forwarding: parameters.is_a?(NumberedParametersNode) ? [] : find_forwarding(parameters&.parameters))), + node.body&.accept(copy_compiler(forwarding: implicit_parameters ? [] : find_forwarding(parameters&.parameters))), [node.closing, srange(node.closing_loc)] ) end @@ -1028,14 +1083,18 @@ def visit_lambda_node(node) # foo # ^^^ def visit_local_variable_read_node(node) - builder.ident([node.name, srange(node.location)]).updated(:lvar) + name = node.name + + # This is just a guess. parser doesn't have support for the implicit + # `it` variable yet, so we'll probably have to visit this once it + # does. + name = :it if name == :"0it" + + builder.ident([name, srange(node.location)]).updated(:lvar) end # foo = 1 # ^^^^^^^ - # - # foo, bar = 1 - # ^^^ ^^^ def visit_local_variable_write_node(node) builder.assign( builder.assignable(builder.ident(token(node.name_loc))), @@ -1421,6 +1480,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) @@ -1485,9 +1549,33 @@ def visit_string_node(node) elsif node.opening == "?" builder.character([node.unescaped, srange(node.location)]) else + content_lines = node.content.lines + unescaped_lines = node.unescaped.lines + + parts = + if content_lines.length <= 1 || unescaped_lines.length <= 1 + [builder.string_internal([node.unescaped, srange(node.content_loc)])] + elsif content_lines.length != unescaped_lines.length + # This occurs when we have line continuations in the string. We + # need to come back and fix this, but for now this stops the + # code from breaking when we encounter it because of trying to + # transpose arrays of different lengths. + [builder.string_internal([node.unescaped, srange(node.content_loc)])] + else + start_offset = node.content_loc.start_offset + + [content_lines, 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 @@ -1526,9 +1614,23 @@ def visit_symbol_node(node) builder.symbol([node.unescaped, srange(node.location)]) end else + parts = if node.value.lines.one? + [builder.string_internal([node.unescaped, srange(node.value_loc)])] + else + start_offset = node.value_loc.start_offset + + node.value.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.symbol_compose( token(node.opening_loc), - [builder.string_internal([node.unescaped, srange(node.value_loc)])], + parts, token(node.closing_loc) ) end @@ -1577,7 +1679,7 @@ def visit_unless_node(node) end # until foo; bar end - # ^^^^^^^^^^^^^^^^^ + # ^^^^^^^^^^^^^^^^^^ # # bar until foo # ^^^^^^^^^^^^^ @@ -1610,7 +1712,7 @@ def visit_when_node(node) if node.then_keyword_loc token(node.then_keyword_loc) else - srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset || (node.conditions.last.location.end_offset + 1), [";"]) + srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, [";"]) end, visit(node.statements) ) @@ -1648,9 +1750,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 @@ -1755,12 +1871,16 @@ def srange_offsets(start_offset, end_offset) # Constructs a new source range by finding the given tokens between the # given start offset and end offset. If the needle is not found, it - # returns nil. + # returns nil. Importantly it does not search past newlines or comments. + # + # Note that end_offset is allowed to be nil, in which case this will + # search until the end of the string. def srange_find(start_offset, end_offset, tokens) - tokens.find do |token| - next unless (index = source_buffer.source.byteslice(start_offset...end_offset).index(token)) - offset = start_offset + index - return [token, Range.new(source_buffer, offset_cache[offset], offset_cache[offset + token.length])] + if (match = source_buffer.source.byteslice(start_offset...end_offset).match(/(\s*)(#{tokens.join("|")})/)) + _, whitespace, token = *match + token_offset = start_offset + whitespace.bytesize + + [token, Range.new(source_buffer, offset_cache[token_offset], offset_cache[token_offset + token.bytesize])] end end @@ -1773,13 +1893,14 @@ def token(location) def visit_block(call, block) if block parameters = block.parameters + implicit_parameters = parameters.is_a?(NumberedParametersNode) || parameters.is_a?(ItParametersNode) builder.block( call, token(block.opening_loc), if parameters.nil? builder.args(nil, [], nil, false) - elsif parameters.is_a?(NumberedParametersNode) + elsif implicit_parameters visit(parameters) else builder.args( @@ -1794,7 +1915,7 @@ def visit_block(call, block) false ) end, - block.body&.accept(copy_compiler(forwarding: parameters.is_a?(NumberedParametersNode) ? [] : find_forwarding(parameters&.parameters))), + block.body&.accept(copy_compiler(forwarding: implicit_parameters ? [] : find_forwarding(parameters&.parameters))), token(block.closing_loc) ) else @@ -1804,7 +1925,7 @@ def visit_block(call, block) # Visit a heredoc that can be either a string or an xstring. def visit_heredoc(node) - children = [] + children = Array.new node.parts.each do |part| pushing = if part.is_a?(StringNode) && part.unescaped.include?("\n") diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb index b710b1981b97f9..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, @@ -213,9 +213,13 @@ def initialize(source_buffer, lexed, offset_cache) # Convert the prism tokens into the expected format for the parser gem. def to_a tokens = [] + index = 0 + length = lexed.length + + heredoc_identifier_stack = [] - while index < lexed.length + while index < length token, state = lexed[index] index += 1 next if %i[IGNORED_NEWLINE __END__ EOF].include?(token.type) @@ -229,14 +233,18 @@ def to_a value.delete_prefix!("?") when :tCOMMENT if token.type == :EMBDOC_BEGIN - until (next_token = lexed[index][0]) && next_token.type == :EMBDOC_END + start_index = index + + while !((next_token = lexed[index][0]) && next_token.type == :EMBDOC_END) && (index < length - 1) value += next_token.value index += 1 end - value += next_token.value - location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[lexed[index][0].location.end_offset]) - index += 1 + if start_index != index + value += next_token.value + location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[lexed[index][0].location.end_offset]) + index += 1 + end else value.chomp! location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.end_offset - 1]) @@ -244,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 @@ -253,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 @@ -261,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 @@ -269,26 +277,64 @@ 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| + 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 end when :tSTRING_DVAR value = nil when :tSTRING_END - if token.type == :REGEXP_END + if token.type == :HEREDOC_END && value.end_with?("\n") + newline_length = value.end_with?("\r\n") ? 2 : 1 + 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] location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.start_offset + 1]) end @@ -302,9 +348,13 @@ def to_a index += 1 end when :tFID - if !tokens.empty? && tokens[-1][0] == :kDEF + 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]] @@ -319,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") @@ -326,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. @@ -337,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 e6fd8db2901d8e..6c9687a5cc0ecc 100644 --- a/lib/prism/translation/parser/rubocop.rb +++ b/lib/prism/translation/parser/rubocop.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true # typed: ignore +warn "WARN: Prism is directly supported since RuboCop 1.62. The `prism/translation/parser/rubocop` file is deprecated." + require "parser" require "rubocop" -require "prism" -require "prism/translation/parser" +require_relative "../../prism" +require_relative "../parser" module Prism module Translation @@ -27,10 +29,14 @@ module ProcessedSource # list of known parsers. def parser_class(ruby_version) if ruby_version == Prism::Translation::Parser::VERSION_3_3 - require "prism/translation/parser33" + warn "WARN: Setting `TargetRubyVersion: 80_82_73_83_77.33` is deprecated. " \ + "Set to `ParserEngine: parser_prism` and `TargetRubyVersion: 3.3` instead." + require_relative "../parser33" Prism::Translation::Parser33 elsif ruby_version == Prism::Translation::Parser::VERSION_3_4 - require "prism/translation/parser34" + warn "WARN: Setting `TargetRubyVersion: 80_82_73_83_77.34` is deprecated. " \ + "Set to `ParserEngine: parser_prism` and `TargetRubyVersion: 3.4` instead." + require_relative "../parser34" Prism::Translation::Parser34 else super @@ -41,10 +47,14 @@ def parser_class(ruby_version) # list of known parsers. def parser_class(ruby_version, _parser_engine) if ruby_version == Prism::Translation::Parser::VERSION_3_3 - require "prism/translation/parser33" + warn "WARN: Setting `TargetRubyVersion: 80_82_73_83_77.33` is deprecated. " \ + "Set to `ParserEngine: parser_prism` and `TargetRubyVersion: 3.3` instead." + require_relative "../parser33" Prism::Translation::Parser33 elsif ruby_version == Prism::Translation::Parser::VERSION_3_4 - require "prism/translation/parser34" + warn "WARN: Setting `TargetRubyVersion: 80_82_73_83_77.34` is deprecated. " \ + "Set to `ParserEngine: parser_prism` and `TargetRubyVersion: 3.4` instead." + require_relative "../parser34" Prism::Translation::Parser34 else super diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index 262d410b073102..2c5e4569c28fc1 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -1,71 +1,457 @@ # frozen_string_literal: true require "ripper" -require_relative "ripper/ripper_compiler" module Prism module Translation - # Note: This integration is not finished, and therefore still has many - # inconsistencies with Ripper. If you'd like to help out, pull requests would - # be greatly appreciated! + # This class provides a compatibility layer between prism and Ripper. It + # functions by parsing the entire tree first and then walking it and + # executing each of the Ripper callbacks as it goes. To use this class, you + # treat `Prism::Translation::Ripper` effectively as you would treat the + # `Ripper` class. # - # This class is meant to provide a compatibility layer between prism and - # Ripper. It functions by parsing the entire tree first and then walking it - # and executing each of the Ripper callbacks as it goes. + # Note that this class will serve the most common use cases, but Ripper's + # API is extensive and undocumented. It relies on reporting the state of the + # parser at any given time. We do our best to replicate that here, but + # because it is a different architecture it is not possible to perfectly + # replicate the behavior of Ripper. # - # This class is going to necessarily be slower than the native Ripper API. It - # is meant as a stopgap until developers migrate to using prism. It is also - # meant as a test harness for the prism parser. + # The main known difference is that we may omit dispatching some events in + # some cases. This impacts the following events: # - # To use this class, you treat `Prism::Translation::Ripper` effectively as you would - # treat the `Ripper` class. - class Ripper < RipperCompiler - # This class mirrors the ::Ripper::SexpBuilder subclass of ::Ripper that - # returns the arrays of [type, *children]. - class SexpBuilder < Ripper - private + # - on_assign_error + # - on_comma + # - on_ignored_nl + # - on_ignored_sp + # - on_kw + # - on_label_end + # - on_lbrace + # - on_lbracket + # - on_lparen + # - on_nl + # - on_op + # - on_operator_ambiguous + # - on_rbrace + # - on_rbracket + # - on_rparen + # - on_semicolon + # - on_sp + # - on_symbeg + # - on_tstring_beg + # - on_tstring_end + # + class Ripper < Compiler + # Parses the given Ruby program read from +src+. + # +src+ must be a String or an IO or a object with a #gets method. + def self.parse(src, filename = "(ripper)", lineno = 1) + new(src, filename, lineno).parse + end - ::Ripper::PARSER_EVENTS.each do |event| - define_method(:"on_#{event}") do |*args| - [event, *args] - end - end + # Tokenizes the Ruby program and returns an array of an array, + # which is formatted like + # [[lineno, column], type, token, state]. + # The +filename+ argument is mostly ignored. + # By default, this method does not handle syntax errors in +src+, + # use the +raise_errors+ keyword to raise a SyntaxError for an error in +src+. + # + # require "ripper" + # require "pp" + # + # pp Ripper.lex("def m(a) nil end") + # #=> [[[1, 0], :on_kw, "def", FNAME ], + # [[1, 3], :on_sp, " ", FNAME ], + # [[1, 4], :on_ident, "m", ENDFN ], + # [[1, 5], :on_lparen, "(", BEG|LABEL], + # [[1, 6], :on_ident, "a", ARG ], + # [[1, 7], :on_rparen, ")", ENDFN ], + # [[1, 8], :on_sp, " ", BEG ], + # [[1, 9], :on_kw, "nil", END ], + # [[1, 12], :on_sp, " ", END ], + # [[1, 13], :on_kw, "end", END ]] + # + def self.lex(src, filename = "-", lineno = 1, raise_errors: false) + result = Prism.lex_compat(src, filepath: filename, line: lineno) - ::Ripper::SCANNER_EVENTS.each do |event| - define_method(:"on_#{event}") do |value| - [:"@#{event}", value, [lineno, column]] - end + if result.failure? && raise_errors + raise SyntaxError, result.errors.first.message + else + result.value end end - # This class mirrors the ::Ripper::SexpBuilderPP subclass of ::Ripper that - # returns the same values as ::Ripper::SexpBuilder except with a couple of - # niceties that flatten linked lists into arrays. - class SexpBuilderPP < SexpBuilder - private + # This contains a table of all of the parser events and their + # corresponding arity. + PARSER_EVENT_TABLE = { + BEGIN: 1, + END: 1, + alias: 2, + alias_error: 2, + aref: 2, + aref_field: 2, + arg_ambiguous: 1, + arg_paren: 1, + args_add: 2, + args_add_block: 2, + args_add_star: 2, + args_forward: 0, + args_new: 0, + array: 1, + aryptn: 4, + assign: 2, + assign_error: 2, + assoc_new: 2, + assoc_splat: 1, + assoclist_from_args: 1, + bare_assoc_hash: 1, + begin: 1, + binary: 3, + block_var: 2, + blockarg: 1, + bodystmt: 4, + brace_block: 2, + break: 1, + call: 3, + case: 2, + class: 3, + class_name_error: 2, + command: 2, + command_call: 4, + const_path_field: 2, + const_path_ref: 2, + const_ref: 1, + def: 3, + defined: 1, + defs: 5, + do_block: 2, + dot2: 2, + dot3: 2, + dyna_symbol: 1, + else: 1, + elsif: 3, + ensure: 1, + excessed_comma: 0, + fcall: 1, + field: 3, + fndptn: 4, + for: 3, + hash: 1, + heredoc_dedent: 2, + hshptn: 3, + if: 3, + if_mod: 2, + ifop: 3, + in: 3, + kwrest_param: 1, + lambda: 2, + magic_comment: 2, + massign: 2, + method_add_arg: 2, + method_add_block: 2, + mlhs_add: 2, + mlhs_add_post: 2, + mlhs_add_star: 2, + mlhs_new: 0, + mlhs_paren: 1, + module: 2, + mrhs_add: 2, + mrhs_add_star: 2, + mrhs_new: 0, + mrhs_new_from_args: 1, + next: 1, + nokw_param: 1, + opassign: 3, + operator_ambiguous: 2, + param_error: 2, + params: 7, + paren: 1, + parse_error: 1, + program: 1, + qsymbols_add: 2, + qsymbols_new: 0, + qwords_add: 2, + qwords_new: 0, + redo: 0, + regexp_add: 2, + regexp_literal: 2, + regexp_new: 0, + rescue: 4, + rescue_mod: 2, + rest_param: 1, + retry: 0, + return: 1, + return0: 0, + sclass: 2, + stmts_add: 2, + stmts_new: 0, + string_add: 2, + string_concat: 2, + string_content: 0, + string_dvar: 1, + string_embexpr: 1, + string_literal: 1, + super: 1, + symbol: 1, + symbol_literal: 1, + symbols_add: 2, + symbols_new: 0, + top_const_field: 1, + top_const_ref: 1, + unary: 2, + undef: 1, + unless: 3, + unless_mod: 2, + until: 2, + until_mod: 2, + var_alias: 2, + var_field: 1, + var_ref: 1, + vcall: 1, + void_stmt: 0, + when: 3, + while: 2, + while_mod: 2, + word_add: 2, + word_new: 0, + words_add: 2, + words_new: 0, + xstring_add: 2, + xstring_literal: 1, + xstring_new: 0, + yield: 1, + yield0: 0, + zsuper: 0 + } - def _dispatch_event_new # :nodoc: - [] - end + # This contains a table of all of the scanner events and their + # corresponding arity. + SCANNER_EVENT_TABLE = { + CHAR: 1, + __end__: 1, + backref: 1, + backtick: 1, + comma: 1, + comment: 1, + const: 1, + cvar: 1, + embdoc: 1, + embdoc_beg: 1, + embdoc_end: 1, + embexpr_beg: 1, + embexpr_end: 1, + embvar: 1, + float: 1, + gvar: 1, + heredoc_beg: 1, + heredoc_end: 1, + ident: 1, + ignored_nl: 1, + imaginary: 1, + int: 1, + ivar: 1, + kw: 1, + label: 1, + label_end: 1, + lbrace: 1, + lbracket: 1, + lparen: 1, + nl: 1, + op: 1, + period: 1, + qsymbols_beg: 1, + qwords_beg: 1, + rational: 1, + rbrace: 1, + rbracket: 1, + regexp_beg: 1, + regexp_end: 1, + rparen: 1, + semicolon: 1, + sp: 1, + symbeg: 1, + symbols_beg: 1, + tlambda: 1, + tlambeg: 1, + tstring_beg: 1, + tstring_content: 1, + tstring_end: 1, + words_beg: 1, + words_sep: 1, + ignored_sp: 1 + } + + # This array contains name of parser events. + PARSER_EVENTS = PARSER_EVENT_TABLE.keys - def _dispatch_event_push(list, item) # :nodoc: - list << item - list + # This array contains name of scanner events. + SCANNER_EVENTS = SCANNER_EVENT_TABLE.keys + + # This array contains name of all ripper events. + EVENTS = PARSER_EVENTS + SCANNER_EVENTS + + # A list of all of the Ruby keywords. + KEYWORDS = [ + "alias", + "and", + "begin", + "BEGIN", + "break", + "case", + "class", + "def", + "defined?", + "do", + "else", + "elsif", + "end", + "END", + "ensure", + "false", + "for", + "if", + "in", + "module", + "next", + "nil", + "not", + "or", + "redo", + "rescue", + "retry", + "return", + "self", + "super", + "then", + "true", + "undef", + "unless", + "until", + "when", + "while", + "yield", + "__ENCODING__", + "__FILE__", + "__LINE__" + ] + + # A list of all of the Ruby binary operators. + BINARY_OPERATORS = [ + :!=, + :!~, + :=~, + :==, + :===, + :<=>, + :>, + :>=, + :<, + :<=, + :&, + :|, + :^, + :>>, + :<<, + :-, + :+, + :%, + :/, + :*, + :** + ] + + private_constant :KEYWORDS, :BINARY_OPERATORS + + # Parses +src+ and create S-exp tree. + # Returns more readable tree rather than Ripper.sexp_raw. + # This method is mainly for developer use. + # The +filename+ argument is mostly ignored. + # By default, this method does not handle syntax errors in +src+, + # returning +nil+ in such cases. Use the +raise_errors+ keyword + # to raise a SyntaxError for an error in +src+. + # + # require "ripper" + # require "pp" + # + # pp Ripper.sexp("def m(a) nil end") + # #=> [:program, + # [[:def, + # [:@ident, "m", [1, 4]], + # [:paren, [:params, [[:@ident, "a", [1, 6]]], nil, nil, nil, nil, nil, nil]], + # [:bodystmt, [[:var_ref, [:@kw, "nil", [1, 9]]]], nil, nil, nil]]]] + # + def self.sexp(src, filename = "-", lineno = 1, raise_errors: false) + builder = SexpBuilderPP.new(src, filename, lineno) + sexp = builder.parse + if builder.error? + if raise_errors + raise SyntaxError, builder.error + end + else + sexp end + end - ::Ripper::PARSER_EVENT_TABLE.each do |event, arity| - case event - when /_new\z/ - alias_method :"on_#{event}", :_dispatch_event_new if arity == 0 - when /_add\z/ - alias_method :"on_#{event}", :_dispatch_event_push + # Parses +src+ and create S-exp tree. + # This method is mainly for developer use. + # The +filename+ argument is mostly ignored. + # By default, this method does not handle syntax errors in +src+, + # returning +nil+ in such cases. Use the +raise_errors+ keyword + # to raise a SyntaxError for an error in +src+. + # + # require "ripper" + # require "pp" + # + # pp Ripper.sexp_raw("def m(a) nil end") + # #=> [:program, + # [:stmts_add, + # [:stmts_new], + # [:def, + # [:@ident, "m", [1, 4]], + # [:paren, [:params, [[:@ident, "a", [1, 6]]], nil, nil, nil]], + # [:bodystmt, + # [:stmts_add, [:stmts_new], [:var_ref, [:@kw, "nil", [1, 9]]]], + # nil, + # nil, + # nil]]]] + # + def self.sexp_raw(src, filename = "-", lineno = 1, raise_errors: false) + builder = SexpBuilder.new(src, filename, lineno) + sexp = builder.parse + if builder.error? + if raise_errors + raise SyntaxError, builder.error end + else + sexp end end - ############################################################################ + autoload :SexpBuilder, "prism/translation/ripper/sexp" + autoload :SexpBuilderPP, "prism/translation/ripper/sexp" + + # The source that is being parsed. + attr_reader :source + + # The filename of the source being parsed. + attr_reader :filename + + # The current line number of the parser. + attr_reader :lineno + + # The current column number of the parser. + attr_reader :column + + # Create a new Translation::Ripper object with the given source. + def initialize(source, filename = "(ripper)", lineno = 1) + @source = source + @filename = filename + @lineno = lineno + @column = 0 + @result = nil + end + + ########################################################################## # Public interface - ############################################################################ + ########################################################################## # True if the parser encountered an error during parsing. def error? @@ -74,13 +460,80 @@ def error? # Parse the source and return the result. def parse + result.comments.each do |comment| + location = comment.location + bounds(location) + + if comment.is_a?(InlineComment) + on_comment(comment.slice) + else + offset = location.start_offset + lines = comment.slice.lines + + lines.each_with_index do |line, index| + bounds(location.copy(start_offset: offset)) + + if index == 0 + on_embdoc_beg(line) + elsif index == lines.size - 1 + on_embdoc_end(line) + else + on_embdoc(line) + end + + offset += line.bytesize + end + end + end + result.magic_comments.each do |magic_comment| on_magic_comment(magic_comment.key, magic_comment.value) end + unless result.data_loc.nil? + on___end__(result.data_loc.slice.each_line.first) + end + + result.warnings.each do |warning| + bounds(warning.location) + + if warning.level == :default + warning(warning.message) + else + case warning.type + when :ambiguous_first_argument_plus + on_arg_ambiguous("+") + when :ambiguous_first_argument_minus + on_arg_ambiguous("-") + when :ambiguous_slash + on_arg_ambiguous("/") + else + warn(warning.message) + end + end + end + if error? result.errors.each do |error| - on_parse_error(error.message) + location = error.location + bounds(location) + + case error.type + when :alias_argument + on_alias_error("can't make alias for the number variables", location.slice) + when :argument_formal_class + on_param_error("formal argument cannot be a class variable", location.slice) + when :argument_format_constant + on_param_error("formal argument cannot be a constant", location.slice) + when :argument_formal_global + on_param_error("formal argument cannot be a global variable", location.slice) + when :argument_formal_ivar + on_param_error("formal argument cannot be an instance variable", location.slice) + when :class_name, :module_name + on_class_name_error("class/module name must be CONSTANT", location.slice) + else + on_parse_error(error.message) + end end nil @@ -89,38 +542,2904 @@ def parse end end - ############################################################################ - # Entrypoints for subclasses - ############################################################################ + ########################################################################## + # Visitor methods + ########################################################################## + + # alias foo bar + # ^^^^^^^^^^^^^ + def visit_alias_method_node(node) + new_name = visit(node.new_name) + old_name = visit(node.old_name) + + bounds(node.location) + on_alias(new_name, old_name) + end + + # alias $foo $bar + # ^^^^^^^^^^^^^^^ + def visit_alias_global_variable_node(node) + new_name = visit_alias_global_variable_node_value(node.new_name) + old_name = visit_alias_global_variable_node_value(node.old_name) - # This is a convenience method that runs the SexpBuilder subclass parser. - def self.sexp_raw(source) - SexpBuilder.new(source).parse + bounds(node.location) + on_var_alias(new_name, old_name) end - # This is a convenience method that runs the SexpBuilderPP subclass parser. - def self.sexp(source) - SexpBuilderPP.new(source).parse + # Visit one side of an alias global variable node. + private def visit_alias_global_variable_node_value(node) + bounds(node.location) + + case node + when BackReferenceReadNode + on_backref(node.slice) + when GlobalVariableReadNode + on_gvar(node.name.to_s) + else + raise + end end - # Lazily initialize the parse result. - def result - @result ||= Prism.parse(source) + # foo => bar | baz + # ^^^^^^^^^ + def visit_alternation_pattern_node(node) + 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) + left = visit(node.left) + right = visit(node.right) + + bounds(node.location) + on_binary(left, node.operator.to_sym, right) + end + + # [] + # ^^ + def visit_array_node(node) + case (opening = node.opening) + when /^%w/ + opening_loc = node.opening_loc + bounds(opening_loc) + on_qwords_beg(opening) + + elements = on_qwords_new + previous = nil + + node.elements.each do |element| + visit_words_sep(opening_loc, previous, element) + + bounds(element.location) + elements = on_qwords_add(elements, on_tstring_content(element.content)) + + previous = element + end + + bounds(node.closing_loc) + on_tstring_end(node.closing) + when /^%i/ + opening_loc = node.opening_loc + bounds(opening_loc) + on_qsymbols_beg(opening) + + elements = on_qsymbols_new + previous = nil + + node.elements.each do |element| + visit_words_sep(opening_loc, previous, element) + + bounds(element.location) + elements = on_qsymbols_add(elements, on_tstring_content(element.value)) + + previous = element + end + + bounds(node.closing_loc) + on_tstring_end(node.closing) + when /^%W/ + opening_loc = node.opening_loc + bounds(opening_loc) + on_words_beg(opening) + + elements = on_words_new + previous = nil + + node.elements.each do |element| + visit_words_sep(opening_loc, previous, element) + + bounds(element.location) + elements = + on_words_add( + elements, + if element.is_a?(StringNode) + on_word_add(on_word_new, on_tstring_content(element.content)) + else + element.parts.inject(on_word_new) do |word, part| + word_part = + if part.is_a?(StringNode) + bounds(part.location) + on_tstring_content(part.content) + else + visit(part) + end + + on_word_add(word, word_part) + end + end + ) + + previous = element + end + + bounds(node.closing_loc) + on_tstring_end(node.closing) + when /^%I/ + opening_loc = node.opening_loc + bounds(opening_loc) + on_symbols_beg(opening) + + elements = on_symbols_new + previous = nil + + node.elements.each do |element| + visit_words_sep(opening_loc, previous, element) + + bounds(element.location) + elements = + on_symbols_add( + elements, + if element.is_a?(SymbolNode) + on_word_add(on_word_new, on_tstring_content(element.value)) + else + element.parts.inject(on_word_new) do |word, part| + word_part = + if part.is_a?(StringNode) + bounds(part.location) + on_tstring_content(part.content) + else + visit(part) + end + + on_word_add(word, word_part) + end + end + ) + + previous = element + end + + bounds(node.closing_loc) + on_tstring_end(node.closing) + else + bounds(node.opening_loc) + on_lbracket(opening) + + elements = visit_arguments(node.elements) unless node.elements.empty? + + bounds(node.closing_loc) + on_rbracket(node.closing) + end + + bounds(node.location) + on_array(elements) + end + + # Dispatch a words_sep event that contains the space between the elements + # of list literals. + private def visit_words_sep(opening_loc, previous, current) + end_offset = (previous.nil? ? opening_loc : previous.location).end_offset + start_offset = current.location.start_offset + + if end_offset != start_offset + bounds(current.location.copy(start_offset: end_offset)) + on_words_sep(source.byteslice(end_offset...start_offset)) + end + end + + # Visit a list of elements, like the elements of an array or arguments. + private def visit_arguments(elements) + bounds(elements.first.location) + elements.inject(on_args_new) do |args, element| + arg = visit(element) + bounds(element.location) + + case element + when BlockArgumentNode + on_args_add_block(args, arg) + when SplatNode + on_args_add_star(args, arg) + else + on_args_add(args, arg) + end + end + end + + # foo => [bar] + # ^^^^^ + def visit_array_pattern_node(node) + constant = visit(node.constant) + requireds = visit_all(node.requireds) if node.requireds.any? + rest = + if (rest_node = node.rest).is_a?(SplatNode) + if rest_node.expression.nil? + bounds(rest_node.location) + on_var_field(nil) + else + visit(rest_node.expression) + end + end + + posts = visit_all(node.posts) if node.posts.any? + + bounds(node.location) + on_aryptn(constant, requireds, rest, posts) + end + + # foo(bar) + # ^^^ + def visit_arguments_node(node) + arguments, _ = visit_call_node_arguments(node, nil, false) + arguments + end + + # { a: 1 } + # ^^^^ + def visit_assoc_node(node) + key = visit(node.key) + value = visit(node.value) + + bounds(node.location) + on_assoc_new(key, value) + end + + # def foo(**); bar(**); end + # ^^ + # + # { **foo } + # ^^^^^ + def visit_assoc_splat_node(node) + value = visit(node.value) + + bounds(node.location) + on_assoc_splat(value) + end + + # $+ + # ^^ + def visit_back_reference_read_node(node) + bounds(node.location) + on_backref(node.slice) + end + + # begin end + # ^^^^^^^^^ + def visit_begin_node(node) + clauses = visit_begin_node_clauses(node.begin_keyword_loc, node, false) + + bounds(node.location) + on_begin(clauses) + end + + # Visit the clauses of a begin node to form an on_bodystmt call. + private def visit_begin_node_clauses(location, node, allow_newline) + statements = + if node.statements.nil? + on_stmts_add(on_stmts_new, on_void_stmt) + else + body = node.statements.body + body.unshift(nil) if void_stmt?(location, node.statements.body[0].location, allow_newline) + + bounds(node.statements.location) + visit_statements_node_body(body) + end + + rescue_clause = visit(node.rescue_clause) + else_clause = + unless (else_clause_node = node.else_clause).nil? + else_statements = + if else_clause_node.statements.nil? + [nil] + else + body = else_clause_node.statements.body + body.unshift(nil) if void_stmt?(else_clause_node.else_keyword_loc, else_clause_node.statements.body[0].location, allow_newline) + body + end + + bounds(else_clause_node.location) + visit_statements_node_body(else_statements) + end + ensure_clause = visit(node.ensure_clause) + + bounds(node.location) + on_bodystmt(statements, rescue_clause, else_clause, ensure_clause) + end + + # Visit the body of a structure that can have either a set of statements + # or statements wrapped in rescue/else/ensure. + private def visit_body_node(location, node, allow_newline = false) + case node + when nil + bounds(location) + on_bodystmt(visit_statements_node_body([nil]), nil, nil, nil) + when StatementsNode + body = [*node.body] + body.unshift(nil) if void_stmt?(location, body[0].location, allow_newline) + stmts = visit_statements_node_body(body) + + bounds(node.body.first.location) + on_bodystmt(stmts, nil, nil, nil) + when BeginNode + visit_begin_node_clauses(location, node, allow_newline) + else + raise + end + end + + # foo(&bar) + # ^^^^ + def visit_block_argument_node(node) + visit(node.expression) + end + + # foo { |; bar| } + # ^^^ + def visit_block_local_variable_node(node) + bounds(node.location) + on_ident(node.name.to_s) + end + + # Visit a BlockNode. + def visit_block_node(node) + braces = node.opening == "{" + parameters = visit(node.parameters) + + body = + case node.body + when nil + bounds(node.location) + stmts = on_stmts_add(on_stmts_new, on_void_stmt) + + bounds(node.location) + braces ? stmts : on_bodystmt(stmts, nil, nil, nil) + when StatementsNode + stmts = node.body.body + stmts.unshift(nil) if void_stmt?(node.parameters&.location || node.opening_loc, node.body.location, false) + stmts = visit_statements_node_body(stmts) + + bounds(node.body.location) + braces ? stmts : on_bodystmt(stmts, nil, nil, nil) + when BeginNode + visit_body_node(node.parameters&.location || node.opening_loc, node.body) + else + raise + end + + if braces + bounds(node.location) + on_brace_block(parameters, body) + else + bounds(node.location) + on_do_block(parameters, body) + end + end + + # def foo(&bar); end + # ^^^^ + def visit_block_parameter_node(node) + if node.name_loc.nil? + bounds(node.location) + on_blockarg(nil) + else + bounds(node.name_loc) + name = visit_token(node.name.to_s) + + bounds(node.location) + on_blockarg(name) + end + end + + # A block's parameters. + def visit_block_parameters_node(node) + parameters = + if node.parameters.nil? + on_params(nil, nil, nil, nil, nil, nil, nil) + else + visit(node.parameters) + end + + locals = + if node.locals.any? + visit_all(node.locals) + else + false + end + + bounds(node.location) + on_block_var(parameters, locals) + end + + # break + # ^^^^^ + # + # break foo + # ^^^^^^^^^ + def visit_break_node(node) + if node.arguments.nil? + bounds(node.location) + on_break(on_args_new) + else + arguments = visit(node.arguments) + + bounds(node.location) + on_break(arguments) + end + end + + # foo + # ^^^ + # + # foo.bar + # ^^^^^^^ + # + # foo.bar() {} + # ^^^^^^^^^^^^ + def visit_call_node(node) + if node.call_operator_loc.nil? + case node.name + when :[] + receiver = visit(node.receiver) + arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + + bounds(node.location) + call = on_aref(receiver, arguments) + + if block.nil? + call + else + bounds(node.location) + on_method_add_block(call, block) + end + when :[]= + receiver = visit(node.receiver) + + *arguments, last_argument = node.arguments.arguments + arguments << node.block if !node.block.nil? + + arguments = + if arguments.any? + args = visit_arguments(arguments) + + if !node.block.nil? + args + else + bounds(arguments.first.location) + on_args_add_block(args, false) + end + end + + bounds(node.location) + call = on_aref_field(receiver, arguments) + value = visit_write_value(last_argument) + + bounds(last_argument.location) + on_assign(call, value) + when :-@, :+@, :~ + receiver = visit(node.receiver) + + bounds(node.location) + on_unary(node.name, receiver) + when :! + receiver = visit(node.receiver) + + bounds(node.location) + on_unary(node.message == "not" ? :not : :!, receiver) + when *BINARY_OPERATORS + receiver = visit(node.receiver) + value = visit(node.arguments.arguments.first) + + bounds(node.location) + on_binary(receiver, node.name, value) + else + bounds(node.message_loc) + message = visit_token(node.message, false) + + if node.variable_call? + on_vcall(message) + else + arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location)) + call = + if node.opening_loc.nil? && arguments&.any? + bounds(node.location) + on_command(message, arguments) + elsif !node.opening_loc.nil? + bounds(node.location) + on_method_add_arg(on_fcall(message), on_arg_paren(arguments)) + else + bounds(node.location) + on_method_add_arg(on_fcall(message), on_args_new) + end + + if block.nil? + call + else + bounds(node.block.location) + on_method_add_block(call, block) + end + end + end + else + receiver = visit(node.receiver) + + bounds(node.call_operator_loc) + call_operator = visit_token(node.call_operator) + + message = + if node.message_loc.nil? + :call + else + bounds(node.message_loc) + visit_token(node.message, false) + end + + if node.name.end_with?("=") && !node.message.end_with?("=") && !node.arguments.nil? && node.block.nil? + value = visit_write_value(node.arguments.arguments.first) + + bounds(node.location) + on_assign(on_field(receiver, call_operator, message), value) + else + arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location)) + call = + if node.opening_loc.nil? + bounds(node.location) + + if node.arguments.nil? && !node.block.is_a?(BlockArgumentNode) + on_call(receiver, call_operator, message) + else + on_command_call(receiver, call_operator, message, arguments) + end + else + bounds(node.opening_loc) + arguments = on_arg_paren(arguments) + + bounds(node.location) + on_method_add_arg(on_call(receiver, call_operator, message), arguments) + end + + if block.nil? + call + else + bounds(node.block.location) + on_method_add_block(call, block) + end + end + end + end + + # Visit the arguments and block of a call node and return the arguments + # and block as they should be used. + private def visit_call_node_arguments(arguments_node, block_node, trailing_comma) + arguments = arguments_node&.arguments || [] + block = block_node + + if block.is_a?(BlockArgumentNode) + arguments << block + block = nil + end + + [ + if arguments.length == 1 && arguments.first.is_a?(ForwardingArgumentsNode) + visit(arguments.first) + elsif arguments.any? + args = visit_arguments(arguments) + + if block_node.is_a?(BlockArgumentNode) || arguments.last.is_a?(ForwardingArgumentsNode) || command?(arguments.last) || trailing_comma + args + else + bounds(arguments.first.location) + on_args_add_block(args, false) + end + end, + visit(block) + ] + end + + # Returns true if the given node is a command node. + private def command?(node) + node.is_a?(CallNode) && + node.opening_loc.nil? && + (!node.arguments.nil? || node.block.is_a?(BlockArgumentNode)) && + !BINARY_OPERATORS.include?(node.name) + end + + # foo.bar += baz + # ^^^^^^^^^^^^^^^ + def visit_call_operator_write_node(node) + receiver = visit(node.receiver) + + bounds(node.call_operator_loc) + call_operator = visit_token(node.call_operator) + + bounds(node.message_loc) + message = visit_token(node.message) + + bounds(node.location) + target = on_field(receiver, call_operator, message) + + bounds(node.operator_loc) + operator = on_op("#{node.operator}=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # foo.bar &&= baz + # ^^^^^^^^^^^^^^^ + def visit_call_and_write_node(node) + receiver = visit(node.receiver) + + bounds(node.call_operator_loc) + call_operator = visit_token(node.call_operator) + + bounds(node.message_loc) + message = visit_token(node.message) + + bounds(node.location) + target = on_field(receiver, call_operator, message) + + bounds(node.operator_loc) + operator = on_op("&&=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # foo.bar ||= baz + # ^^^^^^^^^^^^^^^ + def visit_call_or_write_node(node) + receiver = visit(node.receiver) + + bounds(node.call_operator_loc) + call_operator = visit_token(node.call_operator) + + bounds(node.message_loc) + message = visit_token(node.message) + + bounds(node.location) + target = on_field(receiver, call_operator, message) + + bounds(node.operator_loc) + operator = on_op("||=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # foo.bar, = 1 + # ^^^^^^^ + def visit_call_target_node(node) + if node.call_operator == "::" + receiver = visit(node.receiver) + + bounds(node.message_loc) + message = visit_token(node.message) + + bounds(node.location) + on_const_path_field(receiver, message) + else + receiver = visit(node.receiver) + + bounds(node.call_operator_loc) + call_operator = visit_token(node.call_operator) + + bounds(node.message_loc) + message = visit_token(node.message) + + bounds(node.location) + on_field(receiver, call_operator, message) + end + end + + # foo => bar => baz + # ^^^^^^^^^^ + def visit_capture_pattern_node(node) + value = visit(node.value) + target = visit(node.target) + + bounds(node.location) + on_binary(value, :"=>", target) end - def _dispatch0; end # :nodoc: - def _dispatch1(_); end # :nodoc: - def _dispatch2(_, _); end # :nodoc: - def _dispatch3(_, _, _); end # :nodoc: - def _dispatch4(_, _, _, _); end # :nodoc: - def _dispatch5(_, _, _, _, _); end # :nodoc: - def _dispatch7(_, _, _, _, _, _, _); end # :nodoc: + # case foo; when bar; end + # ^^^^^^^^^^^^^^^^^^^^^^^ + def visit_case_node(node) + predicate = visit(node.predicate) + clauses = + node.conditions.reverse_each.inject(visit(node.consequent)) do |consequent, condition| + on_when(*visit(condition), consequent) + end + + bounds(node.location) + on_case(predicate, clauses) + end + + # case foo; in bar; end + # ^^^^^^^^^^^^^^^^^^^^^ + def visit_case_match_node(node) + predicate = visit(node.predicate) + clauses = + node.conditions.reverse_each.inject(visit(node.consequent)) do |consequent, condition| + on_in(*visit(condition), consequent) + end + + bounds(node.location) + on_case(predicate, clauses) + end + + # class Foo; end + # ^^^^^^^^^^^^^^ + def visit_class_node(node) + constant_path = + if node.constant_path.is_a?(ConstantReadNode) + bounds(node.constant_path.location) + on_const_ref(on_const(node.constant_path.name.to_s)) + else + visit(node.constant_path) + end + + superclass = visit(node.superclass) + bodystmt = visit_body_node(node.superclass&.location || node.constant_path.location, node.body, node.superclass.nil?) + + bounds(node.location) + on_class(constant_path, superclass, bodystmt) + end + + # @@foo + # ^^^^^ + def visit_class_variable_read_node(node) + bounds(node.location) + on_var_ref(on_cvar(node.slice)) + end + + # @@foo = 1 + # ^^^^^^^^^ + # + # @@foo, @@bar = 1 + # ^^^^^ ^^^^^ + def visit_class_variable_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_cvar(node.name.to_s)) + value = visit_write_value(node.value) + + bounds(node.location) + on_assign(target, value) + end + + # @@foo += bar + # ^^^^^^^^^^^^ + def visit_class_variable_operator_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_cvar(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("#{node.operator}=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # @@foo &&= bar + # ^^^^^^^^^^^^^ + def visit_class_variable_and_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_cvar(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("&&=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # @@foo ||= bar + # ^^^^^^^^^^^^^ + def visit_class_variable_or_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_cvar(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("||=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # @@foo, = bar + # ^^^^^ + def visit_class_variable_target_node(node) + bounds(node.location) + on_var_field(on_cvar(node.name.to_s)) + end - alias_method :on_parse_error, :_dispatch1 - alias_method :on_magic_comment, :_dispatch2 + # Foo + # ^^^ + def visit_constant_read_node(node) + bounds(node.location) + on_var_ref(on_const(node.name.to_s)) + end + + # Foo = 1 + # ^^^^^^^ + # + # Foo, Bar = 1 + # ^^^ ^^^ + def visit_constant_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_const(node.name.to_s)) + value = visit_write_value(node.value) + + bounds(node.location) + on_assign(target, value) + end + + # Foo += bar + # ^^^^^^^^^^^ + def visit_constant_operator_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_const(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("#{node.operator}=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # Foo &&= bar + # ^^^^^^^^^^^^ + def visit_constant_and_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_const(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("&&=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # Foo ||= bar + # ^^^^^^^^^^^^ + def visit_constant_or_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_const(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("||=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # Foo, = bar + # ^^^ + def visit_constant_target_node(node) + bounds(node.location) + on_var_field(on_const(node.name.to_s)) + end + + # Foo::Bar + # ^^^^^^^^ + def visit_constant_path_node(node) + if node.parent.nil? + bounds(node.child.location) + child = on_const(node.child.name.to_s) + + bounds(node.location) + on_top_const_ref(child) + else + parent = visit(node.parent) + + bounds(node.child.location) + child = on_const(node.child.name.to_s) + + bounds(node.location) + on_const_path_ref(parent, child) + end + end + + # Foo::Bar = 1 + # ^^^^^^^^^^^^ + # + # Foo::Foo, Bar::Bar = 1 + # ^^^^^^^^ ^^^^^^^^ + def visit_constant_path_write_node(node) + target = visit_constant_path_write_node_target(node.target) + value = visit_write_value(node.value) + + bounds(node.location) + on_assign(target, value) + end + + # Visit a constant path that is part of a write node. + private def visit_constant_path_write_node_target(node) + if node.parent.nil? + bounds(node.child.location) + child = on_const(node.child.name.to_s) + + bounds(node.location) + on_top_const_field(child) + else + parent = visit(node.parent) + + bounds(node.child.location) + child = on_const(node.child.name.to_s) + + bounds(node.location) + on_const_path_field(parent, child) + end + end + + # Foo::Bar += baz + # ^^^^^^^^^^^^^^^ + def visit_constant_path_operator_write_node(node) + target = visit_constant_path_write_node_target(node.target) + value = visit(node.value) + + bounds(node.operator_loc) + operator = on_op("#{node.operator}=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # Foo::Bar &&= baz + # ^^^^^^^^^^^^^^^^ + def visit_constant_path_and_write_node(node) + target = visit_constant_path_write_node_target(node.target) + value = visit(node.value) + + bounds(node.operator_loc) + operator = on_op("&&=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # Foo::Bar ||= baz + # ^^^^^^^^^^^^^^^^ + def visit_constant_path_or_write_node(node) + target = visit_constant_path_write_node_target(node.target) + value = visit(node.value) + + bounds(node.operator_loc) + operator = on_op("||=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # Foo::Bar, = baz + # ^^^^^^^^ + def visit_constant_path_target_node(node) + visit_constant_path_write_node_target(node) + end + + # def foo; end + # ^^^^^^^^^^^^ + # + # def self.foo; end + # ^^^^^^^^^^^^^^^^^ + def visit_def_node(node) + receiver = visit(node.receiver) + operator = + if !node.operator_loc.nil? + bounds(node.operator_loc) + visit_token(node.operator) + end + + bounds(node.name_loc) + name = visit_token(node.name_loc.slice) + + parameters = + if node.parameters.nil? + bounds(node.location) + on_params(nil, nil, nil, nil, nil, nil, nil) + else + visit(node.parameters) + end + + if !node.lparen_loc.nil? + bounds(node.lparen_loc) + parameters = on_paren(parameters) + end + + bodystmt = + if node.equal_loc.nil? + visit_body_node(node.rparen_loc || node.end_keyword_loc, node.body) + else + body = visit(node.body.body.first) + + bounds(node.body.location) + on_bodystmt(body, nil, nil, nil) + end + + bounds(node.location) + if receiver.nil? + on_def(name, parameters, bodystmt) + else + on_defs(receiver, operator, name, parameters, bodystmt) + end + end + + # defined? a + # ^^^^^^^^^^ + # + # defined?(a) + # ^^^^^^^^^^^ + def visit_defined_node(node) + bounds(node.location) + on_defined(visit(node.value)) + end + + # if foo then bar else baz end + # ^^^^^^^^^^^^ + def visit_else_node(node) + statements = + if node.statements.nil? + [nil] + else + body = node.statements.body + body.unshift(nil) if void_stmt?(node.else_keyword_loc, node.statements.body[0].location, false) + body + end + + bounds(node.location) + on_else(visit_statements_node_body(statements)) + end + + # "foo #{bar}" + # ^^^^^^ + def visit_embedded_statements_node(node) + bounds(node.opening_loc) + on_embexpr_beg(node.opening) + + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + + bounds(node.closing_loc) + on_embexpr_end(node.closing) + + bounds(node.location) + on_string_embexpr(statements) + end + + # "foo #@bar" + # ^^^^^ + def visit_embedded_variable_node(node) + bounds(node.operator_loc) + on_embvar(node.operator) + + variable = visit(node.variable) + + bounds(node.location) + on_string_dvar(variable) + end + + # Visit an EnsureNode node. + def visit_ensure_node(node) + statements = + if node.statements.nil? + [nil] + else + body = node.statements.body + body.unshift(nil) if void_stmt?(node.ensure_keyword_loc, body[0].location, false) + body + end + + statements = visit_statements_node_body(statements) + + bounds(node.location) + on_ensure(statements) + end + + # false + # ^^^^^ + def visit_false_node(node) + bounds(node.location) + on_var_ref(on_kw("false")) + end + + # foo => [*, bar, *] + # ^^^^^^^^^^^ + def visit_find_pattern_node(node) + constant = visit(node.constant) + left = + if node.left.expression.nil? + bounds(node.left.location) + on_var_field(nil) + else + visit(node.left.expression) + end + + requireds = visit_all(node.requireds) if node.requireds.any? + right = + if node.right.expression.nil? + bounds(node.right.location) + on_var_field(nil) + else + visit(node.right.expression) + end + + bounds(node.location) + on_fndptn(constant, left, requireds, right) + end + + # if foo .. bar; end + # ^^^^^^^^^^ + def visit_flip_flop_node(node) + left = visit(node.left) + right = visit(node.right) + + bounds(node.location) + if node.exclude_end? + on_dot3(left, right) + else + on_dot2(left, right) + end + end + + # 1.0 + # ^^^ + def visit_float_node(node) + visit_number_node(node) { |text| on_float(text) } + end + + # for foo in bar do end + # ^^^^^^^^^^^^^^^^^^^^^ + def visit_for_node(node) + index = visit(node.index) + collection = visit(node.collection) + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + + bounds(node.location) + on_for(index, collection, statements) + end + + # def foo(...); bar(...); end + # ^^^ + def visit_forwarding_arguments_node(node) + bounds(node.location) + on_args_forward + end + + # def foo(...); end + # ^^^ + def visit_forwarding_parameter_node(node) + bounds(node.location) + on_args_forward + end + + # super + # ^^^^^ + # + # super {} + # ^^^^^^^^ + def visit_forwarding_super_node(node) + if node.block.nil? + bounds(node.location) + on_zsuper + else + block = visit(node.block) + + bounds(node.location) + on_method_add_block(on_zsuper, block) + end + end + + # $foo + # ^^^^ + def visit_global_variable_read_node(node) + bounds(node.location) + on_var_ref(on_gvar(node.name.to_s)) + end + + # $foo = 1 + # ^^^^^^^^ + # + # $foo, $bar = 1 + # ^^^^ ^^^^ + def visit_global_variable_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_gvar(node.name.to_s)) + value = visit_write_value(node.value) + + bounds(node.location) + on_assign(target, value) + end + + # $foo += bar + # ^^^^^^^^^^^ + def visit_global_variable_operator_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_gvar(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("#{node.operator}=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # $foo &&= bar + # ^^^^^^^^^^^^ + def visit_global_variable_and_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_gvar(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("&&=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # $foo ||= bar + # ^^^^^^^^^^^^ + def visit_global_variable_or_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_gvar(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("||=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # $foo, = bar + # ^^^^ + def visit_global_variable_target_node(node) + bounds(node.location) + on_var_field(on_gvar(node.name.to_s)) + end + + # {} + # ^^ + def visit_hash_node(node) + elements = + if node.elements.any? + args = visit_all(node.elements) + + bounds(node.elements.first.location) + on_assoclist_from_args(args) + end + + bounds(node.location) + on_hash(elements) + end + + # foo => {} + # ^^ + def visit_hash_pattern_node(node) + constant = visit(node.constant) + elements = + if node.elements.any? || !node.rest.nil? + node.elements.map do |element| + [ + if (key = element.key).opening_loc.nil? + visit(key) + else + bounds(key.value_loc) + if (value = key.value).empty? + on_string_content + else + on_string_add(on_string_content, on_tstring_content(value)) + end + end, + visit(element.value) + ] + end + end + + rest = + case node.rest + when AssocSplatNode + visit(node.rest.value) + when NoKeywordsParameterNode + bounds(node.rest.location) + on_var_field(visit(node.rest)) + end + + bounds(node.location) + on_hshptn(constant, elements, rest) + end + + # if foo then bar end + # ^^^^^^^^^^^^^^^^^^^ + # + # bar if foo + # ^^^^^^^^^^ + # + # foo ? bar : baz + # ^^^^^^^^^^^^^^^ + def visit_if_node(node) + if node.then_keyword == "?" + predicate = visit(node.predicate) + truthy = visit(node.statements.body.first) + falsy = visit(node.consequent.statements.body.first) + + bounds(node.location) + on_ifop(predicate, truthy, falsy) + elsif node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset) + predicate = visit(node.predicate) + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + consequent = visit(node.consequent) + + bounds(node.location) + if node.if_keyword == "if" + on_if(predicate, statements, consequent) + else + on_elsif(predicate, statements, consequent) + end + else + statements = visit(node.statements.body.first) + predicate = visit(node.predicate) + + bounds(node.location) + on_if_mod(predicate, statements) + end + end + + # 1i + # ^^ + def visit_imaginary_node(node) + visit_number_node(node) { |text| on_imaginary(text) } + end + + # { foo: } + # ^^^^ + def visit_implicit_node(node) + end + + # foo { |bar,| } + # ^ + def visit_implicit_rest_node(node) + bounds(node.location) + on_excessed_comma + end + + # case foo; in bar; end + # ^^^^^^^^^^^^^^^^^^^^^ + 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_pattern_node(node.pattern) + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + + [pattern, statements] + end + + # foo[bar] += baz + # ^^^^^^^^^^^^^^^ + def visit_index_operator_write_node(node) + receiver = visit(node.receiver) + arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + + bounds(node.location) + target = on_aref_field(receiver, arguments) + + bounds(node.operator_loc) + operator = on_op("#{node.operator}=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # foo[bar] &&= baz + # ^^^^^^^^^^^^^^^^ + def visit_index_and_write_node(node) + receiver = visit(node.receiver) + arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + + bounds(node.location) + target = on_aref_field(receiver, arguments) + + bounds(node.operator_loc) + operator = on_op("&&=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # foo[bar] ||= baz + # ^^^^^^^^^^^^^^^^ + def visit_index_or_write_node(node) + receiver = visit(node.receiver) + arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + + bounds(node.location) + target = on_aref_field(receiver, arguments) + + bounds(node.operator_loc) + operator = on_op("||=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # foo[bar], = 1 + # ^^^^^^^^ + def visit_index_target_node(node) + receiver = visit(node.receiver) + arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + + bounds(node.location) + on_aref_field(receiver, arguments) + end + + # @foo + # ^^^^ + def visit_instance_variable_read_node(node) + bounds(node.location) + on_var_ref(on_ivar(node.name.to_s)) + end + + # @foo = 1 + # ^^^^^^^^ + def visit_instance_variable_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_ivar(node.name.to_s)) + value = visit_write_value(node.value) + + bounds(node.location) + on_assign(target, value) + end + + # @foo += bar + # ^^^^^^^^^^^ + def visit_instance_variable_operator_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_ivar(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("#{node.operator}=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # @foo &&= bar + # ^^^^^^^^^^^^ + def visit_instance_variable_and_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_ivar(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("&&=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # @foo ||= bar + # ^^^^^^^^^^^^ + def visit_instance_variable_or_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_ivar(node.name.to_s)) + + bounds(node.operator_loc) + operator = on_op("||=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # @foo, = bar + # ^^^^ + def visit_instance_variable_target_node(node) + bounds(node.location) + on_var_field(on_ivar(node.name.to_s)) + end + + # 1 + # ^ + def visit_integer_node(node) + visit_number_node(node) { |text| on_int(text) } + end + + # if /foo #{bar}/ then end + # ^^^^^^^^^^^^ + def visit_interpolated_match_last_line_node(node) + bounds(node.opening_loc) + on_regexp_beg(node.opening) + + bounds(node.parts.first.location) + parts = + node.parts.inject(on_regexp_new) do |content, part| + on_regexp_add(content, visit_string_content(part)) + end + + bounds(node.closing_loc) + closing = on_regexp_end(node.closing) + + bounds(node.location) + on_regexp_literal(parts, closing) + end + + # /foo #{bar}/ + # ^^^^^^^^^^^^ + def visit_interpolated_regular_expression_node(node) + bounds(node.opening_loc) + on_regexp_beg(node.opening) + + bounds(node.parts.first.location) + parts = + node.parts.inject(on_regexp_new) do |content, part| + on_regexp_add(content, visit_string_content(part)) + end + + bounds(node.closing_loc) + closing = on_regexp_end(node.closing) + + bounds(node.location) + on_regexp_literal(parts, closing) + end + + # "foo #{bar}" + # ^^^^^^^^^^^^ + def visit_interpolated_string_node(node) + if node.opening&.start_with?("<<~") + heredoc = visit_heredoc_string_node(node) + + bounds(node.location) + on_string_literal(heredoc) + elsif !node.heredoc? && node.parts.length > 1 && node.parts.any? { |part| (part.is_a?(StringNode) || part.is_a?(InterpolatedStringNode)) && !part.opening_loc.nil? } + first, *rest = node.parts + rest.inject(visit(first)) do |content, part| + concat = visit(part) + + bounds(part.location) + on_string_concat(content, concat) + end + else + bounds(node.parts.first.location) + parts = + node.parts.inject(on_string_content) do |content, part| + on_string_add(content, visit_string_content(part)) + end + + bounds(node.location) + on_string_literal(parts) + end + end + + # :"foo #{bar}" + # ^^^^^^^^^^^^^ + def visit_interpolated_symbol_node(node) + bounds(node.parts.first.location) + parts = + node.parts.inject(on_string_content) do |content, part| + on_string_add(content, visit_string_content(part)) + end + + bounds(node.location) + on_dyna_symbol(parts) + end + + # `foo #{bar}` + # ^^^^^^^^^^^^ + def visit_interpolated_x_string_node(node) + if node.opening.start_with?("<<~") + heredoc = visit_heredoc_x_string_node(node) + + bounds(node.location) + on_xstring_literal(heredoc) + else + bounds(node.parts.first.location) + parts = + node.parts.inject(on_xstring_new) do |content, part| + on_xstring_add(content, visit_string_content(part)) + end + + bounds(node.location) + on_xstring_literal(parts) + end + end + + # Visit an individual part of a string-like node. + private def visit_string_content(part) + if part.is_a?(StringNode) + bounds(part.content_loc) + on_tstring_content(part.content) + else + visit(part) + end + end + + # -> { it } + # ^^^^^^^^^ + def visit_it_parameters_node(node) + end + + # foo(bar: baz) + # ^^^^^^^^ + def visit_keyword_hash_node(node) + elements = visit_all(node.elements) + + bounds(node.location) + on_bare_assoc_hash(elements) + end + + # def foo(**bar); end + # ^^^^^ + # + # def foo(**); end + # ^^ + def visit_keyword_rest_parameter_node(node) + if node.name_loc.nil? + bounds(node.location) + on_kwrest_param(nil) + else + bounds(node.name_loc) + name = on_ident(node.name.to_s) + + bounds(node.location) + on_kwrest_param(name) + end + end + + # -> {} + def visit_lambda_node(node) + bounds(node.operator_loc) + on_tlambda(node.operator) + + parameters = + if node.parameters.is_a?(BlockParametersNode) + # Ripper does not track block-locals within lambdas, so we skip + # directly to the parameters here. + params = + if node.parameters.parameters.nil? + bounds(node.location) + on_params(nil, nil, nil, nil, nil, nil, nil) + else + visit(node.parameters.parameters) + end + + if node.parameters.opening_loc.nil? + params + else + bounds(node.parameters.opening_loc) + on_paren(params) + end + else + bounds(node.location) + on_params(nil, nil, nil, nil, nil, nil, nil) + end + + braces = node.opening == "{" + if braces + bounds(node.opening_loc) + on_tlambeg(node.opening) + end + + body = + case node.body + when nil + bounds(node.location) + stmts = on_stmts_add(on_stmts_new, on_void_stmt) + + bounds(node.location) + braces ? stmts : on_bodystmt(stmts, nil, nil, nil) + when StatementsNode + stmts = node.body.body + stmts.unshift(nil) if void_stmt?(node.parameters&.location || node.opening_loc, node.body.location, false) + stmts = visit_statements_node_body(stmts) + + bounds(node.body.location) + braces ? stmts : on_bodystmt(stmts, nil, nil, nil) + when BeginNode + visit_body_node(node.opening_loc, node.body) + else + raise + end + + bounds(node.location) + on_lambda(parameters, body) + end + + # foo + # ^^^ + def visit_local_variable_read_node(node) + bounds(node.location) + + if node.name == :"0it" + on_vcall(on_ident(node.slice)) + else + on_var_ref(on_ident(node.slice)) + end + end + + # foo = 1 + # ^^^^^^^ + def visit_local_variable_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_ident(node.name_loc.slice)) + value = visit_write_value(node.value) + + bounds(node.location) + on_assign(target, value) + end + + # foo += bar + # ^^^^^^^^^^ + def visit_local_variable_operator_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_ident(node.name_loc.slice)) + + bounds(node.operator_loc) + operator = on_op("#{node.operator}=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # foo &&= bar + # ^^^^^^^^^^^ + def visit_local_variable_and_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_ident(node.name_loc.slice)) + + bounds(node.operator_loc) + operator = on_op("&&=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # foo ||= bar + # ^^^^^^^^^^^ + def visit_local_variable_or_write_node(node) + bounds(node.name_loc) + target = on_var_field(on_ident(node.name_loc.slice)) + + bounds(node.operator_loc) + operator = on_op("||=") + value = visit_write_value(node.value) + + bounds(node.location) + on_opassign(target, operator, value) + end + + # foo, = bar + # ^^^ + def visit_local_variable_target_node(node) + bounds(node.location) + on_var_field(on_ident(node.name.to_s)) + end + + # if /foo/ then end + # ^^^^^ + def visit_match_last_line_node(node) + bounds(node.opening_loc) + on_regexp_beg(node.opening) + + bounds(node.content_loc) + tstring_content = on_tstring_content(node.content) + + bounds(node.closing_loc) + closing = on_regexp_end(node.closing) + + on_regexp_literal(on_regexp_add(on_regexp_new, tstring_content), closing) + end + + # foo in bar + # ^^^^^^^^^^ + def visit_match_predicate_node(node) + value = visit(node.value) + pattern = on_in(visit_pattern_node(node.pattern), nil, nil) + + on_case(value, pattern) + end + + # foo => bar + # ^^^^^^^^^^ + def visit_match_required_node(node) + value = visit(node.value) + pattern = on_in(visit_pattern_node(node.pattern), nil, nil) + + on_case(value, pattern) + end + + # /(?foo)/ =~ bar + # ^^^^^^^^^^^^^^^^^^^^ + def visit_match_write_node(node) + visit(node.call) + end + + # A node that is missing from the syntax tree. This is only used in the + # case of a syntax error. + def visit_missing_node(node) + raise "Cannot visit missing nodes directly." + end + + # module Foo; end + # ^^^^^^^^^^^^^^^ + def visit_module_node(node) + constant_path = + if node.constant_path.is_a?(ConstantReadNode) + bounds(node.constant_path.location) + on_const_ref(on_const(node.constant_path.name.to_s)) + else + visit(node.constant_path) + end + + bodystmt = visit_body_node(node.constant_path.location, node.body, true) + + bounds(node.location) + on_module(constant_path, bodystmt) + end + + # (foo, bar), bar = qux + # ^^^^^^^^^^ + def visit_multi_target_node(node) + bounds(node.location) + targets = visit_multi_target_node_targets(node.lefts, node.rest, node.rights, true) + + if node.lparen_loc.nil? + targets + else + bounds(node.lparen_loc) + on_mlhs_paren(targets) + end + end + + # Visit the targets of a multi-target node. + private def visit_multi_target_node_targets(lefts, rest, rights, skippable) + if skippable && lefts.length == 1 && lefts.first.is_a?(MultiTargetNode) && rest.nil? && rights.empty? + return visit(lefts.first) + end + + mlhs = on_mlhs_new + + lefts.each do |left| + bounds(left.location) + mlhs = on_mlhs_add(mlhs, visit(left)) + end + + case rest + when nil + # do nothing + when ImplicitRestNode + # these do not get put into the generated tree + bounds(rest.location) + on_excessed_comma + else + bounds(rest.location) + mlhs = on_mlhs_add_star(mlhs, visit(rest)) + end + + if rights.any? + bounds(rights.first.location) + post = on_mlhs_new + + rights.each do |right| + bounds(right.location) + post = on_mlhs_add(post, visit(right)) + end + + mlhs = on_mlhs_add_post(mlhs, post) + end + + mlhs + end + + # foo, bar = baz + # ^^^^^^^^^^^^^^ + def visit_multi_write_node(node) + bounds(node.location) + targets = visit_multi_target_node_targets(node.lefts, node.rest, node.rights, true) + + unless node.lparen_loc.nil? + bounds(node.lparen_loc) + targets = on_mlhs_paren(targets) + end + + value = visit_write_value(node.value) + + bounds(node.location) + on_massign(targets, value) + end + + # next + # ^^^^ + # + # next foo + # ^^^^^^^^ + def visit_next_node(node) + if node.arguments.nil? + bounds(node.location) + on_next(on_args_new) + else + arguments = visit(node.arguments) + + bounds(node.location) + on_next(arguments) + end + end + + # nil + # ^^^ + def visit_nil_node(node) + bounds(node.location) + on_var_ref(on_kw("nil")) + end + + # def foo(**nil); end + # ^^^^^ + def visit_no_keywords_parameter_node(node) + bounds(node.location) + on_nokw_param(nil) + + :nil + end + + # -> { _1 + _2 } + # ^^^^^^^^^^^^^^ + def visit_numbered_parameters_node(node) + end + + # $1 + # ^^ + def visit_numbered_reference_read_node(node) + bounds(node.location) + on_backref(node.slice) + end + + # def foo(bar: baz); end + # ^^^^^^^^ + def visit_optional_keyword_parameter_node(node) + bounds(node.name_loc) + name = on_label("#{node.name}:") + value = visit(node.value) + + [name, value] + end + + # def foo(bar = 1); end + # ^^^^^^^ + def visit_optional_parameter_node(node) + bounds(node.name_loc) + name = visit_token(node.name.to_s) + value = visit(node.value) + + [name, value] + end + + # a or b + # ^^^^^^ + def visit_or_node(node) + left = visit(node.left) + right = visit(node.right) + + bounds(node.location) + on_binary(left, node.operator.to_sym, right) + end + + # def foo(bar, *baz); end + # ^^^^^^^^^ + def visit_parameters_node(node) + requireds = node.requireds.map { |required| required.is_a?(MultiTargetNode) ? visit_destructured_parameter_node(required) : visit(required) } if node.requireds.any? + optionals = visit_all(node.optionals) if node.optionals.any? + rest = visit(node.rest) + posts = node.posts.map { |post| post.is_a?(MultiTargetNode) ? visit_destructured_parameter_node(post) : visit(post) } if node.posts.any? + keywords = visit_all(node.keywords) if node.keywords.any? + keyword_rest = visit(node.keyword_rest) + block = visit(node.block) + + bounds(node.location) + on_params(requireds, optionals, rest, posts, keywords, keyword_rest, block) + end + + # Visit a destructured positional parameter node. + private def visit_destructured_parameter_node(node) + bounds(node.location) + targets = visit_multi_target_node_targets(node.lefts, node.rest, node.rights, false) + + bounds(node.lparen_loc) + on_mlhs_paren(targets) + end + + # () + # ^^ + # + # (1) + # ^^^ + def visit_parentheses_node(node) + body = + if node.body.nil? + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.body) + end + + bounds(node.location) + on_paren(body) + end + + # foo => ^(bar) + # ^^^^^^ + def visit_pinned_expression_node(node) + expression = visit(node.expression) + + bounds(node.location) + on_begin(expression) + end + + # foo = 1 and bar => ^foo + # ^^^^ + def visit_pinned_variable_node(node) + visit(node.variable) + end + + # END {} + # ^^^^^^ + def visit_post_execution_node(node) + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + + bounds(node.location) + on_END(statements) + end + + # BEGIN {} + # ^^^^^^^^ + def visit_pre_execution_node(node) + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + + bounds(node.location) + on_BEGIN(statements) + end + + # The top-level program node. + def visit_program_node(node) + body = node.statements.body + body << nil if body.empty? + statements = visit_statements_node_body(body) + + bounds(node.location) + on_program(statements) + end + + # 0..5 + # ^^^^ + def visit_range_node(node) + left = visit(node.left) + right = visit(node.right) + + bounds(node.location) + if node.exclude_end? + on_dot3(left, right) + else + on_dot2(left, right) + end + end + + # 1r + # ^^ + def visit_rational_node(node) + visit_number_node(node) { |text| on_rational(text) } + end + + # redo + # ^^^^ + def visit_redo_node(node) + bounds(node.location) + on_redo + end + + # /foo/ + # ^^^^^ + def visit_regular_expression_node(node) + bounds(node.opening_loc) + on_regexp_beg(node.opening) + + if node.content.empty? + bounds(node.closing_loc) + closing = on_regexp_end(node.closing) + + on_regexp_literal(on_regexp_new, closing) + else + bounds(node.content_loc) + tstring_content = on_tstring_content(node.content) + + bounds(node.closing_loc) + closing = on_regexp_end(node.closing) + + on_regexp_literal(on_regexp_add(on_regexp_new, tstring_content), closing) + end + end + + # def foo(bar:); end + # ^^^^ + def visit_required_keyword_parameter_node(node) + bounds(node.name_loc) + [on_label("#{node.name}:"), false] + end + + # def foo(bar); end + # ^^^ + def visit_required_parameter_node(node) + bounds(node.location) + on_ident(node.name.to_s) + end + + # foo rescue bar + # ^^^^^^^^^^^^^^ + def visit_rescue_modifier_node(node) + expression = visit_write_value(node.expression) + rescue_expression = visit(node.rescue_expression) + + bounds(node.location) + on_rescue_mod(expression, rescue_expression) + end + + # begin; rescue; end + # ^^^^^^^ + def visit_rescue_node(node) + exceptions = + case node.exceptions.length + when 0 + nil + when 1 + if (exception = node.exceptions.first).is_a?(SplatNode) + bounds(exception.location) + on_mrhs_add_star(on_mrhs_new, visit(exception)) + else + [visit(node.exceptions.first)] + end + else + bounds(node.location) + length = node.exceptions.length + + node.exceptions.each_with_index.inject(on_args_new) do |mrhs, (exception, index)| + arg = visit(exception) + + bounds(exception.location) + mrhs = on_mrhs_new_from_args(mrhs) if index == length - 1 + + if exception.is_a?(SplatNode) + if index == length - 1 + on_mrhs_add_star(mrhs, arg) + else + on_args_add_star(mrhs, arg) + end + else + if index == length - 1 + on_mrhs_add(mrhs, arg) + else + on_args_add(mrhs, arg) + end + end + end + end + + reference = visit(node.reference) + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + + consequent = visit(node.consequent) + + bounds(node.location) + on_rescue(exceptions, reference, statements, consequent) + end + + # def foo(*bar); end + # ^^^^ + # + # def foo(*); end + # ^ + def visit_rest_parameter_node(node) + if node.name_loc.nil? + bounds(node.location) + on_rest_param(nil) + else + bounds(node.name_loc) + on_rest_param(visit_token(node.name.to_s)) + end + end + + # retry + # ^^^^^ + def visit_retry_node(node) + bounds(node.location) + on_retry + end + + # return + # ^^^^^^ + # + # return 1 + # ^^^^^^^^ + def visit_return_node(node) + if node.arguments.nil? + bounds(node.location) + on_return0 + else + arguments = visit(node.arguments) + + bounds(node.location) + on_return(arguments) + end + end + + # self + # ^^^^ + def visit_self_node(node) + bounds(node.location) + 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) + expression = visit(node.expression) + bodystmt = visit_body_node(node.body&.location || node.end_keyword_loc, node.body) + + bounds(node.location) + on_sclass(expression, bodystmt) + end + + # __ENCODING__ + # ^^^^^^^^^^^^ + def visit_source_encoding_node(node) + bounds(node.location) + on_var_ref(on_kw("__ENCODING__")) + end + + # __FILE__ + # ^^^^^^^^ + def visit_source_file_node(node) + bounds(node.location) + on_var_ref(on_kw("__FILE__")) + end + + # __LINE__ + # ^^^^^^^^ + def visit_source_line_node(node) + bounds(node.location) + on_var_ref(on_kw("__LINE__")) + end + + # foo(*bar) + # ^^^^ + # + # def foo((bar, *baz)); end + # ^^^^ + # + # def foo(*); bar(*); end + # ^ + def visit_splat_node(node) + visit(node.expression) + end + + # A list of statements. + def visit_statements_node(node) + bounds(node.location) + visit_statements_node_body(node.body) + end + + # Visit the list of statements of a statements node. We support nil + # statements in the list. This would normally not be allowed by the + # structure of the prism parse tree, but we manually add them here so that + # we can mirror Ripper's void stmt. + private def visit_statements_node_body(body) + body.inject(on_stmts_new) do |stmts, stmt| + on_stmts_add(stmts, stmt.nil? ? on_void_stmt : visit(stmt)) + end + end + + # "foo" + # ^^^^^ + def visit_string_node(node) + if (content = node.content).empty? + bounds(node.location) + on_string_literal(on_string_content) + elsif (opening = node.opening) == "?" + bounds(node.location) + on_CHAR("?#{node.content}") + elsif opening.start_with?("<<~") + heredoc = visit_heredoc_string_node(node.to_interpolated) + + bounds(node.location) + on_string_literal(heredoc) + else + bounds(node.content_loc) + tstring_content = on_tstring_content(content) + + bounds(node.location) + on_string_literal(on_string_add(on_string_content, tstring_content)) + end + end + + # Ripper gives back the escaped string content but strips out the common + # leading whitespace. Prism gives back the unescaped string content and + # a location for the escaped string content. Unfortunately these don't + # work well together, so here we need to re-derive the common leading + # whitespace. + private def visit_heredoc_node_whitespace(parts) + common_whitespace = nil + dedent_next = true + + parts.each do |part| + if part.is_a?(StringNode) + if dedent_next && !(content = part.content).chomp.empty? + common_whitespace = [ + common_whitespace || Float::INFINITY, + content[/\A\s*/].each_char.inject(0) do |part_whitespace, char| + char == "\t" ? ((part_whitespace / 8 + 1) * 8) : (part_whitespace + 1) + end + ].min + end + + dedent_next = true + else + dedent_next = false + end + end + + common_whitespace || 0 + end + + # Visit a string that is expressed using a <<~ heredoc. + private def visit_heredoc_node(parts, base) + common_whitespace = visit_heredoc_node_whitespace(parts) + + if common_whitespace == 0 + bounds(parts.first.location) + + string = [] + result = base + + parts.each do |part| + if part.is_a?(StringNode) + if string.empty? + string = [part] + else + string << part + end + else + unless string.empty? + bounds(string[0].location) + result = yield result, on_tstring_content(string.map(&:content).join) + string = [] + end + + result = yield result, visit(part) + end + end + + unless string.empty? + bounds(string[0].location) + result = yield result, on_tstring_content(string.map(&:content).join) + end + + result + else + bounds(parts.first.location) + result = + parts.inject(base) do |string_content, part| + yield string_content, visit_string_content(part) + end + + bounds(parts.first.location) + on_heredoc_dedent(result, common_whitespace) + end + end + + # Visit a heredoc node that is representing a string. + private def visit_heredoc_string_node(node) + bounds(node.opening_loc) + on_heredoc_beg(node.opening) + + bounds(node.location) + result = + visit_heredoc_node(node.parts, on_string_content) do |parts, part| + on_string_add(parts, part) + end + + bounds(node.closing_loc) + on_heredoc_end(node.closing) + + result + end + + # Visit a heredoc node that is representing an xstring. + private def visit_heredoc_x_string_node(node) + bounds(node.opening_loc) + on_heredoc_beg(node.opening) + + bounds(node.location) + result = + visit_heredoc_node(node.parts, on_xstring_new) do |parts, part| + on_xstring_add(parts, part) + end + + bounds(node.closing_loc) + on_heredoc_end(node.closing) + + result + end + + # super(foo) + # ^^^^^^^^^^ + def visit_super_node(node) + arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.rparen_loc || node.location)) + + if !node.lparen_loc.nil? + bounds(node.lparen_loc) + arguments = on_arg_paren(arguments) + end + + bounds(node.location) + call = on_super(arguments) + + if block.nil? + call + else + bounds(node.block.location) + on_method_add_block(call, block) + end + end + + # :foo + # ^^^^ + def visit_symbol_node(node) + if (opening = node.opening)&.match?(/^%s|['"]:?$/) + bounds(node.value_loc) + content = on_string_content + + if !(value = node.value).empty? + content = on_string_add(content, on_tstring_content(value)) + end + + on_dyna_symbol(content) + elsif (closing = node.closing) == ":" + bounds(node.location) + on_label("#{node.value}:") + elsif opening.nil? && node.closing_loc.nil? + bounds(node.value_loc) + on_symbol_literal(visit_token(node.value)) + else + bounds(node.value_loc) + on_symbol_literal(on_symbol(visit_token(node.value))) + end + end + + # true + # ^^^^ + def visit_true_node(node) + bounds(node.location) + on_var_ref(on_kw("true")) + end + + # undef foo + # ^^^^^^^^^ + def visit_undef_node(node) + names = visit_all(node.names) + + bounds(node.location) + on_undef(names) + end + + # unless foo; bar end + # ^^^^^^^^^^^^^^^^^^^ + # + # bar unless foo + # ^^^^^^^^^^^^^^ + def visit_unless_node(node) + if node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset) + predicate = visit(node.predicate) + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + consequent = visit(node.consequent) + + bounds(node.location) + on_unless(predicate, statements, consequent) + else + statements = visit(node.statements.body.first) + predicate = visit(node.predicate) + + bounds(node.location) + on_unless_mod(predicate, statements) + end + end + + # until foo; bar end + # ^^^^^^^^^^^^^^^^^ + # + # bar until foo + # ^^^^^^^^^^^^^ + def visit_until_node(node) + if node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset) + predicate = visit(node.predicate) + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + + bounds(node.location) + on_until(predicate, statements) + else + statements = visit(node.statements.body.first) + predicate = visit(node.predicate) + + bounds(node.location) + on_until_mod(predicate, statements) + end + end + + # case foo; when bar; end + # ^^^^^^^^^^^^^ + def visit_when_node(node) + # This is a special case where we're not going to call on_when directly + # because we don't have access to the consequent. Instead, we'll return + # the component parts and let the parent node handle it. + conditions = visit_arguments(node.conditions) + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + + [conditions, statements] + end + + # while foo; bar end + # ^^^^^^^^^^^^^^^^^^ + # + # bar while foo + # ^^^^^^^^^^^^^ + def visit_while_node(node) + if node.statements.nil? || (node.predicate.location.start_offset < node.statements.location.start_offset) + predicate = visit(node.predicate) + statements = + if node.statements.nil? + bounds(node.location) + on_stmts_add(on_stmts_new, on_void_stmt) + else + visit(node.statements) + end + + bounds(node.location) + on_while(predicate, statements) + else + statements = visit(node.statements.body.first) + predicate = visit(node.predicate) + + bounds(node.location) + on_while_mod(predicate, statements) + end + end + + # `foo` + # ^^^^^ + def visit_x_string_node(node) + if node.unescaped.empty? + bounds(node.location) + on_xstring_literal(on_xstring_new) + elsif node.opening.start_with?("<<~") + heredoc = visit_heredoc_x_string_node(node.to_interpolated) + + bounds(node.location) + on_xstring_literal(heredoc) + else + bounds(node.content_loc) + content = on_tstring_content(node.content) + + bounds(node.location) + on_xstring_literal(on_xstring_add(on_xstring_new, content)) + end + end + + # yield + # ^^^^^ + # + # yield 1 + # ^^^^^^^ + def visit_yield_node(node) + if node.arguments.nil? && node.lparen_loc.nil? + bounds(node.location) + on_yield0 + else + arguments = + if node.arguments.nil? + bounds(node.location) + on_args_new + else + visit(node.arguments) + end + + unless node.lparen_loc.nil? + bounds(node.lparen_loc) + arguments = on_paren(arguments) + end + + bounds(node.location) + on_yield(arguments) + end + end + + private + + # Lazily initialize the parse result. + def result + @result ||= Prism.parse(source) + end + + ########################################################################## + # Helpers + ########################################################################## + + # Returns true if there is a comma between the two locations. + def trailing_comma?(left, right) + source.byteslice(left.end_offset...right.start_offset).include?(",") + end + + # Returns true if there is a semicolon between the two locations. + def void_stmt?(left, right, allow_newline) + pattern = allow_newline ? /[;\n]/ : /;/ + source.byteslice(left.end_offset...right.start_offset).match?(pattern) + end + + # Visit the string content of a particular node. This method is used to + # split into the various token types. + def visit_token(token, allow_keywords = true) + case token + when "." + on_period(token) + when "`" + on_backtick(token) + when *(allow_keywords ? KEYWORDS : []) + on_kw(token) + when /^_/ + on_ident(token) + when /^[[:upper:]]\w*$/ + on_const(token) + when /^@@/ + on_cvar(token) + when /^@/ + on_ivar(token) + when /^\$/ + on_gvar(token) + when /^[[:punct:]]/ + on_op(token) + else + on_ident(token) + end + end + + # Visit a node that represents a number. We need to explicitly handle the + # unary - operator. + def visit_number_node(node) + slice = node.slice + location = node.location + + if slice[0] == "-" + bounds(location.copy(start_offset: location.start_offset + 1)) + value = yield slice[1..-1] + + bounds(node.location) + on_unary(:-@, value) + else + bounds(location) + yield slice + end + end + + # Visit a node that represents a write value. This is used to handle the + # special case of an implicit array that is generated without brackets. + def visit_write_value(node) + if node.is_a?(ArrayNode) && node.opening_loc.nil? + elements = node.elements + length = elements.length + + bounds(elements.first.location) + elements.each_with_index.inject((elements.first.is_a?(SplatNode) && length == 1) ? on_mrhs_new : on_args_new) do |args, (element, index)| + arg = visit(element) + bounds(element.location) + + if index == length - 1 + if element.is_a?(SplatNode) + mrhs = index == 0 ? args : on_mrhs_new_from_args(args) + on_mrhs_add_star(mrhs, arg) + else + on_mrhs_add(on_mrhs_new_from_args(args), arg) + end + else + case element + when BlockArgumentNode + on_args_add_block(args, arg) + when SplatNode + on_args_add_star(args, arg) + else + on_args_add(args, arg) + end + end + end + else + visit(node) + end + end + + # This method is responsible for updating lineno and column information + # to reflect the current node. + # + # This method could be drastically improved with some caching on the start + # of every line, but for now it's good enough. + def bounds(location) + @lineno = location.start_line + @column = location.start_column + end + + ########################################################################## + # Ripper interface + ########################################################################## + + # :stopdoc: + def _dispatch_0; end + def _dispatch_1(_); end + def _dispatch_2(_, _); end + def _dispatch_3(_, _, _); end + def _dispatch_4(_, _, _, _); end + def _dispatch_5(_, _, _, _, _); end + def _dispatch_7(_, _, _, _, _, _, _); end + # :startdoc: + + # + # Parser Events + # + + PARSER_EVENT_TABLE.each do |id, arity| + alias_method "on_#{id}", "_dispatch_#{arity}" + end + + # This method is called when weak warning is produced by the parser. + # +fmt+ and +args+ is printf style. + def warn(fmt, *args) + end + + # This method is called when strong warning is produced by the parser. + # +fmt+ and +args+ is printf style. + def warning(fmt, *args) + end + + # This method is called when the parser found syntax error. + def compile_error(msg) + end + + # + # Scanner Events + # + + SCANNER_EVENTS.each do |id| + alias_method "on_#{id}", :_dispatch_1 + end + + # This method is provided by the Ripper C extension. It is called when a + # string needs to be dedented because of a tilde heredoc. It is expected + # that it will modify the string in place and return the number of bytes + # that were removed. + def dedent_string(string, width) + whitespace = 0 + cursor = 0 + + while cursor < string.length && string[cursor].match?(/\s/) && whitespace < width + if string[cursor] == "\t" + whitespace = ((whitespace / 8 + 1) * 8) + break if whitespace > width + else + whitespace += 1 + end + + cursor += 1 + end - (::Ripper::SCANNER_EVENT_TABLE.merge(::Ripper::PARSER_EVENT_TABLE)).each do |event, arity| - alias_method :"on_#{event}", :"_dispatch#{arity}" + string.replace(string[cursor..]) + cursor end end end diff --git a/lib/prism/translation/ripper/ripper_compiler.rb b/lib/prism/translation/ripper/ripper_compiler.rb deleted file mode 100644 index 6690717a28137b..00000000000000 --- a/lib/prism/translation/ripper/ripper_compiler.rb +++ /dev/null @@ -1,748 +0,0 @@ -# frozen_string_literal: true - -module Prism - module Translation - # A visitor that knows how to convert a prism syntax tree into what - # we need for Ripper compatibility. - class RipperCompiler < ::Prism::Compiler - # The source that is being parsed. - attr_reader :source - - # The current line number of the parser. - attr_reader :lineno - - # The current column number of the parser. - attr_reader :column - - # Create a new Translation::Ripper object with the given source. - def initialize(source) - @source = source - @result = nil - @lineno = nil - @column = nil - - @offset_cache = build_offset_cache(source) - @void_stmt_val = on_stmts_add(on_stmts_new, on_void_stmt) - end - - # Excerpt a chunk of the source - def source_range(start_c, end_c) - @source[@offset_cache[start_c]..@offset_cache[end_c]] - end - - # Prism deals with offsets in bytes, while Ripper deals with - # offsets in characters. We need to handle this conversion in order to - # build the parser gem AST. - # - # If the bytesize of the source is the same as the length, then we can - # just use the offset directly. Otherwise, we build an array where the - # index is the byte offset and the value is the character offset. - def build_offset_cache(source) - if source.bytesize == source.length - -> (offset) { offset } - else - offset_cache = [] - offset = 0 - - source.each_char do |char| - char.bytesize.times { offset_cache << offset } - offset += 1 - end - - offset_cache << offset - end - end - - ############################################################################ - # Visitor methods - ############################################################################ - - # Visit an ArrayNode node. - def visit_array_node(node) - elements = visit_elements(node.elements) unless node.elements.empty? - bounds(node.location) - on_array(elements) - end - - # Visit a CallNode node. - # Ripper distinguishes between many different method-call - # nodes -- unary and binary operators, "command" calls with - # no parentheses, and call/fcall/vcall. - def visit_call_node(node) - return visit_aref_node(node) if node.name == :[] - return visit_aref_field_node(node) if node.name == :[]= - - if node.variable_call? - raise NotImplementedError unless node.receiver.nil? - - bounds(node.message_loc) - return on_vcall(on_ident(node.message)) - end - - if node.opening_loc.nil? - return visit_no_paren_call(node) - end - - # A non-operator method call with parentheses - - args = if node.arguments.nil? - on_arg_paren(nil) - else - on_arg_paren(on_args_add_block(visit_elements(node.arguments.arguments), false)) - end - - bounds(node.message_loc) - ident_val = on_ident(node.message) - - bounds(node.location) - args_call_val = on_method_add_arg(on_fcall(ident_val), args) - if node.block - block_val = visit(node.block) - - return on_method_add_block(args_call_val, block_val) - else - return args_call_val - end - end - - # Visit a LocalVariableWriteNode. - def visit_local_variable_write_node(node) - bounds(node.name_loc) - ident_val = on_ident(node.name.to_s) - on_assign(on_var_field(ident_val), visit(node.value)) - end - - # Visit a LocalVariableAndWriteNode. - def visit_local_variable_and_write_node(node) - visit_binary_op_assign(node) - end - - # Visit a LocalVariableOrWriteNode. - def visit_local_variable_or_write_node(node) - visit_binary_op_assign(node) - end - - # Visit nodes for +=, *=, -=, etc., called LocalVariableOperatorWriteNodes. - def visit_local_variable_operator_write_node(node) - visit_binary_op_assign(node, operator: "#{node.operator}=") - end - - # Visit a LocalVariableReadNode. - def visit_local_variable_read_node(node) - bounds(node.location) - ident_val = on_ident(node.slice) - - on_var_ref(ident_val) - end - - # Visit a BlockNode. - def visit_block_node(node) - params_val = node.parameters.nil? ? nil : visit(node.parameters) - - # If the body is empty, we use a void statement. If there is - # a semicolon after the opening delimiter, we append a void - # statement, unless the body is also empty. So we should never - # get a double void statement. - - body_val = if node.body.nil? - @void_stmt_val - elsif node_has_semicolon?(node) - v = visit(node.body) - raise(NotImplementedError, "Unexpected statement structure #{v.inspect}") if v[0] != :stmts_add - v[1] = @void_stmt_val - v - else - visit(node.body) - end - - if node.opening == "{" - on_brace_block(params_val, body_val) - elsif node.opening == "do" - on_do_block(params_val, on_bodystmt(body_val, nil, nil, nil)) - else - raise NotImplementedError, "Unexpected Block opening character!" - end - end - - # Visit a BlockParametersNode. - def visit_block_parameters_node(node) - on_block_var(visit(node.parameters), no_block_value) - end - - # Visit a ParametersNode. - # This will require expanding as we support more kinds of parameters. - def visit_parameters_node(node) - #on_params(required, optional, nil, nil, nil, nil, nil) - on_params(visit_all(node.requireds), nil, nil, nil, nil, nil, nil) - end - - # Visit a RequiredParameterNode. - def visit_required_parameter_node(node) - bounds(node.location) - on_ident(node.name.to_s) - end - - # Visit a BreakNode. - def visit_break_node(node) - return on_break(on_args_new) if node.arguments.nil? - - args_val = visit_elements(node.arguments.arguments) - on_break(on_args_add_block(args_val, false)) - end - - # Visit an AliasMethodNode. - def visit_alias_method_node(node) - # For both the old and new name, if there is a colon in the symbol - # name (e.g. 'alias :foo :bar') then we do *not* emit the [:symbol] wrapper around - # the lexer token (e.g. :@ident) inside [:symbol_literal]. But if there - # is no colon (e.g. 'alias foo bar') then we *do* still emit the [:symbol] wrapper. - - if node.new_name.is_a?(SymbolNode) && !node.new_name.opening - new_name_val = visit_symbol_literal_node(node.new_name, no_symbol_wrapper: true) - else - new_name_val = visit(node.new_name) - end - if node.old_name.is_a?(SymbolNode) && !node.old_name.opening - old_name_val = visit_symbol_literal_node(node.old_name, no_symbol_wrapper: true) - else - old_name_val = visit(node.old_name) - end - - on_alias(new_name_val, old_name_val) - end - - # Visit an AliasGlobalVariableNode. - def visit_alias_global_variable_node(node) - on_var_alias(visit(node.new_name), visit(node.old_name)) - end - - # Visit a GlobalVariableReadNode. - def visit_global_variable_read_node(node) - bounds(node.location) - on_gvar(node.name.to_s) - end - - # Visit a BackReferenceReadNode. - def visit_back_reference_read_node(node) - bounds(node.location) - on_backref(node.name.to_s) - end - - # Visit an AndNode. - def visit_and_node(node) - visit_binary_operator(node) - end - - # Visit an OrNode. - def visit_or_node(node) - visit_binary_operator(node) - end - - # Visit a TrueNode. - def visit_true_node(node) - bounds(node.location) - on_var_ref(on_kw("true")) - end - - # Visit a FalseNode. - def visit_false_node(node) - bounds(node.location) - on_var_ref(on_kw("false")) - end - - # Visit a FloatNode node. - def visit_float_node(node) - visit_number(node) { |text| on_float(text) } - end - - # Visit a ImaginaryNode node. - def visit_imaginary_node(node) - visit_number(node) { |text| on_imaginary(text) } - end - - # Visit an IntegerNode node. - def visit_integer_node(node) - visit_number(node) { |text| on_int(text) } - end - - # Visit a ParenthesesNode node. - def visit_parentheses_node(node) - body = - if node.body.nil? - @void_stmt_val - else - visit(node.body) - end - - bounds(node.location) - on_paren(body) - end - - # Visit a BeginNode node. - def visit_begin_node(node) - rescue_val = node.rescue_clause ? visit(node.rescue_clause) : nil - ensure_val = node.ensure_clause ? visit(node.ensure_clause) : nil - - if node.statements - stmts_val = visit(node.statements) - if node_has_semicolon?(node) - # If there's a semicolon, we need to replace [:stmts_new] with - # [:stmts_add, [:stmts_new], [:void_stmt]]. - stmts_val[1] = @void_stmt_val - end - else - stmts_val = @void_stmt_val - end - - on_begin(on_bodystmt(stmts_val, rescue_val, nil, ensure_val)) - end - - # Visit an EnsureNode node. - def visit_ensure_node(node) - if node.statements - # If there are any statements, we need to see if there's a semicolon - # between the ensure and the start of the first statement. - - stmts_val = visit(node.statements) - if node_has_semicolon?(node) - # If there's a semicolon, we need to replace [:stmts_new] with - # [:stmts_add, [:stmts_new], [:void_stmt]]. - stmts_val[1] = @void_stmt_val - end - else - stmts_val = @void_stmt_val - end - on_ensure(stmts_val) - end - - # Visit a RescueNode node. - def visit_rescue_node(node) - consequent_val = nil - if node.consequent - consequent_val = visit(node.consequent) - end - - if node.statements - stmts_val = visit(node.statements) - else - stmts_val = @void_stmt_val - end - - if node.reference - raise NotImplementedError unless node.reference.is_a?(LocalVariableTargetNode) - bounds(node.reference.location) - ref_val = on_var_field(on_ident(node.reference.name.to_s)) - else - ref_val = nil - end - - # No exception(s) - if !node.exceptions || node.exceptions.empty? - return on_rescue(nil, ref_val, stmts_val, consequent_val) - end - - exc_vals = node.exceptions.map { |exc| visit(exc) } - - if node.exceptions.length == 1 - return on_rescue(exc_vals, ref_val, stmts_val, consequent_val) - end - - inner_vals = exc_vals[0..-2].inject(on_args_new) do |output, exc_val| - on_args_add(output, exc_val) - end - exc_vals = on_mrhs_add(on_mrhs_new_from_args(inner_vals), exc_vals[-1]) - - on_rescue(exc_vals, ref_val, stmts_val, consequent_val) - end - - # Visit a ProgramNode node. - def visit_program_node(node) - statements = visit(node.statements) - bounds(node.location) - on_program(statements) - end - - # Visit a RangeNode node. - def visit_range_node(node) - left = visit(node.left) - right = visit(node.right) - - bounds(node.location) - if node.exclude_end? - on_dot3(left, right) - else - on_dot2(left, right) - end - end - - # Visit a RationalNode node. - def visit_rational_node(node) - visit_number(node) { |text| on_rational(text) } - end - - # Visit a StringNode node. - def visit_string_node(node) - bounds(node.content_loc) - tstring_val = on_tstring_content(node.unescaped.to_s) - on_string_literal(on_string_add(on_string_content, tstring_val)) - end - - # Visit an XStringNode node. - def visit_x_string_node(node) - bounds(node.content_loc) - tstring_val = on_tstring_content(node.unescaped.to_s) - on_xstring_literal(on_xstring_add(on_xstring_new, tstring_val)) - end - - # Visit an InterpolatedStringNode node. - def visit_interpolated_string_node(node) - on_string_literal(visit_enumerated_node(node)) - end - - # Visit a ConstantReadNode node. - def visit_constant_read_node(node) - bounds(node.location) - on_var_ref(on_const(node.name.to_s)) - end - - # Visit a ConstantWriteNode node. - def visit_constant_write_node(node) - bounds(node.location) - const_val = on_var_field(on_const(node.name.to_s)) - - on_assign(const_val, visit(node.value)) - end - - # Visit an EmbeddedStatementsNode node. - def visit_embedded_statements_node(node) - visit(node.statements) - end - - # Visit a SymbolNode node. - def visit_symbol_node(node) - visit_symbol_literal_node(node) - end - - # Visit an InterpolatedSymbolNode node. - def visit_interpolated_symbol_node(node) - on_dyna_symbol(visit_enumerated_node(node)) - end - - # Visit a StatementsNode node. - def visit_statements_node(node) - bounds(node.location) - node.body.inject(on_stmts_new) do |stmts, stmt| - on_stmts_add(stmts, visit(stmt)) - end - end - - private - - # Generate Ripper events for a CallNode with no opening_loc - def visit_no_paren_call(node) - # No opening_loc can mean an operator. It can also mean a - # method call with no parentheses. - if node.message.match?(/^[[:punct:]]/) - left = visit(node.receiver) - if node.arguments&.arguments&.length == 1 - right = visit(node.arguments.arguments.first) - - return on_binary(left, node.name, right) - elsif !node.arguments || node.arguments.empty? - return on_unary(node.name, left) - else - raise NotImplementedError, "More than two arguments for operator" - end - elsif node.call_operator_loc.nil? - # In Ripper a method call like "puts myvar" with no parentheses is a "command". - bounds(node.message_loc) - ident_val = on_ident(node.message) - - # Unless it has a block, and then it's an fcall (e.g. "foo { bar }") - if node.block - block_val = visit(node.block) - # In these calls, even if node.arguments is nil, we still get an :args_new call. - args = if node.arguments.nil? - on_args_new - else - on_args_add_block(visit_elements(node.arguments.arguments)) - end - method_args_val = on_method_add_arg(on_fcall(ident_val), args) - return on_method_add_block(method_args_val, block_val) - else - if node.arguments.nil? - return on_command(ident_val, nil) - else - args = on_args_add_block(visit_elements(node.arguments.arguments), false) - return on_command(ident_val, args) - end - end - else - operator = node.call_operator_loc.slice - if operator == "." || operator == "&." - left_val = visit(node.receiver) - - bounds(node.call_operator_loc) - operator_val = operator == "." ? on_period(node.call_operator) : on_op(node.call_operator) - - bounds(node.message_loc) - right_val = on_ident(node.message) - - call_val = on_call(left_val, operator_val, right_val) - - if node.block - block_val = visit(node.block) - return on_method_add_block(call_val, block_val) - else - return call_val - end - else - raise NotImplementedError, "operator other than . or &. for call: #{operator.inspect}" - end - end - end - - # Visit a list of elements, like the elements of an array or arguments. - def visit_elements(elements) - bounds(elements.first.location) - elements.inject(on_args_new) do |args, element| - on_args_add(args, visit(element)) - end - end - - # Visit an InterpolatedStringNode or an InterpolatedSymbolNode node. - def visit_enumerated_node(node) - parts = node.parts.map do |part| - case part - when StringNode - bounds(part.content_loc) - on_tstring_content(part.content) - when EmbeddedStatementsNode - on_string_embexpr(visit(part)) - else - raise NotImplementedError, "Unexpected node type in visit_enumerated_node" - end - end - - parts.inject(on_string_content) do |items, item| - on_string_add(items, item) - end - end - - # Visit an operation-and-assign node, such as +=. - def visit_binary_op_assign(node, operator: node.operator) - bounds(node.name_loc) - ident_val = on_ident(node.name.to_s) - - bounds(node.operator_loc) - op_val = on_op(operator) - - on_opassign(on_var_field(ident_val), op_val, visit(node.value)) - end - - # In Prism this is a CallNode with :[] as the operator. - # In Ripper it's an :aref. - def visit_aref_node(node) - first_arg_val = visit(node.arguments.arguments[0]) - args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false) - on_aref(visit(node.receiver), args_val) - end - - # In Prism this is a CallNode with :[]= as the operator. - # In Ripper it's an :aref_field. - def visit_aref_field_node(node) - first_arg_val = visit(node.arguments.arguments[0]) - args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false) - assign_val = visit(node.arguments.arguments[1]) - on_assign(on_aref_field(visit(node.receiver), args_val), assign_val) - end - - # In an alias statement Ripper will emit @kw instead of @ident if the object - # being aliased is a Ruby keyword. For instance, in the line "alias :foo :if", - # the :if is treated as a lexer keyword. So we need to know what symbols are - # also keywords. - RUBY_KEYWORDS = [ - "alias", - "and", - "begin", - "BEGIN", - "break", - "case", - "class", - "def", - "defined?", - "do", - "else", - "elsif", - "end", - "END", - "ensure", - "false", - "for", - "if", - "in", - "module", - "next", - "nil", - "not", - "or", - "redo", - "rescue", - "retry", - "return", - "self", - "super", - "then", - "true", - "undef", - "unless", - "until", - "when", - "while", - "yield", - "__ENCODING__", - "__FILE__", - "__LINE__", - ] - - # Ripper has several methods of emitting a symbol literal. Inside an alias - # sometimes it suppresses the [:symbol] wrapper around ident. If the symbol - # is also the name of a keyword (e.g. :if) it will emit a :@kw wrapper, not - # an :@ident wrapper, with similar treatment for constants and operators. - def visit_symbol_literal_node(node, no_symbol_wrapper: false) - if (opening = node.opening) && (['"', "'"].include?(opening[-1]) || opening.start_with?("%s")) - bounds(node.value_loc) - str_val = node.value.to_s - if str_val == "" - return on_dyna_symbol(on_string_content) - else - tstring_val = on_tstring_content(str_val) - return on_dyna_symbol(on_string_add(on_string_content, tstring_val)) - end - end - - bounds(node.value_loc) - node_name = node.value.to_s - if RUBY_KEYWORDS.include?(node_name) - token_val = on_kw(node_name) - elsif node_name.length == 0 - raise NotImplementedError - elsif /[[:upper:]]/.match(node_name[0]) - token_val = on_const(node_name) - elsif /[[:punct:]]/.match(node_name[0]) - token_val = on_op(node_name) - else - token_val = on_ident(node_name) - end - sym_val = no_symbol_wrapper ? token_val : on_symbol(token_val) - on_symbol_literal(sym_val) - end - - # Visit a node that represents a number. We need to explicitly handle the - # unary - operator. - def visit_number(node) - slice = node.slice - location = node.location - - if slice[0] == "-" - bounds_values(location.start_line, location.start_column + 1) - value = yield slice[1..-1] - - bounds(node.location) - on_unary(visit_unary_operator(:-@), value) - else - bounds(location) - yield slice - end - end - - if RUBY_ENGINE == "jruby" && Gem::Version.new(JRUBY_VERSION) < Gem::Version.new("9.4.6.0") - # JRuby before 9.4.6.0 uses :- for unary minus instead of :-@ - def visit_unary_operator(value) - value == :-@ ? :- : value - end - else - # For most Rubies and JRuby after 9.4.6.0 this is a no-op. - def visit_unary_operator(value) - value - end - end - - if RUBY_ENGINE == "jruby" - # For JRuby, "no block" in an on_block_var is nil - def no_block_value - nil - end - else - # For CRuby et al, "no block" in an on_block_var is false - def no_block_value - false - end - end - - # Visit a binary operator node like an AndNode or OrNode - def visit_binary_operator(node) - left_val = visit(node.left) - right_val = visit(node.right) - on_binary(left_val, node.operator.to_sym, right_val) - end - - # Some nodes, such as `begin`, `ensure` and `do` may have a semicolon - # after the keyword and before the first statement. This affects - # Ripper's return values. - def node_has_semicolon?(node) - first_field, second_field = case node - when BeginNode - [:begin_keyword_loc, :statements] - when EnsureNode - [:ensure_keyword_loc, :statements] - when BlockNode - [:opening_loc, :body] - else - raise NotImplementedError - end - first_offs, second_offs = delimiter_offsets_for(node, first_field, second_field) - - # We need to know if there's a semicolon after the keyword, but before - # the start of the first statement in the ensure. - range_has_string?(first_offs, second_offs, ";") - end - - # For a given node, grab the offsets for the end of the first field - # and the beginning of the second field. - def delimiter_offsets_for(node, first, second) - first_field = node.send(first) - first_end_loc = first_field.start_offset + first_field.length - second_begin_loc = node.send(second).body[0].location.start_offset - 1 - [first_end_loc, second_begin_loc] - end - - # Check whether the source code contains the given substring between the - # specified offsets. - def range_has_string?(first, last, token) - sr = source_range(first, last) - sr.include?(token) - end - - # This method is responsible for updating lineno and column information - # to reflect the current node. - # - # This method could be drastically improved with some caching on the start - # of every line, but for now it's good enough. - def bounds(location) - @lineno = location.start_line - @column = location.start_column - end - - # If we need to do something unusual, we can directly update the line number - # and column to reflect the current node. - def bounds_values(lineno, column) - @lineno = lineno - @column = column - end - end - - private_constant :RipperCompiler - end -end - diff --git a/lib/prism/translation/ripper/sexp.rb b/lib/prism/translation/ripper/sexp.rb new file mode 100644 index 00000000000000..dc26a639a351fd --- /dev/null +++ b/lib/prism/translation/ripper/sexp.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require_relative "../ripper" + +module Prism + module Translation + class Ripper + # This class mirrors the ::Ripper::SexpBuilder subclass of ::Ripper that + # returns the arrays of [type, *children]. + class SexpBuilder < Ripper + # :stopdoc: + + attr_reader :error + + private + + def dedent_element(e, width) + if (n = dedent_string(e[1], width)) > 0 + e[2][1] += n + end + e + end + + def on_heredoc_dedent(val, width) + sub = proc do |cont| + cont.map! do |e| + if Array === e + case e[0] + when :@tstring_content + e = dedent_element(e, width) + when /_add\z/ + e[1] = sub[e[1]] + end + elsif String === e + dedent_string(e, width) + end + e + end + end + sub[val] + val + end + + events = private_instance_methods(false).grep(/\Aon_/) {$'.to_sym} + (PARSER_EVENTS - events).each do |event| + module_eval(<<-End, __FILE__, __LINE__ + 1) + def on_#{event}(*args) + args.unshift :#{event} + end + End + end + + SCANNER_EVENTS.each do |event| + module_eval(<<-End, __FILE__, __LINE__ + 1) + def on_#{event}(tok) + [:@#{event}, tok, [lineno(), column()]] + end + End + end + + def on_error(mesg) + @error = mesg + end + remove_method :on_parse_error + alias on_parse_error on_error + alias compile_error on_error + + # :startdoc: + end + + # This class mirrors the ::Ripper::SexpBuilderPP subclass of ::Ripper that + # returns the same values as ::Ripper::SexpBuilder except with a couple of + # niceties that flatten linked lists into arrays. + class SexpBuilderPP < SexpBuilder + # :stopdoc: + + private + + def on_heredoc_dedent(val, width) + val.map! do |e| + next e if Symbol === e and /_content\z/ =~ e + if Array === e and e[0] == :@tstring_content + e = dedent_element(e, width) + elsif String === e + dedent_string(e, width) + end + e + end + val + end + + def _dispatch_event_new + [] + end + + def _dispatch_event_push(list, item) + list.push item + list + end + + def on_mlhs_paren(list) + [:mlhs, *list] + end + + def on_mlhs_add_star(list, star) + list.push([:rest_param, star]) + end + + def on_mlhs_add_post(list, post) + list.concat(post) + end + + PARSER_EVENT_TABLE.each do |event, arity| + if /_new\z/ =~ event and arity == 0 + alias_method "on_#{event}", :_dispatch_event_new + elsif /_add\z/ =~ event + alias_method "on_#{event}", :_dispatch_event_push + end + end + + # :startdoc: + end + end + end +end diff --git a/lib/prism/translation/ripper/shim.rb b/lib/prism/translation/ripper/shim.rb new file mode 100644 index 00000000000000..10e21cd16a213f --- /dev/null +++ b/lib/prism/translation/ripper/shim.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# This writes the prism ripper translation into the Ripper constant so that +# users can transparently use Ripper without any changes. +Ripper = Prism::Translation::Ripper diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index 176c777f47a3dd..a8692db5ea9f74 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -6,7 +6,7 @@ module Prism module Translation # This module is the entry-point for converting a prism syntax tree into the # seattlerb/ruby_parser gem's syntax tree. - module RubyParser + class RubyParser # A prism visitor that builds Sexp objects. class Compiler < ::Prism::Compiler # This is the name of the file that we are compiling. We set it on every @@ -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 @@ -805,17 +805,29 @@ def visit_integer_node(node) # if /foo #{bar}/ then end # ^^^^^^^^^^^^ def visit_interpolated_match_last_line_node(node) - s(node, :match, s(node, :dregx).concat(visit_interpolated_parts(node.parts))) + parts = visit_interpolated_parts(node.parts) + regexp = + if parts.length == 1 + s(node, :lit, Regexp.new(parts.first, node.options)) + else + s(node, :dregx).concat(parts).tap do |result| + options = node.options + result << options if options != 0 + end + end + + s(node, :match, regexp) end # /foo #{bar}/ # ^^^^^^^^^^^^ def visit_interpolated_regular_expression_node(node) - if node.parts.all? { |part| part.is_a?(StringNode) || (part.is_a?(EmbeddedStatementsNode) && part.statements&.body&.length == 1 && part.statements.body.first.is_a?(StringNode)) } - unescaped = node.parts.map { |part| part.is_a?(StringNode) ? part.unescaped : part.statements.body.first.unescaped }.join - s(node, :lit, Regexp.new(unescaped, node.options)) + parts = visit_interpolated_parts(node.parts) + + if parts.length == 1 + s(node, :lit, Regexp.new(parts.first, node.options)) else - s(node, :dregx).concat(visit_interpolated_parts(node.parts)).tap do |result| + s(node, :dregx).concat(parts).tap do |result| options = node.options result << options if options != 0 end @@ -825,45 +837,71 @@ def visit_interpolated_regular_expression_node(node) # "foo #{bar}" # ^^^^^^^^^^^^ def visit_interpolated_string_node(node) - if (node.parts.all? { |part| part.is_a?(StringNode) || (part.is_a?(EmbeddedStatementsNode) && part.statements&.body&.length == 1 && part.statements.body.first.is_a?(StringNode)) }) || - (node.opening.nil? && node.parts.all? { |part| part.is_a?(StringNode) && !part.opening_loc.nil? }) - unescaped = node.parts.map { |part| part.is_a?(StringNode) ? part.unescaped : part.statements.body.first.unescaped }.join - s(node, :str, unescaped) - else - s(node, :dstr).concat(visit_interpolated_parts(node.parts)) - end + parts = visit_interpolated_parts(node.parts) + parts.length == 1 ? s(node, :str, parts.first) : s(node, :dstr).concat(parts) end # :"foo #{bar}" # ^^^^^^^^^^^^^ def visit_interpolated_symbol_node(node) - if node.parts.all? { |part| part.is_a?(StringNode) || (part.is_a?(EmbeddedStatementsNode) && part.statements&.body&.length == 1 && part.statements.body.first.is_a?(StringNode)) } - unescaped = node.parts.map { |part| part.is_a?(StringNode) ? part.unescaped : part.statements.body.first.unescaped }.join - s(node, :lit, unescaped.to_sym) - else - s(node, :dsym).concat(visit_interpolated_parts(node.parts)) - end + parts = visit_interpolated_parts(node.parts) + parts.length == 1 ? s(node, :lit, parts.first.to_sym) : s(node, :dsym).concat(parts) end # `foo #{bar}` # ^^^^^^^^^^^^ def visit_interpolated_x_string_node(node) - children = visit_interpolated_parts(node.parts) - s(node.heredoc? ? node.parts.first : node, :dxstr).concat(children) + source = node.heredoc? ? node.parts.first : node + parts = visit_interpolated_parts(node.parts) + parts.length == 1 ? s(source, :xstr, parts.first) : s(source, :dxstr).concat(parts) end # Visit the interpolated content of the string-like node. private def visit_interpolated_parts(parts) - parts.each_with_object([]).with_index do |(part, results), index| - if index == 0 - if part.is_a?(StringNode) - results << part.unescaped + visited = [] + parts.each do |part| + result = visit(part) + + if result[0] == :evstr && result[1] + if result[1][0] == :str + visited << result[1] + elsif result[1][0] == :dstr + visited.concat(result[1][1..-1]) + else + visited << result + end + else + visited << result + end + end + + state = :beginning #: :beginning | :string_content | :interpolated_content + + visited.each_with_object([]) do |result, results| + case state + when :beginning + if result.is_a?(String) + results << result + state = :string_content + elsif result.is_a?(Array) && result[0] == :str + results << result[1] + state = :string_content else results << "" - results << visit(part) + results << result + state = :interpolated_content + end + when :string_content + if result.is_a?(String) + results[0] << result + elsif result.is_a?(Array) && result[0] == :str + results[0] << result[1] + else + results << result + state = :interpolated_content end else - results << visit(part) + results << result end end end @@ -1274,6 +1312,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) @@ -1292,7 +1335,7 @@ def visit_source_encoding_node(node) # __FILE__ # ^^^^^^^^ def visit_source_file_node(node) - s(node, :str, file) + s(node, :str, node.filepath) end # __LINE__ @@ -1407,7 +1450,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 +1477,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 @@ -1490,31 +1533,43 @@ def visit_write_value(node) private_constant :Compiler + # Parse the given source and translate it into the seattlerb/ruby_parser + # gem's Sexp format. + def parse(source, filepath = "(string)") + translate(Prism.parse(source, filepath: filepath), filepath) + end + + # Parse the given file and translate it into the seattlerb/ruby_parser + # gem's Sexp format. + def parse_file(filepath) + translate(Prism.parse_file(filepath), filepath) + end + class << self # Parse the given source and translate it into the seattlerb/ruby_parser # gem's Sexp format. def parse(source, filepath = "(string)") - translate(Prism.parse(source), filepath) + new.parse(source, filepath) end # Parse the given file and translate it into the seattlerb/ruby_parser # gem's Sexp format. def parse_file(filepath) - translate(Prism.parse_file(filepath), filepath) + new.parse_file(filepath) end + end - private - - # Translate the given parse result and filepath into the - # seattlerb/ruby_parser gem's Sexp format. - def translate(result, filepath) - if result.failure? - error = result.errors.first - raise ::RubyParser::SyntaxError, "#{filepath}:#{error.location.start_line} :: #{error.message}" - end + private - result.value.accept(Compiler.new(filepath)) + # Translate the given parse result and filepath into the + # seattlerb/ruby_parser gem's Sexp format. + def translate(result, filepath) + if result.failure? + error = result.errors.first + raise ::RubyParser::SyntaxError, "#{filepath}:#{error.location.start_line} :: #{error.message}" end + + result.value.accept(Compiler.new(filepath)) end end 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/context.rb b/lib/rdoc/context.rb index c6edfb473c2d16..c688d562c3e567 100644 --- a/lib/rdoc/context.rb +++ b/lib/rdoc/context.rb @@ -710,7 +710,7 @@ def display(method_attr) # :nodoc: # This method exists to make it easy to work with Context subclasses that # aren't part of RDoc. - def each_ancestor # :nodoc: + def each_ancestor(&_) # :nodoc: end ## 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/markup/to_bs.rb b/lib/rdoc/markup/to_bs.rb index f9b86487db2153..afd9d6e9812497 100644 --- a/lib/rdoc/markup/to_bs.rb +++ b/lib/rdoc/markup/to_bs.rb @@ -40,6 +40,31 @@ def accept_heading heading @res << "\n" end + ## + # Prepares the visitor for consuming +list_item+ + + def accept_list_item_start list_item + type = @list_type.last + + case type + when :NOTE, :LABEL then + bullets = Array(list_item.label).map do |label| + attributes(label).strip + end.join "\n" + + bullets << ":\n" unless bullets.empty? + + @prefix = ' ' * @indent + @indent += 2 + @prefix << bullets + (' ' * @indent) + else + bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' + @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) + width = bullet.length + 1 + @indent += width + end + end + ## # Turns on or off regexp handling for +convert_string+ diff --git a/lib/rdoc/markup/to_markdown.rb b/lib/rdoc/markup/to_markdown.rb index 5dd60e18f58288..b915fab60b7b7a 100644 --- a/lib/rdoc/markup/to_markdown.rb +++ b/lib/rdoc/markup/to_markdown.rb @@ -45,8 +45,6 @@ def handle_regexp_HARD_BREAK target # Finishes consumption of `list` def accept_list_end list - @res << "\n" - super end @@ -60,6 +58,8 @@ def accept_list_item_end list_item when :NOTE, :LABEL then use_prefix + @res << "\n" + 4 else @list_index[-1] = @list_index.last.succ @@ -81,11 +81,11 @@ def accept_list_item_start list_item attributes(label).strip end.join "\n" - bullets << "\n:" + bullets << "\n" unless bullets.empty? @prefix = ' ' * @indent @indent += 4 - @prefix << bullets + (' ' * (@indent - 1)) + @prefix << bullets << ":" << (' ' * (@indent - 1)) else bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' @prefix = (' ' * @indent) + bullet.ljust(4) diff --git a/lib/rdoc/markup/to_rdoc.rb b/lib/rdoc/markup/to_rdoc.rb index 692904958258d9..88234f5096d122 100644 --- a/lib/rdoc/markup/to_rdoc.rb +++ b/lib/rdoc/markup/to_rdoc.rb @@ -145,11 +145,19 @@ def accept_list_item_start list_item case type when :NOTE, :LABEL then - bullets = Array(list_item.label).map do |label| + stripped_labels = Array(list_item.label).map do |label| attributes(label).strip - end.join "\n" + end + + bullets = case type + when :NOTE + stripped_labels.map { |b| "#{b}::" } + when :LABEL + stripped_labels.map { |b| "[#{b}]" } + end - bullets << ":\n" unless bullets.empty? + bullets = bullets.join("\n") + bullets << "\n" unless stripped_labels.empty? @prefix = ' ' * @indent @indent += 2 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/config.rb b/lib/reline/config.rb index 4c07a737017c09..d7564ba4b7dff8 100644 --- a/lib/reline/config.rb +++ b/lib/reline/config.rb @@ -53,8 +53,6 @@ def initialize @additional_key_bindings[:vi_insert] = {} @additional_key_bindings[:vi_command] = {} @oneshot_key_bindings = {} - @skip_section = nil - @if_stack = nil @editing_mode_label = :emacs @keymap_label = :emacs @keymap_prefix = [] @@ -190,9 +188,7 @@ def read_lines(lines, file = nil) end end end - conditions = [@skip_section, @if_stack] - @skip_section = nil - @if_stack = [] + if_stack = [] lines.each_with_index do |line, no| next if line.match(/\A\s*#/) @@ -201,11 +197,11 @@ def read_lines(lines, file = nil) line = line.chomp.lstrip if line.start_with?('$') - handle_directive(line[1..-1], file, no) + handle_directive(line[1..-1], file, no, if_stack) next end - next if @skip_section + next if if_stack.any? { |_no, skip| skip } case line when /^set +([^ ]+) +([^ ]+)/i @@ -219,14 +215,12 @@ def read_lines(lines, file = nil) @additional_key_bindings[@keymap_label][@keymap_prefix + keystroke] = func end end - unless @if_stack.empty? - raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if" + unless if_stack.empty? + raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if" end - ensure - @skip_section, @if_stack = conditions end - def handle_directive(directive, file, no) + def handle_directive(directive, file, no, if_stack) directive, args = directive.split(' ') case directive when 'if' @@ -239,18 +233,17 @@ def handle_directive(directive, file, no) condition = true if args == 'Ruby' condition = true if args == 'Reline' end - @if_stack << [file, no, @skip_section] - @skip_section = !condition + if_stack << [no, !condition] when 'else' - if @if_stack.empty? + if if_stack.empty? raise InvalidInputrc, "#{file}:#{no}: unmatched else" end - @skip_section = !@skip_section + if_stack.last[1] = !if_stack.last[1] when 'endif' - if @if_stack.empty? + if if_stack.empty? raise InvalidInputrc, "#{file}:#{no}: unmatched endif" end - @skip_section = @if_stack.pop + if_stack.pop when 'include' read(File.expand_path(args)) end 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/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb index a561feee578cd2..5d0a7fb63d2939 100644 --- a/lib/reline/key_actor/emacs.rb +++ b/lib/reline/key_actor/emacs.rb @@ -49,13 +49,13 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 23 ^W :em_kill_region, # 24 ^X - :ed_sequence_lead_in, + :ed_unassigned, # 25 ^Y :em_yank, # 26 ^Z :ed_ignore, # 27 ^[ - :em_meta_next, + :ed_unassigned, # 28 ^\ :ed_ignore, # 29 ^] @@ -319,9 +319,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 158 M-^^ :ed_unassigned, # 159 M-^_ - :em_copy_prev_word, - # 160 M-SPACE :ed_unassigned, + # 160 M-SPACE + :em_set_mark, # 161 M-! :ed_unassigned, # 162 M-" @@ -415,7 +415,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 206 M-N :vi_search_next, # 207 M-O - :ed_sequence_lead_in, + :ed_unassigned, # 208 M-P :vi_search_prev, # 209 M-Q @@ -431,15 +431,15 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 214 M-V :ed_unassigned, # 215 M-W - :em_copy_region, + :ed_unassigned, # 216 M-X - :ed_command, - # 217 M-Y :ed_unassigned, + # 217 M-Y + :em_yank_pop, # 218 M-Z :ed_unassigned, # 219 M-[ - :ed_sequence_lead_in, + :ed_unassigned, # 220 M-\ :ed_unassigned, # 221 M-] @@ -495,9 +495,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 246 M-v :ed_unassigned, # 247 M-w - :em_copy_region, + :ed_unassigned, # 248 M-x - :ed_command, + :ed_unassigned, # 249 M-y :ed_unassigned, # 250 M-z diff --git a/lib/reline/key_actor/vi_command.rb b/lib/reline/key_actor/vi_command.rb index 98146d2f7794e8..06bb0ba8e44fdb 100644 --- a/lib/reline/key_actor/vi_command.rb +++ b/lib/reline/key_actor/vi_command.rb @@ -17,7 +17,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 7 ^G :ed_unassigned, # 8 ^H - :ed_unassigned, + :ed_prev_char, # 9 ^I :ed_unassigned, # 10 ^J @@ -41,7 +41,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 19 ^S :ed_ignore, # 20 ^T - :ed_unassigned, + :ed_transpose_chars, # 21 ^U :vi_kill_line_prev, # 22 ^V @@ -51,7 +51,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 24 ^X :ed_unassigned, # 25 ^Y - :ed_unassigned, + :em_yank, # 26 ^Z :ed_unassigned, # 27 ^[ @@ -75,7 +75,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 36 $ :ed_move_to_end, # 37 % - :vi_match, + :ed_unassigned, # 38 & :ed_unassigned, # 39 ' @@ -89,11 +89,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 43 + :ed_next_history, # 44 , - :vi_repeat_prev_char, + :ed_unassigned, # 45 - :ed_prev_history, # 46 . - :vi_redo, + :ed_unassigned, # 47 / :vi_search_prev, # 48 0 @@ -117,9 +117,9 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 57 9 :ed_argument_digit, # 58 : - :ed_command, + :ed_unassigned, # 59 ; - :vi_repeat_next_char, + :ed_unassigned, # 60 < :ed_unassigned, # 61 = @@ -157,21 +157,21 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 77 M :ed_unassigned, # 78 N - :vi_repeat_search_prev, + :ed_unassigned, # 79 O - :ed_sequence_lead_in, + :ed_unassigned, # 80 P :vi_paste_prev, # 81 Q :ed_unassigned, # 82 R - :vi_replace_mode, + :ed_unassigned, # 83 S - :vi_substitute_line, + :ed_unassigned, # 84 T :vi_to_prev_char, # 85 U - :vi_undo_line, + :ed_unassigned, # 86 V :ed_unassigned, # 87 W @@ -179,11 +179,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 88 X :ed_delete_prev_char, # 89 Y - :vi_yank_end, + :ed_unassigned, # 90 Z :ed_unassigned, # 91 [ - :ed_sequence_lead_in, + :ed_unassigned, # 92 \ :ed_unassigned, # 93 ] @@ -191,7 +191,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 94 ^ :vi_first_print, # 95 _ - :vi_history_word, + :ed_unassigned, # 96 ` :ed_unassigned, # 97 a @@ -221,7 +221,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 109 m :ed_unassigned, # 110 n - :vi_repeat_search_next, + :ed_unassigned, # 111 o :ed_unassigned, # 112 p @@ -231,11 +231,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 114 r :vi_replace_char, # 115 s - :vi_substitute_char, + :ed_unassigned, # 116 t :vi_to_next_char, # 117 u - :vi_undo, + :ed_unassigned, # 118 v :vi_histedit, # 119 w @@ -253,9 +253,9 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 125 } :ed_unassigned, # 126 ~ - :vi_change_case, - # 127 ^? :ed_unassigned, + # 127 ^? + :em_delete_prev_char, # 128 M-^@ :ed_unassigned, # 129 M-^A @@ -415,7 +415,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 206 M-N :ed_unassigned, # 207 M-O - :ed_sequence_lead_in, + :ed_unassigned, # 208 M-P :ed_unassigned, # 209 M-Q @@ -439,7 +439,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 218 M-Z :ed_unassigned, # 219 M-[ - :ed_sequence_lead_in, + :ed_unassigned, # 220 M-\ :ed_unassigned, # 221 M-] diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb index b8e89f81d8075d..c3d7f9c12d04de 100644 --- a/lib/reline/key_actor/vi_insert.rb +++ b/lib/reline/key_actor/vi_insert.rb @@ -41,7 +41,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base # 19 ^S :vi_search_next, # 20 ^T - :ed_insert, + :ed_transpose_chars, # 21 ^U :vi_kill_line_prev, # 22 ^V @@ -51,7 +51,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base # 24 ^X :ed_insert, # 25 ^Y - :ed_insert, + :em_yank, # 26 ^Z :ed_insert, # 27 ^[ 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..81413505d749be 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 @@ -35,28 +33,49 @@ class Reline::LineEditor vi_next_big_word vi_prev_big_word vi_end_big_word - vi_repeat_next_char - vi_repeat_prev_char } 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 +84,42 @@ 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 + if !@is_multiline 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] + [''] * (buffer.size - 1) + elsif @prompt_proc + 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 +129,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 +163,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,56 +226,42 @@ 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 = '' @vi_arg = nil @waiting_proc = nil - @waiting_operator_proc = nil - @waiting_operator_vi_arg = nil - @completion_journey_data = nil + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + @completion_journey_state = nil @completion_state = CompletionState::NORMAL + @completion_occurs = false @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 +272,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 - 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 + @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 move_cursor_up(val) - if val > 0 - Reline::IOGate.move_cursor_up(val) - @rest_height += val - elsif val < 0 - move_cursor_down(-val) - end + private def split_by_width(str, max_width, offset: 0) + Reline::Unicode.split_by_width(str, max_width, @encoding, offset: offset) 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 + def current_byte_pointer_cursor + calculate_width(current_line.byteslice(0, @byte_pointer)) 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 +335,237 @@ 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_prompt_and_input_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] || '' + if (cached = cached_wraps[[prompt, line]]) + next cached + end + *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact + wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt)).first.compact + wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] } + 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_prompt_and_input_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_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line| + prompt_width = Reline::Unicode.calculate_width(prompt, true) + [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]] + 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] ||= [] + # index 0 is for prompt, index 1 is for line, index 2.. is for dialog + dialog_rows[index + 2] = [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 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 +616,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 +698,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 +706,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 +754,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 +796,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 + before.map { |l| Reline::Unicode.escape_for_print(l) } 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 - 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 +837,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 +858,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) @@ -1306,100 +887,79 @@ def editing_mode @completion_state = CompletionState::PERFECT_MATCH else @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH + complete(list, true) if @config.show_all_if_ambiguous end @perfect_matched = completed else @completion_state = CompletionState::MENU + complete(list, true) if @config.show_all_if_ambiguous 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_waiting_operator if VI_MOTIONS.include?(method_symbol) - old_cursor, old_byte_pointer = @cursor, @byte_pointer - @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1 + old_byte_pointer = @byte_pointer + @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg 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) - 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_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) - @waiting_operator_proc = old_waiting_operator_proc - } + byte_pointer_diff = @byte_pointer - old_byte_pointer + @byte_pointer = old_byte_pointer + send(@vi_waiting_operator, byte_pointer_diff) + cleanup_waiting end else # Ignores operator when not motion is given. block.(false) + cleanup_waiting end - @waiting_operator_proc = nil - @waiting_operator_vi_arg = nil - if @vi_arg - @rerender_all = true - @vi_arg = nil - end + @vi_arg = nil else block.(false) end @@ -1416,7 +976,7 @@ def editing_mode end def wrap_method_call(method_symbol, method_obj, key, with_operator = false) - if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil? + if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil? not_insertion = method_symbol != :ed_insert process_insert(force: not_insertion) end @@ -1433,13 +993,34 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) method_obj.(key) end end - end + end + + private def cleanup_waiting + @waiting_proc = nil + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + @searching_prompt = nil + @drop_terminate_spaces = false + end + + private def process_key(key, method_symbol) + if key.is_a?(Symbol) + cleanup_waiting + elsif @waiting_proc + old_byte_pointer = @byte_pointer + @waiting_proc.call(key) + if @vi_waiting_operator + byte_pointer_diff = @byte_pointer - old_byte_pointer + @byte_pointer = old_byte_pointer + send(@vi_waiting_operator, byte_pointer_diff) + cleanup_waiting + end + @kill_ring.process + return + end - private def process_key(key, method_symbol) if method_symbol and respond_to?(method_symbol, true) method_obj = method(method_symbol) - else - method_obj = nil end if method_symbol and key.is_a?(Symbol) if @vi_arg and argumentable?(method_obj) @@ -1451,7 +1032,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 @@ -1462,8 +1042,6 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) run_for_operators(key, method_symbol) do |with_operator| wrap_method_call(method_symbol, method_obj, key, with_operator) end - elsif @waiting_proc - @waiting_proc.(key) elsif method_obj wrap_method_call(method_symbol, method_obj, key) else @@ -1471,13 +1049,9 @@ 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 - elsif @waiting_proc - @waiting_proc.(key) - @kill_ring.process elsif method_obj if method_symbol == :ed_argument_digit wrap_method_call(method_symbol, method_obj, key) @@ -1493,7 +1067,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 +1096,92 @@ 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 + @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 - 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 + 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 + 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 @is_multiline and @auto_indent_proc and not simplified_rendering? and @line - process_auto_indent + if wrapped_cursor_y >= screen_scroll_top + screen_height + @scroll_partial_screen = wrapped_cursor_y - screen_height + 1 end end @@ -1637,43 +1215,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 + end + + def line() + @buffer_of_lines.join("\n") 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 - @check_new_auto_indent = false + process_auto_indent end def retrieve_completion_block(set_completion_quote_character = false) @@ -1687,7 +1262,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 +1270,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 +1292,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,126 +1311,81 @@ 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 target = before end - if @is_multiline - lines = whole_lines - if @line_index > 0 - preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing - end - if (lines.size - 1) > @line_index - postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n") - end + lines = whole_lines + if @line_index > 0 + preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing + end + if (lines.size - 1) > @line_index + postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n") end [preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)] end 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 - @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 + if @buffer_of_lines.size == 1 + @buffer_of_lines[@line_index] = '' + @byte_pointer = 0 + elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0 + @buffer_of_lines.pop + @line_index -= 1 + @byte_pointer = 0 + elsif @line_index < (@buffer_of_lines.size - 1) + @buffer_of_lines.delete_at(@line_index) @byte_pointer = 0 - @cursor = 0 - @cursor_max = 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 +1394,6 @@ def finished? def finish @finished = true - @rerender_all = true @config.reset end @@ -1895,33 +1424,27 @@ 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 + private def completion_journey_up(key) + if not @config.disable_completion and @config.autocompletion + @completion_state = CompletionState::NORMAL + @completion_occurs = move_completed_list(:up) + end + end + alias_method :menu_complete_backward, :completion_journey_up + # Editline:: +ed-unassigned+ This editor command always results in an error. # GNU Readline:: There is no corresponding macro. private def ed_unassigned(key) end # do nothing 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 +1462,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 +1469,6 @@ def finish return end str = key - bytesize = key.bytesize else begin key.chr.encode(Encoding::UTF_8) @@ -1957,7 +1476,6 @@ def finish return end str = key.chr - bytesize = 1 end if @in_pasting @continuous_insertion_buffer << str @@ -1965,28 +1483,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 +1506,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 @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 +1519,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 + elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0 @line_index -= 1 + @byte_pointer = current_line.bytesize end arg -= 1 ed_prev_char(key, arg: arg) if arg > 0 @@ -2048,157 +1532,109 @@ 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 + alias_method :vi_zero, :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 - @byte_pointer += byte_size - end + @byte_pointer = current_line.bytesize end alias_method :end_of_line, :ed_move_to_end - private def generate_searcher - Fiber.new do |first_key| - prev_search_key = first_key - search_word = String.new(encoding: @encoding) - multibyte_buf = String.new(encoding: 'ASCII-8BIT') - last_hit = nil - case first_key - when "\C-r".ord - prompt_name = 'reverse-i-search' - when "\C-s".ord - prompt_name = 'i-search' + private def generate_searcher(search_key) + search_word = String.new(encoding: @encoding) + multibyte_buf = String.new(encoding: 'ASCII-8BIT') + hit_pointer = nil + lambda do |key| + search_again = false + case key + when "\C-h".ord, "\C-?".ord + grapheme_clusters = search_word.grapheme_clusters + if grapheme_clusters.size > 0 + grapheme_clusters.pop + search_word = grapheme_clusters.join + end + when "\C-r".ord, "\C-s".ord + search_again = true if search_key == key + search_key = key + else + multibyte_buf << key + if multibyte_buf.dup.force_encoding(@encoding).valid_encoding? + search_word << multibyte_buf.dup.force_encoding(@encoding) + multibyte_buf.clear + end end - loop do - key = Fiber.yield(search_word) - search_again = false - case key - when -1 # determined - Reline.last_incremental_search = search_word - break - when "\C-h".ord, "\C-?".ord - grapheme_clusters = search_word.grapheme_clusters - if grapheme_clusters.size > 0 - grapheme_clusters.pop - search_word = grapheme_clusters.join - end - when "\C-r".ord, "\C-s".ord - search_again = true if prev_search_key == key - prev_search_key = key - else - multibyte_buf << key - if multibyte_buf.dup.force_encoding(@encoding).valid_encoding? - search_word << multibyte_buf.dup.force_encoding(@encoding) - multibyte_buf.clear + hit = nil + if not search_word.empty? and @line_backup_in_history&.include?(search_word) + hit_pointer = Reline::HISTORY.size + hit = @line_backup_in_history + else + if search_again + if search_word.empty? and Reline.last_incremental_search + search_word = Reline.last_incremental_search end - end - hit = nil - if not search_word.empty? and @line_backup_in_history&.include?(search_word) - @history_pointer = nil - hit = @line_backup_in_history - else - if search_again - if search_word.empty? and Reline.last_incremental_search - search_word = Reline.last_incremental_search - end - if @history_pointer - case prev_search_key - when "\C-r".ord - history_pointer_base = 0 - history = Reline::HISTORY[0..(@history_pointer - 1)] - when "\C-s".ord - history_pointer_base = @history_pointer + 1 - history = Reline::HISTORY[(@history_pointer + 1)..-1] - end - else - history_pointer_base = 0 - history = Reline::HISTORY - end - elsif @history_pointer - case prev_search_key + if @history_pointer + case search_key when "\C-r".ord history_pointer_base = 0 - history = Reline::HISTORY[0..@history_pointer] + history = Reline::HISTORY[0..(@history_pointer - 1)] when "\C-s".ord - history_pointer_base = @history_pointer - history = Reline::HISTORY[@history_pointer..-1] + history_pointer_base = @history_pointer + 1 + history = Reline::HISTORY[(@history_pointer + 1)..-1] end else history_pointer_base = 0 history = Reline::HISTORY end - case prev_search_key + elsif @history_pointer + case search_key when "\C-r".ord - hit_index = history.rindex { |item| - item.include?(search_word) - } + history_pointer_base = 0 + history = Reline::HISTORY[0..@history_pointer] when "\C-s".ord - hit_index = history.index { |item| - item.include?(search_word) - } - end - if hit_index - @history_pointer = history_pointer_base + hit_index - hit = Reline::HISTORY[@history_pointer] + history_pointer_base = @history_pointer + history = Reline::HISTORY[@history_pointer..-1] end + else + history_pointer_base = 0 + history = Reline::HISTORY end - case prev_search_key + case search_key when "\C-r".ord - prompt_name = 'reverse-i-search' + hit_index = history.rindex { |item| + item.include?(search_word) + } when "\C-s".ord - prompt_name = 'i-search' + hit_index = history.index { |item| + item.include?(search_word) + } end - if hit - if @is_multiline - @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 - @searching_prompt = "(%s)`%s'" % [prompt_name, search_word] - else - @line = hit - @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] - end + if hit_index + hit_pointer = history_pointer_base + hit_index + hit = Reline::HISTORY[hit_pointer] end end + case search_key + when "\C-r".ord + prompt_name = 'reverse-i-search' + when "\C-s".ord + prompt_name = 'i-search' + end + prompt_name = "failed #{prompt_name}" unless hit + [search_word, prompt_name, hit_pointer] end end private def incremental_search_history(key) unless @history_pointer - if @is_multiline - @line_backup_in_history = whole_buffer - else - @line_backup_in_history = @line - end + @line_backup_in_history = whole_buffer end - searcher = generate_searcher - searcher.resume(key) + searcher = generate_searcher(key) @searching_prompt = "(reverse-i-search)`': " termination_keys = ["\C-j".ord] termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators @@ -2210,67 +1646,41 @@ def finish else buffer = @line_backup_in_history end - if @is_multiline - @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 - end + @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 @searching_prompt = nil @waiting_proc = nil - @cursor_max = calculate_width(@line) - @cursor = @byte_pointer = 0 - @rerender_all = true - @cached_prompt_list = nil - searcher.resume(-1) + @byte_pointer = 0 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 - end - @history_pointer = nil + @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 + move_history(nil, line: :end, cursor: :end, save_buffer: false) @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 - searcher.resume(k) + search_word, prompt_name, hit_pointer = searcher.call(k) + Reline.last_incremental_search = search_word + @searching_prompt = "(%s)`%s'" % [prompt_name, search_word] + @searching_prompt += ': ' unless @is_multiline + move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer else if @history_pointer line = Reline::HISTORY[@history_pointer] else line = @line_backup_in_history end - if @is_multiline - @line_backup_in_history = whole_buffer - @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 - end + @line_backup_in_history = whole_buffer + @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 @searching_prompt = nil @waiting_proc = nil - @cursor_max = calculate_width(@line) - @cursor = @byte_pointer = 0 - @rerender_all = true - @cached_prompt_list = nil - searcher.resume(-1) + @byte_pointer = 0 end end } @@ -2286,199 +1696,95 @@ def finish end alias_method :forward_search_history, :vi_search_next - private def ed_search_prev_history(key, arg: 1) - history = nil - h_pointer = nil - line_no = nil - substr = @line.slice(0, @byte_pointer) - if @history_pointer.nil? - return if not @line.empty? and substr.empty? - history = Reline::HISTORY - elsif @history_pointer.zero? - history = nil - h_pointer = nil - else - history = Reline::HISTORY.slice(0, @history_pointer) - end - return if history.nil? - if @is_multiline - h_pointer = history.rindex { |h| - h.split("\n").each_with_index { |l, i| - if l.start_with?(substr) - line_no = i - break - end - } - not line_no.nil? - } - else - h_pointer = history.rindex { |l| - l.start_with?(substr) - } - end - return if h_pointer.nil? - @history_pointer = h_pointer - 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 - else - @line = Reline::HISTORY[@history_pointer] + private def search_history(prefix, pointer_range) + pointer_range.each do |pointer| + lines = Reline::HISTORY[pointer].split("\n") + lines.each_with_index do |line, index| + return [pointer, index] if line.start_with?(prefix) + end end - @cursor_max = calculate_width(@line) + nil + end + + private def ed_search_prev_history(key, arg: 1) + substr = current_line.byteslice(0, @byte_pointer) + return if @history_pointer == 0 + return if @history_pointer.nil? && substr.empty? && !current_line.empty? + + history_range = 0...(@history_pointer || Reline::HISTORY.size) + h_pointer, line_index = search_history(substr, history_range.reverse_each) + return unless h_pointer + move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer) 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) - if @history_pointer.nil? - return - elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty? - return - end - history = Reline::HISTORY.slice((@history_pointer + 1)..-1) - h_pointer = nil - line_no = nil - if @is_multiline - h_pointer = history.index { |h| - h.split("\n").each_with_index { |l, i| - if l.start_with?(substr) - line_no = i - break - end - } - not line_no.nil? - } - else - h_pointer = history.index { |l| - l.start_with?(substr) - } - end - h_pointer += @history_pointer + 1 if h_pointer and @history_pointer + substr = current_line.byteslice(0, @byte_pointer) + return if @history_pointer.nil? + + history_range = @history_pointer + 1...Reline::HISTORY.size + h_pointer, line_index = search_history(substr, history_range) return if h_pointer.nil? and not substr.empty? - @history_pointer = h_pointer - if @is_multiline - if @history_pointer.nil? and substr.empty? - @buffer_of_lines = [] - @line_index = 0 - else - @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") - @line_index = line_no - 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 = '' - else - @line = Reline::HISTORY[@history_pointer] - end - end - @cursor_max = calculate_width(@line) + + move_history(h_pointer, line: line_index || :start, cursor: @byte_pointer) arg -= 1 ed_search_next_history(key, arg: arg) if arg > 0 end alias_method :history_search_forward, :ed_search_next_history - private def ed_prev_history(key, arg: 1) - if @is_multiline and @line_index > 0 - @previous_line_index = @line_index - @line_index -= 1 - return - end - if Reline::HISTORY.empty? - return + private def move_history(history_pointer, line:, cursor:, save_buffer: true) + history_pointer ||= Reline::HISTORY.size + return if history_pointer < 0 || history_pointer > Reline::HISTORY.size + old_history_pointer = @history_pointer || Reline::HISTORY.size + if old_history_pointer == Reline::HISTORY.size + @line_backup_in_history = save_buffer ? whole_buffer : '' + else + Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer end - if @history_pointer.nil? - @history_pointer = Reline::HISTORY.size - 1 - 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 - else - @line_backup_in_history = @line - @line = Reline::HISTORY[@history_pointer] - end - elsif @history_pointer.zero? - return + if history_pointer == Reline::HISTORY.size + buf = @line_backup_in_history + @history_pointer = @line_backup_in_history = nil else - if @is_multiline - Reline::HISTORY[@history_pointer] = whole_buffer - @history_pointer -= 1 - @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 - @history_pointer -= 1 - @line = Reline::HISTORY[@history_pointer] - end + buf = Reline::HISTORY[history_pointer] + @history_pointer = history_pointer end - if @config.editing_mode_is?(:emacs, :vi_insert) - @cursor_max = @cursor = calculate_width(@line) - @byte_pointer = @line.bytesize - elsif @config.editing_mode_is?(:vi_command) - @byte_pointer = @cursor = 0 - @cursor_max = calculate_width(@line) + @buffer_of_lines = buf.split("\n") + @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? + @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line + @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor + end + + private def ed_prev_history(key, arg: 1) + if @line_index > 0 + cursor = current_byte_pointer_cursor + @line_index -= 1 + calculate_nearest_cursor(cursor) + return end + move_history( + (@history_pointer || Reline::HISTORY.size) - 1, + line: :end, + cursor: @config.editing_mode_is?(:vi_command) ? :start : :end, + ) arg -= 1 ed_prev_history(key, arg: arg) if arg > 0 end alias_method :previous_history, :ed_prev_history private def ed_next_history(key, arg: 1) - if @is_multiline and @line_index < (@buffer_of_lines.size - 1) - @previous_line_index = @line_index + if @line_index < (@buffer_of_lines.size - 1) + cursor = current_byte_pointer_cursor @line_index += 1 + calculate_nearest_cursor(cursor) return end - if @history_pointer.nil? - return - elsif @history_pointer == (Reline::HISTORY.size - 1) - if @is_multiline - @history_pointer = nil - @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 - end - else - if @is_multiline - Reline::HISTORY[@history_pointer] = whole_buffer - @history_pointer += 1 - @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 - @history_pointer += 1 - @line = 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 - elsif @config.editing_mode_is?(:vi_command) - @byte_pointer = @cursor = 0 - @cursor_max = calculate_width(@line) - end + move_history( + (@history_pointer || Reline::HISTORY.size) + 1, + line: :start, + cursor: @config.editing_mode_is?(:vi_command) ? :start : :end, + ) arg -= 1 ed_next_history(key, arg: arg) if arg > 0 end @@ -2503,40 +1809,29 @@ 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 - @history_pointer = nil - end finish end 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 @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 +1841,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 @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 +1865,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 +1877,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 @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 @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 +1913,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 +2070,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 +2080,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 +2113,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 @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,78 +2160,81 @@ 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) end - 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| - if byte_pointer_diff > 0 - @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - @line, cut = byteslice!(@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 - @config.editing_mode = :vi_insert - @drop_terminate_spaces = false - } - @waiting_operator_vi_arg = arg + private def vi_change_meta(key, arg: nil) + if @vi_waiting_operator + set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @drop_terminate_spaces = true + @vi_waiting_operator = :vi_change_meta_confirm + @vi_waiting_operator_arg = arg || 1 + end end - private def vi_delete_meta(key, arg: 1) - @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff| - if byte_pointer_diff > 0 - @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - @line, cut = byteslice!(@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 - } - @waiting_operator_vi_arg = arg + private def vi_change_meta_confirm(byte_pointer_diff) + vi_delete_meta_confirm(byte_pointer_diff) + @config.editing_mode = :vi_insert + @drop_terminate_spaces = false end - private def vi_yank(key, arg: 1) - @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff| - if byte_pointer_diff > 0 - cut = @line.byteslice(@byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) - end - copy_for_vi(cut) - } - @waiting_operator_vi_arg = arg + private def vi_delete_meta(key, arg: nil) + if @vi_waiting_operator + set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @vi_waiting_operator = :vi_delete_meta_confirm + @vi_waiting_operator_arg = arg || 1 + end + end + + private def vi_delete_meta_confirm(byte_pointer_diff) + if byte_pointer_diff > 0 + line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) + elsif byte_pointer_diff < 0 + line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) + end + copy_for_vi(cut) + set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) + end + + private def vi_yank(key, arg: nil) + if @vi_waiting_operator + copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @vi_waiting_operator = :vi_yank_confirm + @vi_waiting_operator_arg = arg || 1 + end + end + + private def vi_yank_confirm(byte_pointer_diff) + if byte_pointer_diff > 0 + cut = current_line.byteslice(@byte_pointer, byte_pointer_diff) + elsif byte_pointer_diff < 0 + cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) + end + copy_for_vi(cut) 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 current_line.empty? and @buffer_of_lines.size == 1 + set_current_line('', 0) @eof = true finish else @@ -3017,18 +2245,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 @@ -3039,54 +2264,25 @@ def finish if Reline::HISTORY.empty? return 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 - elsif @history_pointer.zero? - return - else - Reline::HISTORY[@history_pointer] = @line - @history_pointer = 0 - @line = Reline::HISTORY[@history_pointer] - @cursor_max = calculate_width(@line) - @cursor = 0 - @byte_pointer = 0 - end + move_history(0, line: :start, cursor: :start) end private def vi_histedit(key) path = Tempfile.open { |fp| - if @is_multiline - fp.write whole_lines.join("\n") - else - fp.write @line - end + fp.write whole_lines.join("\n") fp.path } system("#{ENV['EDITOR']} #{path}") - if @is_multiline - @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) - end + @buffer_of_lines = File.read(path).split("\n") + @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? + @line_index = 0 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 +2290,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,43 +2316,33 @@ def finish end private def vi_to_column(key, arg: 0) - @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc| - # total has [byte_size, cursor] + # Implementing behavior of vi, not Readline's vi-mode. + @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc| mbchar_width = Reline::Unicode.get_mbchar_width(gc) - if (total.last + mbchar_width) >= arg - break total - elsif (total.last + mbchar_width) >= @cursor_max - break total - else - total = [total.first + gc.bytesize, total.last + mbchar_width] - total - end + break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg + [total_byte_size + gc.bytesize, total_width + mbchar_width] } end 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 +2365,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 +2385,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 +2417,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 +2437,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 + if @buffer_of_lines.size > @line_index + 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,14 +2463,16 @@ 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 - private def em_meta_next(key) + private def emacs_editing_mode(key) + @config.editing_mode = :emacs + end + + private def vi_editing_mode(key) + @config.editing_mode = :vi_insert end end 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/unicode.rb b/lib/reline/unicode.rb index 26ef207ba6fe98..7f94e95287e61f 100644 --- a/lib/reline/unicode.rb +++ b/lib/reline/unicode.rb @@ -128,10 +128,10 @@ def self.calculate_width(str, allow_escape_code = false) end end - def self.split_by_width(str, max_width, encoding = str.encoding) + def self.split_by_width(str, max_width, encoding = str.encoding, offset: 0) lines = [String.new(encoding: encoding)] height = 1 - width = 0 + width = offset rest = str.encode(Encoding::UTF_8) in_zero_width = false seq = String.new(encoding: encoding) @@ -145,7 +145,13 @@ def self.split_by_width(str, max_width, encoding = str.encoding) lines.last << NON_PRINTING_END when csi lines.last << csi - seq << csi + unless in_zero_width + if csi == -"\e[m" || csi == -"\e[0m" + seq.clear + else + seq << csi + end + end when osc lines.last << osc seq << osc diff --git a/lib/reline/version.rb b/lib/reline/version.rb index 4ddc186ebb7062..d68c7d203bb0f9 100644 --- a/lib/reline/version.rb +++ b/lib/reline/version.rb @@ -1,3 +1,3 @@ module Reline - VERSION = '0.4.3' + VERSION = '0.5.3' 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 b1f031a9ae1e2b..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) @@ -3736,7 +3758,7 @@ def jit_guard_known_klass(jit, ctx, asm, known_klass, obj_opnd, insn_opnd, compt ctx.upgrade_opnd_type(insn_opnd, Type::Flonum) end - elsif C.FL_TEST(known_klass, C::RUBY_FL_SINGLETON) && comptime_obj == C.rb_class_attached_object(known_klass) + elsif C.RCLASS_SINGLETON_P(known_klass) && comptime_obj == C.rb_class_attached_object(known_klass) # Singleton classes are attached to one specific object, so we can # avoid one memory access (and potentially the is_heap check) by # looking for the expected object directly. 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/commands/update_command.rb b/lib/rubygems/commands/update_command.rb index 3d6fecaa402202..8e80d4685671b9 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -197,18 +197,17 @@ def preparing_gem_layout_for(version) yield else require "tmpdir" - tmpdir = Dir.mktmpdir - FileUtils.mv Gem.plugindir, tmpdir + Dir.mktmpdir("gem_update") do |tmpdir| + FileUtils.mv Gem.plugindir, tmpdir - status = yield + status = yield - if status - FileUtils.rm_rf tmpdir - else - FileUtils.mv File.join(tmpdir, "plugins"), Gem.plugindir - end + unless status + FileUtils.mv File.join(tmpdir, "plugins"), Gem.plugindir + end - status + status + end 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/syntax_suggest/clean_document.rb b/lib/syntax_suggest/clean_document.rb index 0847a62e271a6a..2790ccae8654f2 100644 --- a/lib/syntax_suggest/clean_document.rb +++ b/lib/syntax_suggest/clean_document.rb @@ -267,7 +267,7 @@ def join_groups(groups) groups.each do |lines| line = lines.first - # Handle the case of multiple groups in a a row + # Handle the case of multiple groups in a row # if one is already replaced, move on next if @document[line.index].empty? diff --git a/lib/time.gemspec b/lib/time.gemspec index a9349a253fa27f..4b9f9e1218237d 100644 --- a/lib/time.gemspec +++ b/lib/time.gemspec @@ -20,8 +20,12 @@ 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)/}) } + srcdir, gemspec = File.split(__FILE__) + spec.files = Dir.chdir(srcdir) do + `git ls-files -z`.split("\x0").reject { |f| + f == gemspec or + f.start_with?(".git", "bin/", "test/", "rakelib/", "Gemfile", "Rakefile") + } end spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 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/load.c b/load.c index 8963a2e751b5e6..1309d566b9cc33 100644 --- a/load.c +++ b/load.c @@ -762,11 +762,13 @@ load_iseq_eval(rb_execution_context_t *ec, VALUE fname) } else { rb_ast_t *ast; + VALUE vast; VALUE parser = rb_parser_new(); rb_parser_set_context(parser, NULL, FALSE); - ast = (rb_ast_t *)rb_parser_load_file(parser, fname); + vast = rb_parser_load_file(parser, fname); + ast = rb_ruby_ast_data_get(vast); - iseq = rb_iseq_new_top(&ast->body, rb_fstring_lit(""), + iseq = rb_iseq_new_top(vast, rb_fstring_lit(""), fname, realpath_internal_cached(realpath_map, fname), NULL); rb_ast_dispose(ast); } @@ -1532,10 +1534,22 @@ rb_f_autoload(VALUE obj, VALUE sym, VALUE file) * autoload?(name, inherit=true) -> String or nil * * Returns _filename_ to be loaded if _name_ is registered as - * +autoload+. + * +autoload+ in the current namespace or one of its ancestors. * * autoload(:B, "b") * autoload?(:B) #=> "b" + * + * module C + * autoload(:D, "d") + * autoload?(:D) #=> "d" + * autoload?(:B) #=> nil + * end + * + * class E + * autoload(:F, "f") + * autoload?(:F) #=> "f" + * autoload?(:B) #=> "b" + * end */ static VALUE @@ -1615,5 +1629,5 @@ Init_load(void) rb_define_global_function("autoload?", rb_f_autoload_p, -1); ruby_dln_libmap = rb_hash_new_with_size(0); - rb_gc_register_mark_object(ruby_dln_libmap); + rb_vm_register_global_object(ruby_dln_libmap); } diff --git a/marshal.c b/marshal.c index dabbdcd663eb1a..e26c600ca2f87c 100644 --- a/marshal.c +++ b/marshal.c @@ -533,7 +533,7 @@ hash_each(VALUE key, VALUE value, VALUE v) static void w_extended(VALUE klass, struct dump_arg *arg, int check) { - if (check && FL_TEST(klass, FL_SINGLETON)) { + if (check && RCLASS_SINGLETON_P(klass)) { VALUE origin = RCLASS_ORIGIN(klass); if (SINGLETON_DUMP_UNABLE_P(klass) || (origin != klass && SINGLETON_DUMP_UNABLE_P(origin))) { @@ -2538,7 +2538,7 @@ compat_allocator_table(void) #define RUBY_UNTYPED_DATA_WARNING 0 compat_allocator_tbl_wrapper = Data_Wrap_Struct(0, mark_marshal_compat_t, free_compat_allocator_table, compat_allocator_tbl); - rb_gc_register_mark_object(compat_allocator_tbl_wrapper); + rb_vm_register_global_object(compat_allocator_tbl_wrapper); return compat_allocator_tbl; } diff --git a/memory_view.c b/memory_view.c index df7be91e76a656..519aad2ca12367 100644 --- a/memory_view.c +++ b/memory_view.c @@ -863,7 +863,7 @@ Init_MemoryView(void) VALUE obj = TypedData_Wrap_Struct( 0, &rb_memory_view_exported_object_registry_data_type, exported_object_table); - rb_gc_register_mark_object(obj); + rb_vm_register_global_object(obj); rb_memory_view_exported_object_registry = obj; id_memory_view = rb_intern_const("__memory_view__"); diff --git a/mini_builtin.c b/mini_builtin.c index 8371073f283058..b38642e89b9f19 100644 --- a/mini_builtin.c +++ b/mini_builtin.c @@ -12,34 +12,36 @@ static struct st_table *loaded_builtin_table; #endif -rb_ast_t *rb_builtin_ast(const char *feature_name, VALUE *name_str); +VALUE rb_builtin_vast(const char *feature_name, VALUE *name_str); static const rb_iseq_t * builtin_iseq_load(const char *feature_name, const struct rb_builtin_function *table) { VALUE name_str = 0; - rb_ast_t *ast = rb_builtin_ast(feature_name, &name_str); + rb_ast_t *ast; + VALUE vast = rb_builtin_vast(feature_name, &name_str); rb_vm_t *vm = GET_VM(); - if (!ast) { + if (NIL_P(vast)) { rb_fatal("builtin_iseq_load: can not find %s; " "probably miniprelude.c is out of date", feature_name); } 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); + ast = rb_ruby_ast_data_get(vast); + const rb_iseq_t *iseq = rb_iseq_new_with_opt(vast, name_str, name_str, Qnil, 0, NULL, 0, ISEQ_TYPE_TOP, &optimization, Qnil); GET_VM()->builtin_function_table = NULL; rb_ast_dispose(ast); @@ -51,7 +53,7 @@ builtin_iseq_load(const char *feature_name, const struct rb_builtin_function *ta #ifndef INCLUDED_BY_BUILTIN_C st_insert(loaded_builtin_table, (st_data_t)feature_name, (st_data_t)iseq); - rb_gc_register_mark_object((VALUE)iseq); + rb_vm_register_global_object((VALUE)iseq); #endif return iseq; 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..33a6c0c357d31e 100644 --- a/node.c +++ b/node.c @@ -10,22 +10,13 @@ **********************************************************************/ #ifdef UNIVERSAL_PARSER - #include #include "node.h" #include "rubyparser.h" #include "internal/parse.h" -#define T_NODE 0x1b - -#else +#endif -#include "internal.h" -#include "internal/hash.h" #include "internal/variable.h" -#include "ruby/ruby.h" -#include "vm_core.h" - -#endif #define NODE_BUF_DEFAULT_SIZE (sizeof(struct RNode) * 16) @@ -39,7 +30,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 +39,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 +50,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 +70,17 @@ 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 +148,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 +218,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 +259,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) { @@ -337,14 +304,16 @@ rb_ast_t * rb_ast_new(const rb_parser_config_t *config) { node_buffer_t *nb = rb_node_buffer_new(config); - return config->ast_new((VALUE)nb); + return config->ast_new(nb); } #else rb_ast_t * rb_ast_new(void) { node_buffer_t *nb = rb_node_buffer_new(); - return IMEMO_NEW(rb_ast_t, imemo_ast, (VALUE)nb); + rb_ast_t *ast = ruby_xcalloc(1, sizeof(rb_ast_t)); + ast->node_buffer = nb; + return ast; } #endif @@ -369,41 +338,38 @@ 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) +script_lines_free(rb_ast_t *ast, rb_parser_ary_t *script_lines) { -#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); + for (long i = 0; i < script_lines->len; i++) { + parser_string_free(ast, (rb_parser_string_t *)script_lines->data[i]); } + xfree(script_lines->data); + xfree(script_lines); } void rb_ast_free(rb_ast_t *ast) { - if (ast->node_buffer) { + /* TODO + * The current impl. of rb_ast_free() and rb_ast_dispose() is complicated. + * Upcoming task of changing `ast->node_buffer->config` to `ast->config`, + * that is based on "deIMEMO" of `rb_ast_t *`, will let us simplify the code. + */ +#ifdef UNIVERSAL_PARSER + if (ast && ast->node_buffer) { + void (*free_func)(void *) = xfree; + if (ast->body.script_lines && !FIXNUM_P((VALUE)ast->body.script_lines)) { + script_lines_free(ast, ast->body.script_lines); + ast->body.script_lines = NULL; + } rb_node_buffer_free(ast, ast->node_buffer); ast->node_buffer = 0; + free_func(ast); } +#else + rb_ast_dispose(ast); + xfree(ast); +#endif } static size_t @@ -421,40 +387,67 @@ buffer_list_size(node_buffer_list_t *nb) size_t rb_ast_memsize(const rb_ast_t *ast) { - size_t size = 0; + size_t size = sizeof(rb_ast_t); node_buffer_t *nb = ast->node_buffer; + rb_parser_ary_t *tokens = NULL; + struct rb_ast_local_table_link *link = NULL; + rb_parser_ary_t *script_lines = ast->body.script_lines; + + long i; 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); + link = nb->local_tables; + tokens = nb->tokens; } - return size; -} -void -rb_ast_dispose(rb_ast_t *ast) -{ - rb_ast_free(ast); -} + while (link) { + size += sizeof(struct rb_ast_local_table_link); + size += link->size * sizeof(ID); + link = link->next; + } -VALUE -rb_ast_tokens(rb_ast_t *ast) -{ - return ast->node_buffer->tokens; + if (tokens) { + size += sizeof(rb_parser_ary_t); + for (i = 0; i < tokens->len; i++) { + size += sizeof(rb_parser_ast_token_t); + rb_parser_ast_token_t *token = tokens->data[i]; + size += sizeof(rb_parser_string_t); + size += token->str->len + 1; + } + } + + if (script_lines && !FIXNUM_P((VALUE)script_lines)) { + size += sizeof(rb_parser_ary_t); + for (i = 0; i < script_lines->len; i++) { + size += sizeof(rb_parser_string_t); + size += ((rb_parser_string_t *)script_lines->data[i])->len + 1; + } + } + + return size; } void -rb_ast_set_tokens(rb_ast_t *ast, VALUE tokens) +rb_ast_dispose(rb_ast_t *ast) { - RB_OBJ_WRITE(ast, &ast->node_buffer->tokens, tokens); +#ifdef UNIVERSAL_PARSER + // noop. See the comment in rb_ast_free(). +#else + if (ast && ast->node_buffer) { + if (ast->body.script_lines && !FIXNUM_P((VALUE)ast->body.script_lines)) { + script_lines_free(ast, ast->body.script_lines); + ast->body.script_lines = NULL; + } + rb_node_buffer_free(ast, ast->node_buffer); + ast->node_buffer = 0; + } +#endif } 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..bcc7e451d2c5ee 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,11 @@ 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 +68,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 bf13e6d5d105f6..37abea8441b4ea 100644 --- a/node_dump.c +++ b/node_dump.c @@ -10,6 +10,7 @@ **********************************************************************/ #include "internal.h" +#include "internal/class.h" #include "internal/hash.h" #include "internal/ruby_parser.h" #include "internal/variable.h" @@ -59,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) @@ -67,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));} @@ -89,7 +107,7 @@ rb_dump_literal(VALUE lit) switch (RB_BUILTIN_TYPE(lit)) { case T_CLASS: case T_MODULE: case T_ICLASS: str = rb_class_path(lit); - if (FL_TEST(lit, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(lit)) { str = rb_sprintf("<%"PRIsVALUE">", str); } return str; @@ -462,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; @@ -512,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; @@ -704,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 daf98a588c0a8b..4db0834ae3fd92 100644 --- a/numeric.c +++ b/numeric.c @@ -5171,7 +5171,7 @@ fix_rshift(long val, unsigned long i) * */ -static VALUE +VALUE rb_int_rshift(VALUE x, VALUE y) { if (FIXNUM_P(x)) { @@ -5455,11 +5455,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; } @@ -6253,19 +6254,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_gc_register_mark_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 7ec944cb7a13c3..0a8ce4dc0fab35 100644 --- a/object.c +++ b/object.c @@ -134,9 +134,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)); @@ -288,7 +287,7 @@ VALUE rb_class_real(VALUE cl) { while (cl && - ((RBASIC(cl)->flags & FL_SINGLETON) || BUILTIN_TYPE(cl) == T_ICLASS)) { + (RCLASS_SINGLETON_P(cl) || BUILTIN_TYPE(cl) == T_ICLASS)) { cl = RCLASS_SUPER(cl); } return cl; @@ -397,10 +396,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); - rb_copy_wb_protected_attribute(dest, obj); + 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); } @@ -493,7 +490,7 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze) VALUE singleton = rb_singleton_class_clone_and_attach(obj, clone); RBASIC_SET_CLASS(clone, singleton); - if (FL_TEST(singleton, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(singleton)) { rb_singleton_class_attached(singleton, clone); } @@ -503,7 +500,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); @@ -517,7 +517,7 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze) static VALUE freeze_true_hash; if (!freeze_true_hash) { freeze_true_hash = rb_hash_new(); - rb_gc_register_mark_object(freeze_true_hash); + rb_vm_register_global_object(freeze_true_hash); rb_hash_aset(freeze_true_hash, ID2SYM(idFreeze), Qtrue); rb_obj_freeze(freeze_true_hash); } @@ -541,7 +541,7 @@ rb_obj_clone_setup(VALUE obj, VALUE clone, VALUE kwfreeze) static VALUE freeze_false_hash; if (!freeze_false_hash) { freeze_false_hash = rb_hash_new(); - rb_gc_register_mark_object(freeze_false_hash); + rb_vm_register_global_object(freeze_false_hash); rb_hash_aset(freeze_false_hash, ID2SYM(idFreeze), Qfalse); rb_obj_freeze(freeze_false_hash); } @@ -658,9 +658,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 @@ -674,13 +674,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 @@ -690,14 +690,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 @@ -869,7 +869,7 @@ rb_obj_is_instance_of(VALUE obj, VALUE c) return RBOOL(rb_obj_class(obj) == c); } -// Returns whether c is a proper (c != cl) subclass of cl +// Returns whether c is a proper (c != cl) superclass of cl // Both c and cl must be T_CLASS static VALUE class_search_class_ancestor(VALUE cl, VALUE c) @@ -882,7 +882,7 @@ class_search_class_ancestor(VALUE cl, VALUE c) VALUE *classes = RCLASS_SUPERCLASSES(cl); // If c's inheritance chain is longer, it cannot be an ancestor - // We are checking for a proper subclass so don't check if they are equal + // We are checking for a proper superclass so don't check if they are equal if (cl_depth <= c_depth) return Qfalse; @@ -1745,7 +1745,7 @@ rb_mod_to_s(VALUE klass) ID id_defined_at; VALUE refined_class, defined_at; - if (FL_TEST(klass, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(klass)) { VALUE s = rb_usascii_str_new2("# 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 *++ */ @@ -3082,7 +3082,7 @@ rb_mod_cvar_defined(VALUE obj, VALUE iv) static VALUE rb_mod_singleton_p(VALUE klass) { - return RBOOL(RB_TYPE_P(klass, T_CLASS) && FL_TEST(klass, FL_SINGLETON)); + return RBOOL(RCLASS_SINGLETON_P(klass)); } /*! \private */ @@ -3236,15 +3236,22 @@ ALWAYS_INLINE(static VALUE rb_to_integer_with_id_exception(VALUE val, const char static inline VALUE rb_to_integer_with_id_exception(VALUE val, const char *method, ID mid, int raise) { + // We need to pop the lazily pushed frame when not raising an exception. + rb_control_frame_t *current_cfp; VALUE v; if (RB_INTEGER_TYPE_P(val)) return val; + current_cfp = GET_EC()->cfp; rb_yjit_lazy_push_frame(GET_EC()->cfp->pc); v = try_to_int(val, mid, raise); - if (!raise && NIL_P(v)) return Qnil; + if (!raise && NIL_P(v)) { + GET_EC()->cfp = current_cfp; + return Qnil; + } if (!RB_INTEGER_TYPE_P(v)) { conversion_mismatch(val, "Integer", method, v); } + GET_EC()->cfp = current_cfp; return v; } #define rb_to_integer(val, method, mid) \ @@ -4094,7 +4101,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 * @@ -4450,7 +4457,7 @@ InitVM_Object(void) rb_cNilClass = rb_define_class("NilClass", rb_cObject); rb_cNilClass_to_s = rb_fstring_enc_lit("", rb_usascii_encoding()); - rb_gc_register_mark_object(rb_cNilClass_to_s); + rb_vm_register_global_object(rb_cNilClass_to_s); rb_define_method(rb_cNilClass, "to_s", rb_nil_to_s, 0); rb_define_method(rb_cNilClass, "to_a", nil_to_a, 0); rb_define_method(rb_cNilClass, "to_h", nil_to_h, 0); @@ -4536,7 +4543,7 @@ InitVM_Object(void) rb_cTrueClass = rb_define_class("TrueClass", rb_cObject); rb_cTrueClass_to_s = rb_fstring_enc_lit("true", rb_usascii_encoding()); - rb_gc_register_mark_object(rb_cTrueClass_to_s); + rb_vm_register_global_object(rb_cTrueClass_to_s); rb_define_method(rb_cTrueClass, "to_s", rb_true_to_s, 0); rb_define_alias(rb_cTrueClass, "inspect", "to_s"); rb_define_method(rb_cTrueClass, "&", true_and, 1); @@ -4548,7 +4555,7 @@ InitVM_Object(void) rb_cFalseClass = rb_define_class("FalseClass", rb_cObject); rb_cFalseClass_to_s = rb_fstring_enc_lit("false", rb_usascii_encoding()); - rb_gc_register_mark_object(rb_cFalseClass_to_s); + rb_vm_register_global_object(rb_cFalseClass_to_s); rb_define_method(rb_cFalseClass, "to_s", rb_false_to_s, 0); rb_define_alias(rb_cFalseClass, "inspect", "to_s"); rb_define_method(rb_cFalseClass, "&", false_and, 1); diff --git a/pack.rb b/pack.rb index d505eaee3570ca..b6e29c3eab4f12 100644 --- a/pack.rb +++ b/pack.rb @@ -17,6 +17,7 @@ class String # returns that array. # See {Packed Data}[rdoc-ref:packed_data.rdoc]. def unpack(fmt, offset: 0) + Primitive.attr! :use_block Primitive.pack_unpack(fmt, offset) end diff --git a/parse.y b/parse.y index de90ee797ff058..0bc884fe6bbde6 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,8 +71,24 @@ #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 +#endif /* !UNIVERSAL_PARSER */ + static int rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2); +#ifndef RIPPER +static rb_parser_string_t *rb_parser_string_deep_copy(struct parser_params *p, const rb_parser_string_t *original); +#endif + static int node_integer_cmp(rb_node_integer_t *n1, rb_node_integer_t *n2) { @@ -117,15 +130,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 +143,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 +169,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 +181,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 +199,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 +301,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 +314,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 +335,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 +343,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,15 +484,13 @@ typedef struct parser_string_buffer { token */ struct parser_params { - rb_imemo_tmpbuf_t *heap; - YYSTYPE *lval; YYLTYPE *yylloc; struct { rb_strterm_t *strterm; - VALUE (*gets)(struct parser_params*,VALUE); - VALUE input; + VALUE (*gets)(struct parser_params*,rb_parser_input_data,int); + rb_parser_input_data input; parser_string_buffer_t string_buffer; rb_parser_string_t *lastline; rb_parser_string_t *nextline; @@ -538,10 +498,6 @@ struct parser_params { const char *pcur; const char *pend; const char *ptok; - union { - long ptr; - VALUE (*call)(VALUE, int); - } gets_; enum lex_state_e state; /* track the nest level of any parens "()[]{}" */ int paren_nest; @@ -567,14 +523,14 @@ 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; VALUE debug_output; struct { - VALUE token; + rb_parser_string_t *token; int beg_line; int beg_col; int end_line; @@ -625,7 +581,7 @@ struct parser_params { unsigned int keep_tokens: 1; VALUE error_buffer; - VALUE debug_lines; + rb_parser_ary_t *debug_lines; /* * Store specific keyword locations to generate dummy end token. * Refer to the tail of list element. @@ -634,7 +590,7 @@ struct parser_params { /* id for terms */ int token_id; /* Array for term tokens */ - VALUE tokens; + rb_parser_ary_t *tokens; #else /* Ripper only */ @@ -750,11 +706,13 @@ after_pop_stack(int len, struct parser_params *p) #define TOK_INTERN() intern_cstr(tok(p), toklen(p), p->enc) #define VALID_SYMNAME_P(s, l, enc, type) (rb_enc_symname_type(s, l, enc, (1U<<(type))) == (int)(type)) +#ifndef RIPPER static inline bool end_with_newline_p(struct parser_params *p, VALUE str) { return RSTRING_LEN(str) > 0 && RSTRING_END(str)[-1] == '\n'; } +#endif static void pop_pvtbl(struct parser_params *p, st_table *tbl) @@ -875,170 +833,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 +1083,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 +1113,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 +1126,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 +1191,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 +1221,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 +1571,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); @@ -1811,9 +1770,6 @@ endless_method_name(struct parser_params *p, ID mid, const YYLTYPE *loc) local_push(p, 0); \ } while (0) -#define Qnone 0 -#define Qnull 0 - #ifndef RIPPER # define ifndef_ripper(x) (x) #else @@ -1847,7 +1803,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 +1825,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 +2017,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 +2032,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,11 +2049,10 @@ 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) +#define PARSER_STRING_END(str) (&str->ptr[str->len]) #define STRING_SIZE(str) ((size_t)str->len + 1) #define STRING_TERM_LEN(str) (1) #define STRING_TERM_FILL(str) (str->ptr[str->len] = '\0') @@ -2115,6 +2067,12 @@ parser_memhash(const void *ptr, long len) ((ptrvar) = str->ptr, \ (lenvar) = str->len) +static inline bool +parser_string_end_with_newline_p(struct parser_params *p, rb_parser_string_t *str) +{ + return PARSER_STRING_LEN(str) > 0 && PARSER_STRING_END(str)[-1] == '\n'; +} + static rb_parser_string_t * rb_parser_string_new(rb_parser_t *p, const char *ptr, long len) { @@ -2161,15 +2119,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) @@ -2230,13 +2190,11 @@ PARSER_ENC_CODERANGE_CLEAR(rb_parser_string_t *str) str->coderange = RB_PARSER_ENC_CODERANGE_UNKNOWN; } -#ifndef RIPPER static bool PARSER_ENC_CODERANGE_ASCIIONLY(rb_parser_string_t *str) { return PARSER_ENC_CODERANGE(str) == RB_PARSER_ENC_CODERANGE_7BIT; } -#endif static bool PARSER_ENC_CODERANGE_CLEAN_P(int cr) @@ -2301,7 +2259,6 @@ rb_parser_enc_str_coderange(struct parser_params *p, rb_parser_string_t *str) return cr; } -#ifndef RIPPER static rb_parser_string_t * rb_parser_enc_associate(struct parser_params *p, rb_parser_string_t *str, rb_encoding *enc) { @@ -2314,7 +2271,6 @@ rb_parser_enc_associate(struct parser_params *p, rb_parser_string_t *str, rb_enc rb_parser_string_set_encoding(str, enc); return str; } -#endif static bool rb_parser_is_ascii_string(struct parser_params *p, rb_parser_string_t *str) @@ -2525,6 +2481,13 @@ rb_parser_enc_cr_str_buf_cat(struct parser_params *p, rb_parser_string_t *str, c } +static rb_parser_string_t * +rb_parser_enc_str_buf_cat(struct parser_params *p, rb_parser_string_t *str, const char *ptr, long len, + rb_encoding *ptr_enc) +{ + return rb_parser_enc_cr_str_buf_cat(p, str, ptr, len, ptr_enc, RB_PARSER_ENC_CODERANGE_UNKNOWN, NULL); +} + static rb_parser_string_t * rb_parser_str_buf_append(struct parser_params *p, rb_parser_string_t *str, rb_parser_string_t *str2) { @@ -2565,8 +2528,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 +2547,121 @@ 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 = (rb_parser_ary_data *)xrealloc(ary->data, sizeof(rb_parser_ary_data) * len); + for (i = ary->len; i < len; i++) { + ary->data[i] = 0; + } + } +} + +/* + * Do not call this directly. + * Use rb_parser_ary_new_capa_for_script_line() or rb_parser_ary_new_capa_for_ast_token() instead. + */ +static rb_parser_ary_t * +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_ary_data *)xcalloc(len, sizeof(rb_parser_ary_data)); + } + else { + ary->data = NULL; + } + return ary; +} + +static rb_parser_ary_t * +rb_parser_ary_new_capa_for_script_line(rb_parser_t *p, long len) +{ + rb_parser_ary_t *ary = parser_ary_new_capa(p, len); + ary->data_type = PARSER_ARY_DATA_SCRIPT_LINE; + return ary; +} + +static rb_parser_ary_t * +rb_parser_ary_new_capa_for_ast_token(rb_parser_t *p, long len) +{ + rb_parser_ary_t *ary = parser_ary_new_capa(p, len); + ary->data_type = PARSER_ARY_DATA_AST_TOKEN; + return ary; +} + +/* + * Do not call this directly. + * Use rb_parser_ary_push_script_line() or rb_parser_ary_push_ast_token() instead. + */ +static rb_parser_ary_t * +parser_ary_push(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ary_data 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 rb_parser_ary_t * +rb_parser_ary_push_ast_token(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ast_token_t *val) +{ + if (ary->data_type != PARSER_ARY_DATA_AST_TOKEN) { + rb_bug("unexpected rb_parser_ary_data_type: %d", ary->data_type); + } + return parser_ary_push(p, ary, val); +} + +static rb_parser_ary_t * +rb_parser_ary_push_script_line(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_string_t *val) +{ + if (ary->data_type != PARSER_ARY_DATA_SCRIPT_LINE) { + rb_bug("unexpected rb_parser_ary_data_type: %d", ary->data_type); + } + return parser_ary_push(p, ary, val); +} + +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_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) +{ + void (*free_func)(rb_parser_t *, rb_parser_ary_data) = NULL; + switch (ary->data_type) { + case PARSER_ARY_DATA_AST_TOKEN: + free_func = (void (*)(rb_parser_t *, rb_parser_ary_data))rb_parser_ast_token_free; + break; + case PARSER_ARY_DATA_SCRIPT_LINE: + free_func = (void (*)(rb_parser_t *, rb_parser_ary_data))rb_parser_string_free; + break; + default: + rb_bug("unexpected rb_parser_ary_data_type: %d", ary->data_type); + break; + } + for (long i = 0; i < ary->len; i++) { + free_func(p, ary->data[i]); + } + xfree(ary); +} + +#endif /* !RIPPER */ %} %expect 0 @@ -2619,9 +2693,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 +2704,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 +2721,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 +2734,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; @@ -3409,7 +3484,7 @@ command : fcall command_args %prec tLOWEST } | primary_value call_op operation2 command_args %prec tLOWEST { - $$ = new_command_qcall(p, $2, $1, $3, $4, Qnull, &@3, &@$); + $$ = new_command_qcall(p, $2, $1, $3, $4, 0, &@3, &@$); /*% ripper: command_call!($:1, $:2, $:3, $:4) %*/ } | primary_value call_op operation2 command_args cmd_brace_block @@ -3419,7 +3494,7 @@ command : fcall command_args %prec tLOWEST } | primary_value tCOLON2 operation2 command_args %prec tLOWEST { - $$ = new_command_qcall(p, idCOLON2, $1, $3, $4, Qnull, &@3, &@$); + $$ = new_command_qcall(p, idCOLON2, $1, $3, $4, 0, &@3, &@$); /*% ripper: command_call!($:1, $:2, $:3, $:4) %*/ } | primary_value tCOLON2 operation2 command_args cmd_brace_block @@ -3430,8 +3505,8 @@ command : fcall command_args %prec tLOWEST | primary_value tCOLON2 tCONSTANT '{' brace_body '}' { 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) %*/ + $$ = new_command_qcall(p, idCOLON2, $1, $3, 0, $5, &@3, &@$); + /*% ripper: method_add_block!(command_call!($:1, $:2, $:3, Qnil), $:5) %*/ } | keyword_super command_args { @@ -4136,7 +4211,7 @@ paren_args : '(' opt_call_args rparen | '(' args ',' args_forward rparen { if (!check_forwarding_args(p)) { - $$ = Qnone; + $$ = 0; } else { $$ = new_args_forward_call(p, $2, &@4, &@$); @@ -4146,7 +4221,7 @@ paren_args : '(' opt_call_args rparen | '(' args_forward rparen { if (!check_forwarding_args(p)) { - $$ = Qnone; + $$ = 0; } else { $$ = new_args_forward_call(p, 0, &@2, &@$); @@ -4249,7 +4324,7 @@ block_arg : tAMPER arg_value } | tAMPER { - forwarding_arg_check(p, idFWD_BLOCK, 0, "block"); + forwarding_arg_check(p, idFWD_BLOCK, idFWD_ALL, "block"); $$ = NEW_BLOCK_PASS(NEW_LVAR(idFWD_BLOCK, &@1), &@$); /*% ripper: Qnil %*/ } @@ -4475,28 +4550,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) %*/ } @@ -4951,17 +5026,17 @@ block_args_tail : f_block_kwarg ',' f_kwrest opt_f_block_arg } | f_block_kwarg opt_f_block_arg { - $$ = new_args_tail(p, $1, Qnone, $2, &@1); + $$ = new_args_tail(p, $1, 0, $2, &@1); /*% ripper: rb_ary_new_from_args(3, get_value($:1), Qnil, get_value($:2)); %*/ } | f_any_kwrest opt_f_block_arg { - $$ = new_args_tail(p, Qnone, $1, $2, &@1); + $$ = new_args_tail(p, 0, $1, $2, &@1); /*% ripper: rb_ary_new_from_args(3, Qnil, get_value($:1), get_value($:2)); %*/ } | f_block_arg { - $$ = new_args_tail(p, Qnone, Qnone, $1, &@1); + $$ = new_args_tail(p, 0, 0, $1, &@1); /*% ripper: rb_ary_new_from_args(3, Qnil, Qnil, get_value($:1)); %*/ } ; @@ -4973,7 +5048,7 @@ opt_block_args_tail : ',' block_args_tail } | /* none */ { - $$ = new_args_tail(p, Qnone, Qnone, Qnone, &@0); + $$ = new_args_tail(p, 0, 0, 0, &@0); /*% ripper: rb_ary_new_from_args(3, Qnil, Qnil, Qnil); %*/ } ; @@ -4988,7 +5063,7 @@ excessed_comma : ',' block_param : f_arg ',' f_block_optarg ',' f_rest_arg opt_block_args_tail { - $$ = new_args(p, $1, $3, $5, Qnone, $6, &@$); + $$ = new_args(p, $1, $3, $5, 0, $6, &@$); /*% ripper: ripper_new_args(p, get_value($:1), get_value($:3), get_value($:5), Qnil, get_value($:6)) %*/ } | f_arg ',' f_block_optarg ',' f_rest_arg ',' f_arg opt_block_args_tail @@ -4998,68 +5073,68 @@ block_param : f_arg ',' f_block_optarg ',' f_rest_arg opt_block_args_tail } | f_arg ',' f_block_optarg opt_block_args_tail { - $$ = new_args(p, $1, $3, Qnone, Qnone, $4, &@$); + $$ = new_args(p, $1, $3, 0, 0, $4, &@$); /*% ripper: ripper_new_args(p, get_value($:1), get_value($:3), Qnil, Qnil, get_value($:4)) %*/ } | f_arg ',' f_block_optarg ',' f_arg opt_block_args_tail { - $$ = new_args(p, $1, $3, Qnone, $5, $6, &@$); + $$ = new_args(p, $1, $3, 0, $5, $6, &@$); /*% ripper: ripper_new_args(p, get_value($:1), get_value($:3), Qnil, get_value($:5), get_value($:6)) %*/ } | f_arg ',' f_rest_arg opt_block_args_tail { - $$ = new_args(p, $1, Qnone, $3, Qnone, $4, &@$); + $$ = new_args(p, $1, 0, $3, 0, $4, &@$); /*% ripper: ripper_new_args(p, get_value($:1), Qnil, get_value($:3), Qnil, get_value($:4)) %*/ } | f_arg excessed_comma { - $$ = new_args_tail(p, Qnone, Qnone, Qnone, &@2); - $$ = new_args(p, $1, Qnone, $2, Qnone, $$, &@$); + $$ = new_args_tail(p, 0, 0, 0, &@2); + $$ = new_args(p, $1, 0, $2, 0, $$, &@$); /*% ripper: ripper_new_args(p, get_value($:1), Qnil, get_value($:2), Qnil, rb_ary_new_from_args(3, Qnil, Qnil, Qnil)) %*/ } | f_arg ',' f_rest_arg ',' f_arg opt_block_args_tail { - $$ = new_args(p, $1, Qnone, $3, $5, $6, &@$); + $$ = new_args(p, $1, 0, $3, $5, $6, &@$); /*% ripper: ripper_new_args(p, get_value($:1), Qnil, get_value($:3), get_value($:5), get_value($:6)) %*/ } | f_arg opt_block_args_tail { - $$ = new_args(p, $1, Qnone, Qnone, Qnone, $2, &@$); + $$ = new_args(p, $1, 0, 0, 0, $2, &@$); /*% ripper: ripper_new_args(p, get_value($:1), Qnil, Qnil, Qnil, get_value($:2)) %*/ } | f_block_optarg ',' f_rest_arg opt_block_args_tail { - $$ = new_args(p, Qnone, $1, $3, Qnone, $4, &@$); + $$ = new_args(p, 0, $1, $3, 0, $4, &@$); /*% ripper: ripper_new_args(p, Qnil, get_value($:1), get_value($:3), Qnil, get_value($:4)) %*/ } | f_block_optarg ',' f_rest_arg ',' f_arg opt_block_args_tail { - $$ = new_args(p, Qnone, $1, $3, $5, $6, &@$); + $$ = new_args(p, 0, $1, $3, $5, $6, &@$); /*% ripper: ripper_new_args(p, Qnil, get_value($:1), get_value($:3), get_value($:5), get_value($:6)) %*/ } | f_block_optarg opt_block_args_tail { - $$ = new_args(p, Qnone, $1, Qnone, Qnone, $2, &@$); + $$ = new_args(p, 0, $1, 0, 0, $2, &@$); /*% ripper: ripper_new_args(p, Qnil, get_value($:1), Qnil, Qnil, get_value($:2)) %*/ } | f_block_optarg ',' f_arg opt_block_args_tail { - $$ = new_args(p, Qnone, $1, Qnone, $3, $4, &@$); + $$ = new_args(p, 0, $1, 0, $3, $4, &@$); /*% ripper: ripper_new_args(p, Qnil, get_value($:1), Qnil, get_value($:3), get_value($:4)) %*/ } | f_rest_arg opt_block_args_tail { - $$ = new_args(p, Qnone, Qnone, $1, Qnone, $2, &@$); + $$ = new_args(p, 0, 0, $1, 0, $2, &@$); /*% ripper: ripper_new_args(p, Qnil, Qnil, get_value($:1), Qnil, get_value($:2)) %*/ } | f_rest_arg ',' f_arg opt_block_args_tail { - $$ = new_args(p, Qnone, Qnone, $1, $3, $4, &@$); + $$ = new_args(p, 0, 0, $1, $3, $4, &@$); /*% ripper: ripper_new_args(p, Qnil, Qnil, get_value($:1), get_value($:3), get_value($:4)) %*/ } | block_args_tail { - $$ = new_args(p, Qnone, Qnone, Qnone, Qnone, $1, &@$); + $$ = new_args(p, 0, 0, 0, 0, $1, &@$); /*% ripper: ripper_new_args(p, Qnil, Qnil, Qnil, Qnil, get_value($:1)) %*/ } ; @@ -5267,7 +5342,7 @@ method_call : fcall paren_args } | primary_value tCOLON2 operation3 { - $$ = new_qcall(p, idCOLON2, $1, $3, Qnull, &@3, &@$); + $$ = new_qcall(p, idCOLON2, $1, $3, 0, &@3, &@$); /*% ripper: call!($:1, $:2, $:3) %*/ } | primary_value call_op paren_args @@ -5439,29 +5514,29 @@ p_top_expr : p_top_expr_body p_top_expr_body : p_expr | p_expr ',' { - $$ = new_array_pattern_tail(p, Qnone, 1, Qnone, Qnone, &@$); - $$ = new_array_pattern(p, Qnone, $1, $$, &@$); + $$ = new_array_pattern_tail(p, 0, 1, 0, 0, &@$); + $$ = new_array_pattern(p, 0, $1, $$, &@$); /*% ripper: ripper_new_array_pattern(p, Qnil, get_value($:1), rb_ary_new()); %*/ } | p_expr ',' p_args { - $$ = new_array_pattern(p, Qnone, $1, $3, &@$); + $$ = new_array_pattern(p, 0, $1, $3, &@$); nd_set_first_loc($$, @1.beg_pos); /*% ripper: ripper_new_array_pattern(p, Qnil, get_value($:1), get_value($:3)); %*/ } | p_find { - $$ = new_find_pattern(p, Qnone, $1, &@$); + $$ = new_find_pattern(p, 0, $1, &@$); /*% ripper: ripper_new_find_pattern(p, Qnil, get_value($:1)); %*/ } | p_args_tail { - $$ = new_array_pattern(p, Qnone, Qnone, $1, &@$); + $$ = new_array_pattern(p, 0, 0, $1, &@$); /*% ripper: ripper_new_array_pattern(p, Qnil, Qnil, get_value($:1)); %*/ } | p_kwargs { - $$ = new_hash_pattern(p, Qnone, $1, &@$); + $$ = new_hash_pattern(p, 0, $1, &@$); /*% ripper: ripper_new_hash_pattern(p, Qnil, get_value($:1)); %*/ } ; @@ -5506,7 +5581,7 @@ p_expr_basic : p_value | p_const p_lparen[p_pktbl] p_args rparen { pop_pktbl(p, $p_pktbl); - $$ = new_array_pattern(p, $p_const, Qnone, $p_args, &@$); + $$ = new_array_pattern(p, $p_const, 0, $p_args, &@$); nd_set_first_loc($$, @p_const.beg_pos); /*% ripper: ripper_new_array_pattern(p, get_value($:p_const), Qnil, get_value($:p_args)); %*/ } @@ -5526,14 +5601,14 @@ p_expr_basic : p_value } | p_const '(' rparen { - $$ = new_array_pattern_tail(p, Qnone, 0, Qnone, Qnone, &@$); - $$ = new_array_pattern(p, $p_const, Qnone, $$, &@$); + $$ = new_array_pattern_tail(p, 0, 0, 0, 0, &@$); + $$ = new_array_pattern(p, $p_const, 0, $$, &@$); /*% ripper: ripper_new_array_pattern(p, get_value($:p_const), Qnil, rb_ary_new()); %*/ } | p_const p_lbracket[p_pktbl] p_args rbracket { pop_pktbl(p, $p_pktbl); - $$ = new_array_pattern(p, $p_const, Qnone, $p_args, &@$); + $$ = new_array_pattern(p, $p_const, 0, $p_args, &@$); nd_set_first_loc($$, @p_const.beg_pos); /*% ripper: ripper_new_array_pattern(p, get_value($:p_const), Qnil, get_value($:p_args)); %*/ } @@ -5553,24 +5628,24 @@ p_expr_basic : p_value } | p_const '[' rbracket { - $$ = new_array_pattern_tail(p, Qnone, 0, Qnone, Qnone, &@$); - $$ = new_array_pattern(p, $1, Qnone, $$, &@$); + $$ = new_array_pattern_tail(p, 0, 0, 0, 0, &@$); + $$ = new_array_pattern(p, $1, 0, $$, &@$); /*% ripper: ripper_new_array_pattern(p, get_value($:1), Qnil, rb_ary_new()); %*/ } | tLBRACK p_args rbracket { - $$ = new_array_pattern(p, Qnone, Qnone, $p_args, &@$); + $$ = new_array_pattern(p, 0, 0, $p_args, &@$); /*% ripper: ripper_new_array_pattern(p, Qnil, Qnil, get_value($:p_args)); %*/ } | tLBRACK p_find rbracket { - $$ = new_find_pattern(p, Qnone, $p_find, &@$); + $$ = new_find_pattern(p, 0, $p_find, &@$); /*% ripper: ripper_new_find_pattern(p, Qnil, get_value($:p_find)); %*/ } | tLBRACK rbracket { - $$ = new_array_pattern_tail(p, Qnone, 0, Qnone, Qnone, &@$); - $$ = new_array_pattern(p, Qnone, Qnone, $$, &@$); + $$ = new_array_pattern_tail(p, 0, 0, 0, 0, &@$); + $$ = new_array_pattern(p, 0, 0, $$, &@$); /*% ripper: ripper_new_array_pattern(p, Qnil, Qnil, rb_ary_new()); %*/ } | tLBRACE p_pktbl lex_ctxt[ctxt] @@ -5581,13 +5656,13 @@ p_expr_basic : p_value { pop_pktbl(p, $p_pktbl); p->ctxt.in_kwarg = $ctxt.in_kwarg; - $$ = new_hash_pattern(p, Qnone, $p_kwargs, &@$); + $$ = new_hash_pattern(p, 0, $p_kwargs, &@$); /*% ripper: ripper_new_hash_pattern(p, Qnil, get_value($:p_kwargs)); %*/ } | tLBRACE rbrace { - $$ = new_hash_pattern_tail(p, Qnone, 0, &@$); - $$ = new_hash_pattern(p, Qnone, $$, &@$); + $$ = new_hash_pattern_tail(p, 0, 0, &@$); + $$ = new_hash_pattern(p, 0, $$, &@$); /*%%%*/ /*% VALUE val = ripper_new_hash_pattern_tail(p, Qnil, 0); @@ -5606,7 +5681,7 @@ p_expr_basic : p_value p_args : p_expr { NODE *pre_args = NEW_LIST($1, &@$); - $$ = new_array_pattern_tail(p, pre_args, 0, Qnone, Qnone, &@$); + $$ = new_array_pattern_tail(p, pre_args, 0, 0, 0, &@$); /*%%%*/ /*% VALUE ary = rb_ary_new_from_args(1, get_value($:1)); @@ -5615,7 +5690,7 @@ p_args : p_expr } | p_args_head { - $$ = new_array_pattern_tail(p, $1, 1, Qnone, Qnone, &@$); + $$ = new_array_pattern_tail(p, $1, 1, 0, 0, &@$); /*%%%*/ /*% set_value(rb_ary_new_from_args(3, get_value($:1), Qnil, Qnil)); @@ -5623,7 +5698,7 @@ p_args : p_expr } | p_args_head p_arg { - $$ = new_array_pattern_tail(p, list_concat($1, $2), 0, Qnone, Qnone, &@$); + $$ = new_array_pattern_tail(p, list_concat($1, $2), 0, 0, 0, &@$); /*%%%*/ /*% VALUE pre_args = rb_ary_concat(get_value($:1), get_value($:2)); @@ -5632,7 +5707,7 @@ p_args : p_expr } | p_args_head p_rest { - $$ = new_array_pattern_tail(p, $1, 1, $2, Qnone, &@$); + $$ = new_array_pattern_tail(p, $1, 1, $2, 0, &@$); /*%%%*/ /*% set_value(rb_ary_new_from_args(3, get_value($:1), get_value($:2), Qnil)); @@ -5662,12 +5737,12 @@ p_args_head : p_arg ',' p_args_tail : p_rest { - $$ = new_array_pattern_tail(p, Qnone, 1, $1, Qnone, &@$); + $$ = new_array_pattern_tail(p, 0, 1, $1, 0, &@$); /*% ripper: ripper_new_array_pattern_tail(p, Qnil, get_value($:1), Qnil); %*/ } | p_rest ',' p_args_post { - $$ = new_array_pattern_tail(p, Qnone, 1, $1, $3, &@$); + $$ = new_array_pattern_tail(p, 0, 1, $1, $3, &@$); /*% ripper: ripper_new_array_pattern_tail(p, Qnil, get_value($:1), get_value($:3)); %*/ } ; @@ -5725,7 +5800,7 @@ p_kwargs : p_kwarg ',' p_any_kwrest } | p_any_kwrest { - $$ = new_hash_pattern_tail(p, new_hash(p, Qnone, &@$), $1, &@$); + $$ = new_hash_pattern_tail(p, new_hash(p, 0, &@$), $1, &@$); /*% ripper: ripper_new_hash_pattern_tail(p, rb_ary_new(), get_value($:1)) %*/ } ; @@ -6361,8 +6436,8 @@ f_opt_paren_args: f_paren_args | none { p->ctxt.in_argdef = 0; - $$ = new_args_tail(p, Qnone, Qnone, Qnone, &@0); - $$ = new_args(p, Qnone, Qnone, Qnone, Qnone, $$, &@0); + $$ = new_args_tail(p, 0, 0, 0, &@0); + $$ = new_args(p, 0, 0, 0, 0, $$, &@0); /*% ripper: ripper_new_args(p, Qnil, Qnil, Qnil, Qnil, rb_ary_new_from_args(3, Qnil, Qnil, Qnil)) %*/ } ; @@ -6402,23 +6477,23 @@ args_tail : f_kwarg ',' f_kwrest opt_f_block_arg } | f_kwarg opt_f_block_arg { - $$ = new_args_tail(p, $1, Qnone, $2, &@1); + $$ = new_args_tail(p, $1, 0, $2, &@1); /*% ripper: rb_ary_new_from_args(3, get_value($:1), Qnil, get_value($:2)); %*/ } | f_any_kwrest opt_f_block_arg { - $$ = new_args_tail(p, Qnone, $1, $2, &@1); + $$ = new_args_tail(p, 0, $1, $2, &@1); /*% ripper: rb_ary_new_from_args(3, Qnil, get_value($:1), get_value($:2)); %*/ } | f_block_arg { - $$ = new_args_tail(p, Qnone, Qnone, $1, &@1); + $$ = new_args_tail(p, 0, 0, $1, &@1); /*% ripper: rb_ary_new_from_args(3, Qnil, Qnil, get_value($:1)); %*/ } | args_forward { add_forwarding_args(p); - $$ = new_args_tail(p, Qnone, $1, arg_FWD_BLOCK, &@1); + $$ = new_args_tail(p, 0, $1, arg_FWD_BLOCK, &@1); $$->nd_ainfo.forwarding = 1; /*% ripper: rb_ary_new_from_args(3, Qnil, get_value($:1), Qnil); %*/ } @@ -6431,14 +6506,14 @@ opt_args_tail : ',' args_tail } | /* none */ { - $$ = new_args_tail(p, Qnone, Qnone, Qnone, &@0); + $$ = new_args_tail(p, 0, 0, 0, &@0); /*% ripper: rb_ary_new_from_args(3, Qnil, Qnil, Qnil); %*/ } ; f_args : f_arg ',' f_optarg ',' f_rest_arg opt_args_tail { - $$ = new_args(p, $1, $3, $5, Qnone, $6, &@$); + $$ = new_args(p, $1, $3, $5, 0, $6, &@$); /*% ripper: ripper_new_args(p, get_value($:1), get_value($:3), get_value($:5), Qnil, get_value($:6)) %*/ } | f_arg ',' f_optarg ',' f_rest_arg ',' f_arg opt_args_tail @@ -6448,68 +6523,68 @@ f_args : f_arg ',' f_optarg ',' f_rest_arg opt_args_tail } | f_arg ',' f_optarg opt_args_tail { - $$ = new_args(p, $1, $3, Qnone, Qnone, $4, &@$); + $$ = new_args(p, $1, $3, 0, 0, $4, &@$); /*% ripper: ripper_new_args(p, get_value($:1), get_value($:3), Qnil, Qnil, get_value($:4)) %*/ } | f_arg ',' f_optarg ',' f_arg opt_args_tail { - $$ = new_args(p, $1, $3, Qnone, $5, $6, &@$); + $$ = new_args(p, $1, $3, 0, $5, $6, &@$); /*% ripper: ripper_new_args(p, get_value($:1), get_value($:3), Qnil, get_value($:5), get_value($:6)) %*/ } | f_arg ',' f_rest_arg opt_args_tail { - $$ = new_args(p, $1, Qnone, $3, Qnone, $4, &@$); + $$ = new_args(p, $1, 0, $3, 0, $4, &@$); /*% ripper: ripper_new_args(p, get_value($:1), Qnil, get_value($:3), Qnil, get_value($:4)) %*/ } | f_arg ',' f_rest_arg ',' f_arg opt_args_tail { - $$ = new_args(p, $1, Qnone, $3, $5, $6, &@$); + $$ = new_args(p, $1, 0, $3, $5, $6, &@$); /*% ripper: ripper_new_args(p, get_value($:1), Qnil, get_value($:3), get_value($:5), get_value($:6)) %*/ } | f_arg opt_args_tail { - $$ = new_args(p, $1, Qnone, Qnone, Qnone, $2, &@$); + $$ = new_args(p, $1, 0, 0, 0, $2, &@$); /*% ripper: ripper_new_args(p, get_value($:1), Qnil, Qnil, Qnil, get_value($:2)) %*/ } | f_optarg ',' f_rest_arg opt_args_tail { - $$ = new_args(p, Qnone, $1, $3, Qnone, $4, &@$); + $$ = new_args(p, 0, $1, $3, 0, $4, &@$); /*% ripper: ripper_new_args(p, Qnil, get_value($:1), get_value($:3), Qnil, get_value($:4)) %*/ } | f_optarg ',' f_rest_arg ',' f_arg opt_args_tail { - $$ = new_args(p, Qnone, $1, $3, $5, $6, &@$); + $$ = new_args(p, 0, $1, $3, $5, $6, &@$); /*% ripper: ripper_new_args(p, Qnil, get_value($:1), get_value($:3), get_value($:5), get_value($:6)) %*/ } | f_optarg opt_args_tail { - $$ = new_args(p, Qnone, $1, Qnone, Qnone, $2, &@$); + $$ = new_args(p, 0, $1, 0, 0, $2, &@$); /*% ripper: ripper_new_args(p, Qnil, get_value($:1), Qnil, Qnil, get_value($:2)) %*/ } | f_optarg ',' f_arg opt_args_tail { - $$ = new_args(p, Qnone, $1, Qnone, $3, $4, &@$); + $$ = new_args(p, 0, $1, 0, $3, $4, &@$); /*% ripper: ripper_new_args(p, Qnil, get_value($:1), Qnil, get_value($:3), get_value($:4)) %*/ } | f_rest_arg opt_args_tail { - $$ = new_args(p, Qnone, Qnone, $1, Qnone, $2, &@$); + $$ = new_args(p, 0, 0, $1, 0, $2, &@$); /*% ripper: ripper_new_args(p, Qnil, Qnil, get_value($:1), Qnil, get_value($:2)) %*/ } | f_rest_arg ',' f_arg opt_args_tail { - $$ = new_args(p, Qnone, Qnone, $1, $3, $4, &@$); + $$ = new_args(p, 0, 0, $1, $3, $4, &@$); /*% ripper: ripper_new_args(p, Qnil, Qnil, get_value($:1), get_value($:3), get_value($:4)) %*/ } | args_tail { - $$ = new_args(p, Qnone, Qnone, Qnone, Qnone, $1, &@$); + $$ = new_args(p, 0, 0, 0, 0, $1, &@$); /*% ripper: ripper_new_args(p, Qnil, Qnil, Qnil, Qnil, get_value($:1)) %*/ } | /* none */ { - $$ = new_args_tail(p, Qnone, Qnone, Qnone, &@0); - $$ = new_args(p, Qnone, Qnone, Qnone, Qnone, $$, &@0); + $$ = new_args_tail(p, 0, 0, 0, &@0); + $$ = new_args(p, 0, 0, 0, 0, $$, &@0); /*% ripper: ripper_new_args(p, Qnil, Qnil, Qnil, Qnil, rb_ary_new_from_args(3, Qnil, Qnil, Qnil)) %*/ } ; @@ -6801,7 +6876,7 @@ opt_f_block_arg : ',' f_block_arg } | none { - $$ = Qnull; + $$ = 0; /*% ripper: Qnil; %*/ } ; @@ -6821,7 +6896,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: @@ -6969,7 +7043,7 @@ terms : term none : /* none */ { - $$ = Qnull; + $$ = 0; /*%%%*/ /*% set_value(rb_ripper_none); @@ -7017,7 +7091,7 @@ do { \ # define yylval_id() (yylval.id) #define set_yylval_noname() set_yylval_id(keyword_nil) -#define has_delayed_token(p) (!NIL_P(p->delayed.token)) +#define has_delayed_token(p) (p->delayed.token != NULL) #ifndef RIPPER #define literal_flush(p, ptr) ((p)->lex.ptok = (ptr)) @@ -7035,35 +7109,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_ast_token(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 +7216,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,10 +7234,13 @@ 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) { + /* p->delayed.token is freed by rb_parser_tokens_free */ parser_append_tokens(p, p->delayed.token, t, line); + } else { + rb_parser_string_free(p, p->delayed.token); } - p->delayed.token = Qnil; + p->delayed.token = NULL; } #else #define literal_flush(p, ptr) ((void)(ptr)) @@ -7135,14 +7277,16 @@ ripper_dispatch_delayed_token(struct parser_params *p, enum yytokentype t) /* save and adjust the location to delayed token for callbacks */ int saved_line = p->ruby_sourceline; const char *saved_tokp = p->lex.ptok; - VALUE s_value; + VALUE s_value, str; if (!has_delayed_token(p)) return; p->ruby_sourceline = p->delayed.beg_line; p->lex.ptok = p->lex.pbeg + p->delayed.beg_col; - s_value = ripper_dispatch1(p, ripper_token2eventid(t), p->delayed.token); + str = rb_str_new_mutable_parser_string(p->delayed.token); + rb_parser_string_free(p, p->delayed.token); + s_value = ripper_dispatch1(p, ripper_token2eventid(t), str); set_parser_s_value(s_value); - p->delayed.token = Qnil; + p->delayed.token = NULL; p->ruby_sourceline = saved_line; p->lex.ptok = saved_tokp; } @@ -7561,22 +7705,12 @@ yycompile0(VALUE arg) struct parser_params *p = (struct parser_params *)arg; int cov = FALSE; - if (!compile_for_eval && !NIL_P(p->ruby_sourcefile_string)) { - if (p->debug_lines && p->ruby_sourceline > 0) { - VALUE str = rb_default_rs; - n = p->ruby_sourceline; - do { - rb_ary_push(p->debug_lines, str); - } while (--n); - } - - if (!e_option_supplied(p)) { - cov = TRUE; - } + if (!compile_for_eval && !NIL_P(p->ruby_sourcefile_string) && !e_option_supplied(p)) { + cov = TRUE; } if (p->debug_lines) { - RB_OBJ_WRITE(p->ast, &p->ast->body.script_lines, p->debug_lines); + p->ast->body.script_lines = p->debug_lines; } parser_prepare(p); @@ -7587,6 +7721,8 @@ yycompile0(VALUE arg) RUBY_DTRACE_PARSE_HOOK(BEGIN); n = yyparse(p); RUBY_DTRACE_PARSE_HOOK(END); + + rb_parser_aset_script_lines_for(p->ruby_sourcefile_string, p->debug_lines); p->debug_lines = 0; xfree(p->lex.strterm); @@ -7607,7 +7743,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,12 +7751,12 @@ 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; - if (!p->ast->body.script_lines) p->ast->body.script_lines = INT2FIX(p->line_count); + if (!p->ast->body.script_lines) p->ast->body.script_lines = (rb_parser_ary_t *)INT2FIX(p->line_count); return TRUE; } @@ -7662,31 +7798,11 @@ must_be_ascii_compatible(struct parser_params *p, VALUE s) return enc; } -static VALUE -lex_get_str(struct parser_params *p, VALUE s) -{ - char *beg, *end, *start; - long len; - - beg = RSTRING_PTR(s); - len = RSTRING_LEN(s); - start = beg; - if (p->lex.gets_.ptr) { - if (len == p->lex.gets_.ptr) return Qnil; - beg += p->lex.gets_.ptr; - len -= p->lex.gets_.ptr; - } - end = memchr(beg, '\n', len); - if (end) len = ++end - beg; - p->lex.gets_.ptr += len; - return rb_str_subseq(s, beg - start, len); -} - static rb_parser_string_t * lex_getline(struct parser_params *p) { rb_parser_string_t *str; - VALUE line = (*p->lex.gets)(p, p->lex.input); + VALUE line = (*p->lex.gets)(p, p->lex.input, p->line_count); if (NIL_P(line)) return 0; must_be_ascii_compatible(p, line); p->line_count++; @@ -7696,61 +7812,14 @@ lex_getline(struct parser_params *p) } #ifndef RIPPER -static rb_ast_t* -parser_compile_string(rb_parser_t *p, VALUE fname, VALUE s, int line) -{ - p->lex.gets = lex_get_str; - p->lex.gets_.ptr = 0; - p->lex.input = rb_str_new_frozen(s); - p->lex.pbeg = p->lex.pcur = p->lex.pend = 0; - - return yycompile(p, fname, line); -} - -rb_ast_t* -rb_ruby_parser_compile_string_path(rb_parser_t *p, VALUE f, VALUE s, int line) -{ - must_be_ascii_compatible(p, s); - return parser_compile_string(p, f, s, line); -} - -rb_ast_t* -rb_ruby_parser_compile_string(rb_parser_t *p, const char *f, VALUE s, int line) -{ - return rb_ruby_parser_compile_string_path(p, rb_filesystem_str_new_cstr(f), s, line); -} - -static VALUE -lex_io_gets(struct parser_params *p, VALUE io) -{ - return rb_io_gets_internal(io); -} - -rb_ast_t* -rb_ruby_parser_compile_file_path(rb_parser_t *p, VALUE fname, VALUE file, int start) -{ - p->lex.gets = lex_io_gets; - p->lex.input = file; - p->lex.pbeg = p->lex.pcur = p->lex.pend = 0; - - return yycompile(p, fname, start); -} - -static VALUE -lex_generic_gets(struct parser_params *p, VALUE input) -{ - return (*p->lex.gets_.call)(input, p->line_count); -} - rb_ast_t* -rb_ruby_parser_compile_generic(rb_parser_t *p, VALUE (*lex_gets)(VALUE, int), VALUE fname, VALUE input, int start) +rb_parser_compile(rb_parser_t *p, rb_parser_lex_gets_func *gets, VALUE fname, rb_parser_input_data input, int line) { - p->lex.gets = lex_generic_gets; - p->lex.gets_.call = lex_gets; + p->lex.gets = gets; p->lex.input = input; p->lex.pbeg = p->lex.pcur = p->lex.pend = 0; - return yycompile(p, fname, start); + return yycompile(p, fname, line); } #endif /* !RIPPER */ @@ -7796,7 +7865,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 +7882,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; } @@ -7831,7 +7900,7 @@ add_delayed_token(struct parser_params *p, const char *tok, const char *end, int if (tok < end) { if (has_delayed_token(p)) { - bool next_line = end_with_newline_p(p, p->delayed.token); + bool next_line = parser_string_end_with_newline_p(p, p->delayed.token); int end_line = (next_line ? 1 : 0) + p->delayed.end_line; int end_col = (next_line ? 0 : p->delayed.end_col); if (end_line != p->ruby_sourceline || end_col != tok - p->lex.pbeg) { @@ -7839,12 +7908,12 @@ add_delayed_token(struct parser_params *p, const char *tok, const char *end, int } } if (!has_delayed_token(p)) { - p->delayed.token = rb_str_buf_new(end - tok); - rb_enc_associate(p->delayed.token, p->enc); + p->delayed.token = rb_parser_string_new(p, 0, 0); + rb_parser_enc_associate(p, p->delayed.token, p->enc); p->delayed.beg_line = p->ruby_sourceline; p->delayed.beg_col = rb_long2int(tok - p->lex.pbeg); } - rb_str_buf_cat(p->delayed.token, tok, end - tok); + rb_parser_str_buf_cat(p, p->delayed.token, tok, end - tok); p->delayed.end_line = p->ruby_sourceline; p->delayed.end_col = rb_long2int(end - p->lex.pbeg); p->lex.ptok = end; @@ -7880,9 +7949,9 @@ nextline(struct parser_params *p, int set_encoding) } #ifndef RIPPER if (p->debug_lines) { - VALUE v = rb_str_new_parser_string(str); - if (set_encoding) rb_enc_associate(v, p->enc); - rb_ary_push(p->debug_lines, v); + if (set_encoding) rb_parser_enc_associate(p, str, p->enc); + rb_parser_string_t *copy = rb_parser_string_deep_copy(p, str); + rb_parser_ary_push_script_line(p, p->debug_lines, copy); } #endif p->cr_seen = FALSE; @@ -8042,7 +8111,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, @@ -8713,7 +8782,7 @@ flush_string_content(struct parser_params *p, rb_encoding *enc) if (has_delayed_token(p)) { ptrdiff_t len = p->lex.pcur - p->lex.ptok; if (len > 0) { - rb_enc_str_buf_cat(p->delayed.token, p->lex.ptok, len, enc); + rb_parser_enc_str_buf_cat(p, p->delayed.token, p->lex.ptok, len, enc); p->delayed.end_line = p->ruby_sourceline; p->delayed.end_col = rb_long2int(p->lex.pcur - p->lex.pbeg); } @@ -9230,7 +9299,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); } @@ -9276,7 +9345,7 @@ here_document(struct parser_params *p, rb_strterm_heredoc_t *here) enc = rb_ascii8bit_encoding(); } } - rb_enc_str_buf_cat(p->delayed.token, p->lex.ptok, len, enc); + rb_parser_enc_str_buf_cat(p, p->delayed.token, p->lex.ptok, len, enc); } dispatch_delayed_token(p, tSTRING_CONTENT); } @@ -9558,10 +9627,9 @@ parser_set_encode(struct parser_params *p, const char *name) p->enc = enc; #ifndef RIPPER if (p->debug_lines) { - VALUE lines = p->debug_lines; - long i, n = RARRAY_LEN(lines); - for (i = 0; i < n; ++i) { - rb_enc_associate_index(RARRAY_AREF(lines, i), idx); + long i; + for (i = 0; i < p->debug_lines->len; i++) { + rb_parser_enc_associate(p, p->debug_lines->data[i], enc); } } #endif @@ -9649,23 +9717,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 +10105,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 +11509,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 +12199,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 +12402,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 +12609,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; } @@ -12771,16 +12833,29 @@ literal_concat0(struct parser_params *p, rb_parser_string_t *head, rb_parser_str static rb_parser_string_t * string_literal_head(struct parser_params *p, enum node_type htype, NODE *head) { - if (htype != NODE_DSTR) return false; + if (htype != NODE_DSTR) return NULL; if (RNODE_DSTR(head)->nd_next) { head = RNODE_LIST(RNODE_LIST(RNODE_DSTR(head)->nd_next)->as.nd_end)->nd_head; - if (!head || !nd_type_p(head, NODE_STR)) return false; + if (!head || !nd_type_p(head, NODE_STR)) return NULL; } rb_parser_string_t *lit = RNODE_DSTR(head)->string; - ASSUME(lit != false); + ASSUME(lit); return lit; } +#ifndef RIPPER +static rb_parser_string_t * +rb_parser_string_deep_copy(struct parser_params *p, const rb_parser_string_t *orig) +{ + rb_parser_string_t *copy; + if (!orig) return NULL; + copy = rb_parser_string_new(p, PARSER_STRING_PTR(orig), PARSER_STRING_LEN(orig)); + copy->coderange = orig->coderange; + copy->enc = orig->enc; + return copy; +} +#endif + /* concat two string literals */ static NODE * literal_concat(struct parser_params *p, NODE *head, NODE *tail, const YYLTYPE *loc) @@ -13358,36 +13433,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 +13742,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 +13816,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,266 +14017,15 @@ mark_lvar_used(struct parser_params *p, NODE *rhs) } } +static int is_static_content(NODE *node); + 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) +node_assign(struct parser_params *p, NODE *lhs, NODE *rhs, struct lex_context ctxt, const YYLTYPE *loc) { if (!lhs) return 0; 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 +14212,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 +14357,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 +14491,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 +14665,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 +14856,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) { @@ -15069,17 +14906,10 @@ nd_value(struct parser_params *p, NODE *node) } } -void -rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) +static void +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; @@ -15097,9 +14927,9 @@ rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) key = (st_data_t)head; if (st_delete(literal_keys, &key, &data)) { - rb_compile_warn(p->ruby_sourcefile, nd_line((NODE *)data), - "key %+"PRIsVALUE" is duplicated and overwritten on line %d", - nd_value(p, head), nd_line(head)); + rb_warn2L(nd_line((NODE *)data), + "key %+"PRIsWARN" is duplicated and overwritten on line %d", + nd_value(p, head), WARN_I(nd_line(head))); } st_insert(literal_keys, (st_data_t)key, (st_data_t)hash); } @@ -15107,12 +14937,11 @@ rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) } st_free_table(literal_keys); } -#endif static NODE * new_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc) { - if (hash) rb_parser_warn_duplicate_keys(p, hash); + if (hash) warn_duplicate_keys(p, hash); return NEW_HASH(hash, loc); } @@ -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 @@ -15967,13 +15777,13 @@ parser_initialize(struct parser_params *p) p->lex.lpar_beg = -1; /* make lambda_beginning_p() == FALSE at first */ string_buffer_init(p); p->node_id = 0; - p->delayed.token = Qnil; + p->delayed.token = NULL; p->frozen_string_literal = -1; /* not specified */ #ifndef RIPPER 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; @@ -15998,15 +15808,9 @@ rb_ruby_parser_mark(void *ptr) { struct parser_params *p = (struct parser_params*)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 +15821,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 +15829,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_ary_free(p, p->tokens); + } +#endif + if (p->tokenbuf) { ruby_sized_xfree(p->tokenbuf, p->toksiz); } @@ -16050,6 +15857,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); } @@ -16068,20 +15879,6 @@ rb_ruby_parser_memsize(const void *ptr) return size; } -#ifndef UNIVERSAL_PARSER -#ifndef RIPPER -static const rb_data_type_t parser_data_type = { - "parser", - { - rb_ruby_parser_mark, - rb_ruby_parser_free, - rb_ruby_parser_memsize, - }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY -}; -#endif -#endif - #ifndef RIPPER #undef rb_reserved_word @@ -16109,6 +15906,23 @@ rb_ruby_parser_new(const rb_parser_config_t *config) parser_initialize(p); return p; } +#else +rb_parser_t * +rb_ruby_parser_allocate(void) +{ + /* parser_initialize expects fields to be set to 0 */ + rb_parser_t *p = (rb_parser_t *)ruby_xcalloc(1, sizeof(rb_parser_t)); + return p; +} + +rb_parser_t * +rb_ruby_parser_new(void) +{ + /* parser_initialize expects fields to be set to 0 */ + rb_parser_t *p = rb_ruby_parser_allocate(); + parser_initialize(p); + return p; +} #endif rb_parser_t * @@ -16120,19 +15934,9 @@ rb_ruby_parser_set_context(rb_parser_t *p, const struct rb_iseq_struct *base, in } void -rb_ruby_parser_set_script_lines(rb_parser_t *p, VALUE lines) +rb_ruby_parser_set_script_lines(rb_parser_t *p) { - if (!RTEST(lines)) { - lines = Qfalse; - } - else if (lines == Qtrue) { - lines = rb_ary_new(); - } - else { - Check_Type(lines, T_ARRAY); - rb_ary_modify(lines); - } - p->debug_lines = lines; + p->debug_lines = rb_parser_ary_new_capa_for_script_line(p, 10); } void @@ -16145,140 +15949,13 @@ void rb_ruby_parser_keep_tokens(rb_parser_t *p) { p->keep_tokens = 1; - // TODO - p->tokens = rb_ary_new(); -} - -#ifndef UNIVERSAL_PARSER -rb_ast_t* -rb_parser_compile_file_path(VALUE vparser, VALUE fname, VALUE file, int start) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - RB_GC_GUARD(vparser); /* prohibit tail call optimization */ - return rb_ruby_parser_compile_file_path(p, fname, file, start); -} - -rb_ast_t* -rb_parser_compile_generic(VALUE vparser, VALUE (*lex_gets)(VALUE, int), VALUE fname, VALUE input, int start) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - RB_GC_GUARD(vparser); /* prohibit tail call optimization */ - return rb_ruby_parser_compile_generic(p, lex_gets, fname, input, start); -} - -rb_ast_t* -rb_parser_compile_string(VALUE vparser, const char *f, VALUE s, int line) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - RB_GC_GUARD(vparser); /* prohibit tail call optimization */ - return rb_ruby_parser_compile_string(p, f, s, line); -} - -rb_ast_t* -rb_parser_compile_string_path(VALUE vparser, VALUE f, VALUE s, int line) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - RB_GC_GUARD(vparser); /* prohibit tail call optimization */ - return rb_ruby_parser_compile_string_path(p, f, s, line); -} - -VALUE -rb_parser_encoding(VALUE vparser) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - return rb_ruby_parser_encoding(p); -} - -VALUE -rb_parser_end_seen_p(VALUE vparser) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - return RBOOL(rb_ruby_parser_end_seen_p(p)); + p->tokens = rb_parser_ary_new_capa_for_ast_token(p, 10); } -void -rb_parser_error_tolerant(VALUE vparser) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - rb_ruby_parser_error_tolerant(p); -} - -void -rb_parser_set_script_lines(VALUE vparser, VALUE lines) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - rb_ruby_parser_set_script_lines(p, lines); -} - -void -rb_parser_keep_tokens(VALUE vparser) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - rb_ruby_parser_keep_tokens(p); -} - -VALUE -rb_parser_new(void) -{ - struct parser_params *p; - VALUE parser = TypedData_Make_Struct(0, struct parser_params, - &parser_data_type, p); - parser_initialize(p); - return parser; -} - -VALUE -rb_parser_set_context(VALUE vparser, const struct rb_iseq_struct *base, int main) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - rb_ruby_parser_set_context(p, base, main); - return vparser; -} - -void -rb_parser_set_options(VALUE vparser, int print, int loop, int chomp, int split) -{ - struct parser_params *p; - - TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - rb_ruby_parser_set_options(p, print, loop, chomp, split); -} - -VALUE -rb_parser_set_yydebug(VALUE self, VALUE flag) -{ - struct parser_params *p; - - TypedData_Get_Struct(self, struct parser_params, &parser_data_type, p); - rb_ruby_parser_set_yydebug(p, RTEST(flag)); - return flag; -} -#endif /* !UNIVERSAL_PARSER */ - -VALUE +rb_encoding * rb_ruby_parser_encoding(rb_parser_t *p) { - return rb_enc_from_encoding(p->enc); + return p->enc; } int @@ -16339,7 +16016,7 @@ rb_ruby_parser_set_parsing_thread(rb_parser_t *p, VALUE parsing_thread) } void -rb_ruby_parser_ripper_initialize(rb_parser_t *p, VALUE (*gets)(struct parser_params*,VALUE), VALUE input, VALUE sourcefile_string, const char *sourcefile, int sourceline) +rb_ruby_parser_ripper_initialize(rb_parser_t *p, rb_parser_lex_gets_func *gets, rb_parser_input_data input, VALUE sourcefile_string, const char *sourcefile, int sourceline) { p->lex.gets = gets; p->lex.input = input; @@ -16411,12 +16088,6 @@ rb_ruby_ripper_dedent_string(rb_parser_t *p, VALUE string, int width) return i; } -VALUE -rb_ruby_ripper_lex_get_str(rb_parser_t *p, VALUE s) -{ - return lex_get_str(p, s); -} - int rb_ruby_ripper_initialized_p(rb_parser_t *p) { @@ -16453,6 +16124,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 +16142,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 8dc9c33159ff72..36be735d052b16 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -1,3 +1,289 @@ +errors: + - ALIAS_ARGUMENT + - ALIAS_ARGUMENT_NUMBERED_REFERENCE + - AMPAMPEQ_MULTI_ASSIGN + - ARGUMENT_AFTER_BLOCK + - ARGUMENT_AFTER_FORWARDING_ELLIPSES + - ARGUMENT_BARE_HASH + - ARGUMENT_BLOCK_FORWARDING + - ARGUMENT_BLOCK_MULTI + - ARGUMENT_FORMAL_CLASS + - ARGUMENT_FORMAL_CONSTANT + - ARGUMENT_FORMAL_GLOBAL + - ARGUMENT_FORMAL_IVAR + - ARGUMENT_FORWARDING_UNBOUND + - ARGUMENT_IN + - 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 + - ARGUMENT_UNEXPECTED_BLOCK + - ARRAY_ELEMENT + - ARRAY_EXPRESSION + - ARRAY_EXPRESSION_AFTER_STAR + - ARRAY_SEPARATOR + - ARRAY_TERM + - BEGIN_LONELY_ELSE + - BEGIN_TERM + - BEGIN_UPCASE_BRACE + - BEGIN_UPCASE_TERM + - BEGIN_UPCASE_TOPLEVEL + - BLOCK_PARAM_LOCAL_VARIABLE + - BLOCK_PARAM_PIPE_TERM + - BLOCK_TERM_BRACE + - BLOCK_TERM_END + - CANNOT_PARSE_EXPRESSION + - CANNOT_PARSE_STRING_PART + - CASE_EXPRESSION_AFTER_CASE + - CASE_EXPRESSION_AFTER_WHEN + - CASE_MATCH_MISSING_PREDICATE + - CASE_MISSING_CONDITIONS + - CASE_TERM + - CLASS_IN_METHOD + - CLASS_NAME + - CLASS_SUPERCLASS + - CLASS_TERM + - CLASS_UNEXPECTED_END + - CLASS_VARIABLE_BARE + - CONDITIONAL_ELSIF_PREDICATE + - CONDITIONAL_IF_PREDICATE + - CONDITIONAL_PREDICATE_TERM + - CONDITIONAL_TERM + - CONDITIONAL_TERM_ELSE + - CONDITIONAL_UNLESS_PREDICATE + - CONDITIONAL_UNTIL_PREDICATE + - CONDITIONAL_WHILE_PREDICATE + - CONSTANT_PATH_COLON_COLON_CONSTANT + - DEF_ENDLESS + - DEF_ENDLESS_SETTER + - DEF_NAME + - DEF_PARAMS_TERM + - DEF_PARAMS_TERM_PAREN + - DEF_RECEIVER + - DEF_RECEIVER_TERM + - DEF_TERM + - DEFINED_EXPRESSION + - EMBDOC_TERM + - EMBEXPR_END + - EMBVAR_INVALID + - END_UPCASE_BRACE + - END_UPCASE_TERM + - ESCAPE_INVALID_CONTROL + - ESCAPE_INVALID_CONTROL_REPEAT + - ESCAPE_INVALID_HEXADECIMAL + - ESCAPE_INVALID_META + - ESCAPE_INVALID_META_REPEAT + - ESCAPE_INVALID_UNICODE + - ESCAPE_INVALID_UNICODE_CM_FLAGS + - ESCAPE_INVALID_UNICODE_LITERAL + - ESCAPE_INVALID_UNICODE_LONG + - ESCAPE_INVALID_UNICODE_TERM + - EXPECT_ARGUMENT + - EXPECT_EOL_AFTER_STATEMENT + - EXPECT_EXPRESSION_AFTER_AMPAMPEQ + - EXPECT_EXPRESSION_AFTER_COMMA + - EXPECT_EXPRESSION_AFTER_EQUAL + - EXPECT_EXPRESSION_AFTER_LESS_LESS + - EXPECT_EXPRESSION_AFTER_LPAREN + - EXPECT_EXPRESSION_AFTER_OPERATOR + - EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ + - EXPECT_EXPRESSION_AFTER_QUESTION + - EXPECT_EXPRESSION_AFTER_SPLAT + - EXPECT_EXPRESSION_AFTER_SPLAT_HASH + - EXPECT_EXPRESSION_AFTER_STAR + - EXPECT_IDENT_REQ_PARAMETER + - EXPECT_LPAREN_REQ_PARAMETER + - EXPECT_MESSAGE + - EXPECT_RBRACKET + - EXPECT_RPAREN + - EXPECT_RPAREN_AFTER_MULTI + - EXPECT_RPAREN_REQ_PARAMETER + - EXPECT_STRING_CONTENT + - EXPECT_WHEN_DELIMITER + - EXPRESSION_BARE_HASH + - EXPRESSION_NOT_WRITABLE + - EXPRESSION_NOT_WRITABLE_ENCODING + - EXPRESSION_NOT_WRITABLE_FALSE + - EXPRESSION_NOT_WRITABLE_FILE + - EXPRESSION_NOT_WRITABLE_LINE + - EXPRESSION_NOT_WRITABLE_NIL + - EXPRESSION_NOT_WRITABLE_SELF + - EXPRESSION_NOT_WRITABLE_TRUE + - FLOAT_PARSE + - FOR_COLLECTION + - FOR_IN + - FOR_INDEX + - FOR_TERM + - GLOBAL_VARIABLE_BARE + - HASH_EXPRESSION_AFTER_LABEL + - HASH_KEY + - HASH_ROCKET + - HASH_TERM + - HASH_VALUE + - HEREDOC_TERM + - INCOMPLETE_QUESTION_MARK + - INCOMPLETE_VARIABLE_CLASS + - 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 + - INVALID_LOCAL_VARIABLE_READ + - INVALID_LOCAL_VARIABLE_WRITE + - INVALID_MULTIBYTE_CHAR + - INVALID_MULTIBYTE_CHARACTER + - INVALID_MULTIBYTE_ESCAPE + - INVALID_NUMBER_BINARY + - INVALID_NUMBER_DECIMAL + - INVALID_NUMBER_HEXADECIMAL + - INVALID_NUMBER_OCTAL + - 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 + - LAMBDA_TERM_BRACE + - LAMBDA_TERM_END + - LIST_I_LOWER_ELEMENT + - LIST_I_LOWER_TERM + - LIST_I_UPPER_ELEMENT + - LIST_I_UPPER_TERM + - LIST_W_LOWER_ELEMENT + - LIST_W_LOWER_TERM + - LIST_W_UPPER_ELEMENT + - LIST_W_UPPER_TERM + - MALLOC_FAILED + - MIXED_ENCODING + - MODULE_IN_METHOD + - MODULE_NAME + - MODULE_TERM + - MULTI_ASSIGN_MULTI_SPLATS + - MULTI_ASSIGN_UNEXPECTED_REST + - NO_LOCAL_VARIABLE + - NOT_EXPRESSION + - NUMBER_LITERAL_UNDERSCORE + - NUMBERED_PARAMETER_IT + - NUMBERED_PARAMETER_ORDINARY + - NUMBERED_PARAMETER_OUTER_SCOPE + - OPERATOR_MULTI_ASSIGN + - OPERATOR_WRITE_ARGUMENTS + - OPERATOR_WRITE_BLOCK + - PARAMETER_ASSOC_SPLAT_MULTI + - PARAMETER_BLOCK_MULTI + - PARAMETER_CIRCULAR + - PARAMETER_METHOD_NAME + - PARAMETER_NAME_DUPLICATED + - PARAMETER_NO_DEFAULT + - PARAMETER_NO_DEFAULT_KW + - PARAMETER_NUMBERED_RESERVED + - PARAMETER_ORDER + - PARAMETER_SPLAT_MULTI + - PARAMETER_STAR + - PARAMETER_UNEXPECTED_FWD + - PARAMETER_WILD_LOOSE_COMMA + - PATTERN_CAPTURE_DUPLICATE + - PATTERN_EXPRESSION_AFTER_BRACKET + - PATTERN_EXPRESSION_AFTER_COMMA + - PATTERN_EXPRESSION_AFTER_HROCKET + - PATTERN_EXPRESSION_AFTER_IN + - PATTERN_EXPRESSION_AFTER_KEY + - PATTERN_EXPRESSION_AFTER_PAREN + - PATTERN_EXPRESSION_AFTER_PIN + - PATTERN_EXPRESSION_AFTER_PIPE + - PATTERN_EXPRESSION_AFTER_RANGE + - PATTERN_EXPRESSION_AFTER_REST + - PATTERN_HASH_KEY + - PATTERN_HASH_KEY_DUPLICATE + - PATTERN_HASH_KEY_LABEL + - PATTERN_HASH_KEY_LOCALS + - PATTERN_IDENT_AFTER_HROCKET + - PATTERN_LABEL_AFTER_COMMA + - PATTERN_REST + - PATTERN_TERM_BRACE + - PATTERN_TERM_BRACKET + - PATTERN_TERM_PAREN + - PIPEPIPEEQ_MULTI_ASSIGN + - REGEXP_ENCODING_OPTION_MISMATCH + - REGEXP_INCOMPAT_CHAR_ENCODING + - 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 + - STATEMENT_PREEXE_BEGIN + - STATEMENT_UNDEF + - STRING_CONCATENATION + - STRING_INTERPOLATED_TERM + - STRING_LITERAL_EOF + - STRING_LITERAL_TERM + - SYMBOL_INVALID + - SYMBOL_TERM_DYNAMIC + - SYMBOL_TERM_INTERPOLATED + - TERNARY_COLON + - TERNARY_EXPRESSION_FALSE + - TERNARY_EXPRESSION_TRUE + - UNARY_RECEIVER + - UNDEF_ARGUMENT + - UNEXPECTED_BLOCK_ARGUMENT + - UNEXPECTED_TOKEN_CLOSE_CONTEXT + - UNEXPECTED_TOKEN_IGNORE + - UNTIL_TERM + - VOID_EXPRESSION + - WHILE_TERM + - WRITE_TARGET_IN_METHOD + - WRITE_TARGET_READONLY + - WRITE_TARGET_UNEXPECTED + - XSTRING_TERM +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 + - UNREACHABLE_STATEMENT + - UNUSED_LOCAL_VARIABLE + - VOID_STATEMENT tokens: - name: EOF value: 1 @@ -368,6 +654,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 @@ -413,6 +706,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 @@ -420,7 +722,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: @@ -436,10 +740,25 @@ nodes: fields: - name: new_name type: node + comment: | + Represents the new name of the global variable that can be used after aliasing. This can be either a global variable, a back reference, or a numbered reference. + + alias $foo $bar + ^^^^ - name: old_name type: node + comment: | + Represents the old name of the global variable that could be used before aliasing. This can be either a global variable, a back reference, or a numbered reference. + + alias $foo $bar + ^^^^ - name: keyword_loc type: location + comment: | + The location of the `alias` keyword. + + alias $foo $bar + ^^^^^ comment: | Represents the use of the `alias` keyword to alias a global variable. @@ -524,10 +843,25 @@ nodes: kind: ArrayNodeFlags - name: elements type: node[] + comment: Represent the list of zero or more [non-void expressions](https://github.com/ruby/prism/blob/main/docs/parsing_rules.md#non-void-expression) within the array. - name: opening_loc type: location? + comment: | + Represents the optional source location for the opening token. + + [1,2,3] # "[" + %w[foo bar baz] # "%w[" + %I(apple orange banana) # "%I(" + foo = 1, 2, 3 # nil - name: closing_loc type: location? + comment: | + Represents the optional source location for the closing token. + + [1,2,3] # "]" + %w[foo bar baz] # "]" + %I(apple orange banana) # ")" + foo = 1, 2, 3 # nil comment: | Represents an array literal. This can be a regular array using brackets or a special array using % like %w or %i. @@ -746,8 +1080,18 @@ nodes: - name: arguments type: node? kind: ArgumentsNode + comment: | + The arguments to the break statement, if present. These can be any [non-void expressions](https://github.com/ruby/prism/blob/main/docs/parsing_rules.md#non-void-expression). + + break foo + ^^^ - name: keyword_loc type: location + comment: | + The location of the `break` keyword. + + break foo + ^^^^^ comment: | Represents the use of the `break` keyword. @@ -1057,12 +1401,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. @@ -1607,26 +1976,87 @@ nodes: fields: - name: if_keyword_loc type: location? + comment: | + The location of the `if` keyword if present. + + bar if foo + ^^ + + The `if_keyword_loc` field will be `nil` when the `IfNode` represents a ternary expression. - name: predicate type: node + comment: | + The node for the condition the `IfNode` is testing. + + if foo + ^^^ + bar + end + + bar if foo + ^^^ + + foo ? bar : baz + ^^^ - name: then_keyword_loc type: location? + comment: | + The location of the `then` keyword (if present) or the `?` in a ternary expression, `nil` otherwise. + + if foo then bar end + ^^^^ + + a ? b : c + ^ - name: statements type: node? kind: StatementsNode + comment: | + Represents the body of statements that will be executed when the predicate is evaluated as truthy. Will be `nil` when no body is provided. + + if foo + bar + ^^^ + baz + ^^^ + end - name: consequent type: node? + comment: | + Represents an `ElseNode` or an `IfNode` when there is an `else` or an `elsif` in the `if` statement. + + if foo + bar + elsif baz + ^^^^^^^^^ + qux + ^^^ + end + ^^^ + + if foo then bar else baz end + ^^^^^^^^^^^^ - name: end_keyword_loc type: location? + comment: | + The location of the `end` keyword if present, `nil` otherwise. + + if foo + bar + end + ^^^ newline: predicate comment: | - Represents the use of the `if` keyword, either in the block form or the modifier form. + Represents the use of the `if` keyword, either in the block form or the modifier form, or a ternary expression. bar if foo ^^^^^^^^^^ if foo then bar end ^^^^^^^^^^^^^^^^^^^ + + foo ? bar : baz + ^^^^^^^^^^^^^^^ - name: ImaginaryNode fields: - name: numeric @@ -1873,12 +2303,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. @@ -1941,6 +2396,9 @@ nodes: ^^^^^^^^^^^^^^^^ - name: InterpolatedStringNode fields: + - name: flags + type: flags + kind: InterpolatedStringNodeFlags - name: opening_loc type: location? - name: parts @@ -2372,13 +2830,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. @@ -2757,6 +3215,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 @@ -2784,8 +3265,12 @@ nodes: ^^^^^^^^^^^^ - name: SourceFileNode fields: + - name: flags + type: flags + kind: StringFlags - name: filepath type: string + comment: Represents the file path being parsed. This corresponds directly to the `filepath` option given to the various `Prism::parse*` APIs. comment: | Represents the use of the `__FILE__` keyword. @@ -2907,18 +3392,56 @@ nodes: fields: - name: keyword_loc type: location + comment: | + The location of the `unless` keyword. + + unless cond then bar end + ^^^^^^ + + bar unless cond + ^^^^^^ - name: predicate type: node + comment: | + The condition to be evaluated for the unless expression. Can be any + kind of node that represents a non-void expression. + + unless cond then bar end + ^^^^ + + bar unless cond + ^^^^ - name: then_keyword_loc type: location? + comment: + The location of the `then` keyword, if present. + + unless cond then bar end + ^^^^ - name: statements type: node? kind: StatementsNode + comment: | + The body of statements that will executed if the unless condition is + falsey. Will be `nil` if no body is provided. + + unless cond then bar end + ^^^ - name: consequent type: node? kind: ElseNode + comment: | + The else clause of the unless expression, if present. + + unless cond then bar else baz end + ^^^^^^^^ - name: end_keyword_loc type: location? + comment: | + The location of the `end` keyword, if present. + + unless cond then bar end + ^^^ newline: predicate comment: | Represents the use of the `unless` keyword, either in the block form or the modifier form. 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/diagnostic.h b/prism/diagnostic.h deleted file mode 100644 index ba78be0f013f2f..00000000000000 --- a/prism/diagnostic.h +++ /dev/null @@ -1,352 +0,0 @@ -/** - * @file diagnostic.h - * - * A list of diagnostics generated during parsing. - */ -#ifndef PRISM_DIAGNOSTIC_H -#define PRISM_DIAGNOSTIC_H - -#include "prism/ast.h" -#include "prism/defines.h" -#include "prism/util/pm_list.h" - -#include -#include -#include - -/** - * 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 an argument error. */ - PM_ERROR_LEVEL_ARGUMENT = 1 -} pm_error_level_t; - -/** - * The levels of warnings generated during parsing. - */ -typedef enum { - /** For warnings which should be emitted if $VERBOSE != nil. */ - PM_WARNING_LEVEL_DEFAULT = 0, - - /** For warnings which should be emitted if $VERBOSE == true. */ - PM_WARNING_LEVEL_VERBOSE = 1 -} pm_warning_level_t; - -/** - * This struct represents a diagnostic generated during parsing. - * - * @extends pm_list_node_t - */ -typedef struct { - /** The embedded base node. */ - pm_list_node_t node; - - /** The location of the diagnostic in the source. */ - pm_location_t location; - - /** The message associated with the diagnostic. */ - const char *message; - - /** - * Whether or not the memory related to the message of this diagnostic is - * owned by this diagnostic. If it is, it needs to be freed when the - * diagnostic is freed. - */ - bool owned; - - /** - * The level of the diagnostic, see `pm_error_level_t` and - * `pm_warning_level_t` for possible values. - */ - uint8_t level; -} pm_diagnostic_t; - -/** - * The diagnostic IDs of all of the diagnostics, used to communicate the types - * of errors between the parser and the user. - */ -typedef enum { - // This is a special error that we can potentially replace by others. For - // an example of how this is used, see parse_expression_prefix. - PM_ERR_CANNOT_PARSE_EXPRESSION, - - // These are the error codes. - PM_ERR_ALIAS_ARGUMENT, - PM_ERR_AMPAMPEQ_MULTI_ASSIGN, - PM_ERR_ARGUMENT_AFTER_BLOCK, - PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES, - PM_ERR_ARGUMENT_BARE_HASH, - PM_ERR_ARGUMENT_BLOCK_FORWARDING, - PM_ERR_ARGUMENT_BLOCK_MULTI, - PM_ERR_ARGUMENT_FORMAL_CLASS, - PM_ERR_ARGUMENT_FORMAL_CONSTANT, - PM_ERR_ARGUMENT_FORMAL_GLOBAL, - PM_ERR_ARGUMENT_FORMAL_IVAR, - PM_ERR_ARGUMENT_FORWARDING_UNBOUND, - PM_ERR_ARGUMENT_IN, - PM_ERR_ARGUMENT_NO_FORWARDING_AMP, - PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES, - PM_ERR_ARGUMENT_NO_FORWARDING_STAR, - PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT, - PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT, - PM_ERR_ARGUMENT_TERM_PAREN, - PM_ERR_ARGUMENT_UNEXPECTED_BLOCK, - PM_ERR_ARRAY_ELEMENT, - PM_ERR_ARRAY_EXPRESSION, - PM_ERR_ARRAY_EXPRESSION_AFTER_STAR, - PM_ERR_ARRAY_SEPARATOR, - PM_ERR_ARRAY_TERM, - PM_ERR_BEGIN_LONELY_ELSE, - PM_ERR_BEGIN_TERM, - PM_ERR_BEGIN_UPCASE_BRACE, - PM_ERR_BEGIN_UPCASE_TERM, - PM_ERR_BEGIN_UPCASE_TOPLEVEL, - PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE, - PM_ERR_BLOCK_PARAM_PIPE_TERM, - PM_ERR_BLOCK_TERM_BRACE, - PM_ERR_BLOCK_TERM_END, - PM_ERR_CANNOT_PARSE_STRING_PART, - PM_ERR_CASE_EXPRESSION_AFTER_CASE, - PM_ERR_CASE_EXPRESSION_AFTER_WHEN, - PM_ERR_CASE_MATCH_MISSING_PREDICATE, - PM_ERR_CASE_MISSING_CONDITIONS, - PM_ERR_CASE_TERM, - PM_ERR_CLASS_IN_METHOD, - PM_ERR_CLASS_NAME, - PM_ERR_CLASS_SUPERCLASS, - PM_ERR_CLASS_TERM, - PM_ERR_CLASS_UNEXPECTED_END, - PM_ERR_CONDITIONAL_ELSIF_PREDICATE, - PM_ERR_CONDITIONAL_IF_PREDICATE, - PM_ERR_CONDITIONAL_PREDICATE_TERM, - PM_ERR_CONDITIONAL_TERM, - PM_ERR_CONDITIONAL_TERM_ELSE, - PM_ERR_CONDITIONAL_UNLESS_PREDICATE, - PM_ERR_CONDITIONAL_UNTIL_PREDICATE, - PM_ERR_CONDITIONAL_WHILE_PREDICATE, - PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT, - PM_ERR_DEF_ENDLESS, - PM_ERR_DEF_ENDLESS_SETTER, - PM_ERR_DEF_NAME, - PM_ERR_DEF_NAME_AFTER_RECEIVER, - PM_ERR_DEF_PARAMS_TERM, - PM_ERR_DEF_PARAMS_TERM_PAREN, - PM_ERR_DEF_RECEIVER, - PM_ERR_DEF_RECEIVER_TERM, - PM_ERR_DEF_TERM, - PM_ERR_DEFINED_EXPRESSION, - PM_ERR_EMBDOC_TERM, - PM_ERR_EMBEXPR_END, - PM_ERR_EMBVAR_INVALID, - PM_ERR_END_UPCASE_BRACE, - PM_ERR_END_UPCASE_TERM, - PM_ERR_ESCAPE_INVALID_CONTROL, - PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT, - PM_ERR_ESCAPE_INVALID_HEXADECIMAL, - PM_ERR_ESCAPE_INVALID_META, - PM_ERR_ESCAPE_INVALID_META_REPEAT, - PM_ERR_ESCAPE_INVALID_UNICODE, - PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS, - PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL, - PM_ERR_ESCAPE_INVALID_UNICODE_LONG, - PM_ERR_ESCAPE_INVALID_UNICODE_TERM, - PM_ERR_EXPECT_ARGUMENT, - PM_ERR_EXPECT_EOL_AFTER_STATEMENT, - PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, - PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, - PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA, - PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL, - PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS, - PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN, - PM_ERR_EXPECT_EXPRESSION_AFTER_QUESTION, - PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, - PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT, - PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH, - PM_ERR_EXPECT_EXPRESSION_AFTER_STAR, - PM_ERR_EXPECT_IDENT_REQ_PARAMETER, - PM_ERR_EXPECT_LPAREN_REQ_PARAMETER, - PM_ERR_EXPECT_RBRACKET, - PM_ERR_EXPECT_RPAREN, - PM_ERR_EXPECT_RPAREN_AFTER_MULTI, - PM_ERR_EXPECT_RPAREN_REQ_PARAMETER, - PM_ERR_EXPECT_STRING_CONTENT, - PM_ERR_EXPECT_WHEN_DELIMITER, - PM_ERR_EXPRESSION_BARE_HASH, - PM_ERR_FLOAT_PARSE, - PM_ERR_FOR_COLLECTION, - PM_ERR_FOR_IN, - PM_ERR_FOR_INDEX, - PM_ERR_FOR_TERM, - PM_ERR_HASH_EXPRESSION_AFTER_LABEL, - PM_ERR_HASH_KEY, - PM_ERR_HASH_ROCKET, - PM_ERR_HASH_TERM, - PM_ERR_HASH_VALUE, - PM_ERR_HEREDOC_TERM, - PM_ERR_INCOMPLETE_QUESTION_MARK, - PM_ERR_INCOMPLETE_VARIABLE_CLASS_3_3_0, - PM_ERR_INCOMPLETE_VARIABLE_CLASS, - PM_ERR_INCOMPLETE_VARIABLE_INSTANCE_3_3_0, - PM_ERR_INCOMPLETE_VARIABLE_INSTANCE, - PM_ERR_INVALID_ENCODING_MAGIC_COMMENT, - PM_ERR_INVALID_FLOAT_EXPONENT, - PM_ERR_INVALID_NUMBER_BINARY, - PM_ERR_INVALID_NUMBER_DECIMAL, - PM_ERR_INVALID_NUMBER_HEXADECIMAL, - PM_ERR_INVALID_NUMBER_OCTAL, - PM_ERR_INVALID_NUMBER_UNDERSCORE, - PM_ERR_INVALID_CHARACTER, - PM_ERR_INVALID_MULTIBYTE_CHARACTER, - PM_ERR_INVALID_PRINTABLE_CHARACTER, - PM_ERR_INVALID_PERCENT, - PM_ERR_INVALID_VARIABLE_GLOBAL_3_3_0, - PM_ERR_INVALID_VARIABLE_GLOBAL, - PM_ERR_IT_NOT_ALLOWED_NUMBERED, - PM_ERR_IT_NOT_ALLOWED_ORDINARY, - PM_ERR_LAMBDA_OPEN, - PM_ERR_LAMBDA_TERM_BRACE, - PM_ERR_LAMBDA_TERM_END, - PM_ERR_LIST_I_LOWER_ELEMENT, - PM_ERR_LIST_I_LOWER_TERM, - PM_ERR_LIST_I_UPPER_ELEMENT, - PM_ERR_LIST_I_UPPER_TERM, - PM_ERR_LIST_W_LOWER_ELEMENT, - PM_ERR_LIST_W_LOWER_TERM, - PM_ERR_LIST_W_UPPER_ELEMENT, - PM_ERR_LIST_W_UPPER_TERM, - PM_ERR_MALLOC_FAILED, - PM_ERR_MIXED_ENCODING, - PM_ERR_MODULE_IN_METHOD, - PM_ERR_MODULE_NAME, - PM_ERR_MODULE_TERM, - PM_ERR_MULTI_ASSIGN_MULTI_SPLATS, - PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST, - PM_ERR_NOT_EXPRESSION, - PM_ERR_NO_LOCAL_VARIABLE, - PM_ERR_NUMBER_LITERAL_UNDERSCORE, - PM_ERR_NUMBERED_PARAMETER_IT, - PM_ERR_NUMBERED_PARAMETER_ORDINARY, - PM_ERR_NUMBERED_PARAMETER_OUTER_SCOPE, - PM_ERR_OPERATOR_MULTI_ASSIGN, - PM_ERR_OPERATOR_WRITE_ARGUMENTS, - PM_ERR_OPERATOR_WRITE_BLOCK, - PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI, - PM_ERR_PARAMETER_BLOCK_MULTI, - PM_ERR_PARAMETER_CIRCULAR, - PM_ERR_PARAMETER_METHOD_NAME, - PM_ERR_PARAMETER_NAME_REPEAT, - PM_ERR_PARAMETER_NO_DEFAULT, - PM_ERR_PARAMETER_NO_DEFAULT_KW, - PM_ERR_PARAMETER_NUMBERED_RESERVED, - PM_ERR_PARAMETER_ORDER, - PM_ERR_PARAMETER_SPLAT_MULTI, - PM_ERR_PARAMETER_STAR, - PM_ERR_PARAMETER_UNEXPECTED_FWD, - PM_ERR_PARAMETER_WILD_LOOSE_COMMA, - PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET, - PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET, - PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA, - PM_ERR_PATTERN_EXPRESSION_AFTER_IN, - PM_ERR_PATTERN_EXPRESSION_AFTER_KEY, - PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN, - PM_ERR_PATTERN_EXPRESSION_AFTER_PIN, - PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE, - PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE, - PM_ERR_PATTERN_EXPRESSION_AFTER_REST, - PM_ERR_PATTERN_HASH_KEY, - PM_ERR_PATTERN_HASH_KEY_LABEL, - PM_ERR_PATTERN_IDENT_AFTER_HROCKET, - PM_ERR_PATTERN_LABEL_AFTER_COMMA, - PM_ERR_PATTERN_REST, - PM_ERR_PATTERN_TERM_BRACE, - PM_ERR_PATTERN_TERM_BRACKET, - PM_ERR_PATTERN_TERM_PAREN, - PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN, - PM_ERR_REGEXP_TERM, - PM_ERR_RESCUE_EXPRESSION, - PM_ERR_RESCUE_MODIFIER_VALUE, - PM_ERR_RESCUE_TERM, - PM_ERR_RESCUE_VARIABLE, - PM_ERR_RETURN_INVALID, - PM_ERR_SINGLETON_FOR_LITERALS, - PM_ERR_STATEMENT_ALIAS, - PM_ERR_STATEMENT_POSTEXE_END, - PM_ERR_STATEMENT_PREEXE_BEGIN, - PM_ERR_STATEMENT_UNDEF, - PM_ERR_STRING_CONCATENATION, - PM_ERR_STRING_INTERPOLATED_TERM, - PM_ERR_STRING_LITERAL_EOF, - PM_ERR_STRING_LITERAL_TERM, - PM_ERR_SYMBOL_INVALID, - PM_ERR_SYMBOL_TERM_DYNAMIC, - PM_ERR_SYMBOL_TERM_INTERPOLATED, - PM_ERR_TERNARY_COLON, - PM_ERR_TERNARY_EXPRESSION_FALSE, - PM_ERR_TERNARY_EXPRESSION_TRUE, - PM_ERR_UNARY_RECEIVER, - PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT, - PM_ERR_UNEXPECTED_TOKEN_IGNORE, - PM_ERR_UNDEF_ARGUMENT, - PM_ERR_UNTIL_TERM, - PM_ERR_VOID_EXPRESSION, - PM_ERR_WHILE_TERM, - PM_ERR_WRITE_TARGET_IN_METHOD, - PM_ERR_WRITE_TARGET_READONLY, - PM_ERR_WRITE_TARGET_UNEXPECTED, - PM_ERR_XSTRING_TERM, - - // These are the warning codes. - PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS, - PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS, - PM_WARN_AMBIGUOUS_PREFIX_STAR, - PM_WARN_AMBIGUOUS_SLASH, - PM_WARN_DOT_DOT_DOT_EOL, - PM_WARN_EQUAL_IN_CONDITIONAL, - PM_WARN_END_IN_METHOD, - PM_WARN_DUPLICATED_HASH_KEY, - PM_WARN_DUPLICATED_WHEN_CLAUSE, - PM_WARN_FLOAT_OUT_OF_RANGE, - PM_WARN_INTEGER_IN_FLIP_FLOP, - PM_WARN_KEYWORD_EOL, - - // This is the number of diagnostic codes. - PM_DIAGNOSTIC_ID_LEN, -} pm_diagnostic_id_t; - -/** - * Append a diagnostic to the given list of diagnostics that is using shared - * memory for its message. - * - * @param list The list to append to. - * @param start The start of the diagnostic. - * @param end The end of the diagnostic. - * @param diag_id The diagnostic ID. - * @return Whether the diagnostic was successfully appended. - */ -bool pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id); - -/** - * Append a diagnostic to the given list of diagnostics that is using a format - * string for its message. - * - * @param list The list to append to. - * @param start The start of the diagnostic. - * @param end The end of the diagnostic. - * @param diag_id The diagnostic ID. - * @param ... The arguments to the format string for the message. - * @return Whether the diagnostic was successfully appended. - */ -bool pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id, ...); - -/** - * Deallocate the internal state of the given diagnostic list. - * - * @param list The list to deallocate. - */ -void pm_diagnostic_list_free(pm_list_t *list); - -#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 d0f947eacdab0b..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; @@ -248,10 +254,22 @@ extern const pm_encoding_t pm_encodings[PM_ENCODING_MAXIMUM]; /** * This is the ASCII-8BIT encoding. We need a reference to it so that pm_strpbrk * can compare against it because invalid multibyte characters are not a thing - * in this encoding. + * in this encoding. It is also needed for handling Regexp encoding flags. */ #define PM_ENCODING_ASCII_8BIT_ENTRY (&pm_encodings[PM_ENCODING_ASCII_8BIT]) +/** + * This is the EUC-JP encoding. We need a reference to it to quickly process + * regular expression modifiers. + */ +#define PM_ENCODING_EUC_JP_ENTRY (&pm_encodings[PM_ENCODING_EUC_JP]) + +/** + * This is the Windows-31J encoding. We need a reference to it to quickly + * process regular expression modifiers. + */ +#define PM_ENCODING_WINDOWS_31J_ENTRY (&pm_encodings[PM_ENCODING_WINDOWS_31J]) + /** * Parse the given name of an encoding and return a pointer to the corresponding * encoding struct if one can be found, otherwise return NULL. diff --git a/prism/extension.c b/prism/extension.c index c14e5165db3193..7b3f89447820c2 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -19,17 +19,19 @@ VALUE rb_cPrismEmbDocComment; VALUE rb_cPrismMagicComment; VALUE rb_cPrismParseError; VALUE rb_cPrismParseWarning; +VALUE rb_cPrismResult; VALUE rb_cPrismParseResult; +VALUE rb_cPrismParseLexResult; 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 +141,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); @@ -254,7 +256,7 @@ file_options(int argc, VALUE *argv, pm_string_t *input, pm_options_t *options) { const char * string_source = (const char *) pm_string_source(&options->filepath); - if (!pm_string_mapped_init(input, string_source)) { + if (!pm_string_file_init(input, string_source)) { pm_options_free(options); #ifdef _WIN32 @@ -267,6 +269,8 @@ file_options(int argc, VALUE *argv, pm_string_t *input, pm_options_t *options) { } } +#ifndef PRISM_EXCLUDE_SERIALIZATION + /******************************************************************************/ /* Serializing the AST */ /******************************************************************************/ @@ -308,7 +312,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 +321,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 +352,8 @@ dump_file(int argc, VALUE *argv, VALUE self) { return value; } +#endif + /******************************************************************************/ /* Extracting values for the parse result */ /******************************************************************************/ @@ -441,23 +447,27 @@ 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); } VALUE error_argv[] = { + ID2SYM(rb_intern(pm_diagnostic_id_human(error->diag_id))), rb_enc_str_new_cstr(error->message, encoding), rb_class_new_instance(3, location_argv, rb_cPrismLocation), level }; - rb_ary_push(errors, rb_class_new_instance(3, error_argv, rb_cPrismParseError)); + rb_ary_push(errors, rb_class_new_instance(4, error_argv, rb_cPrismParseError)); } return errors; @@ -491,17 +501,36 @@ parser_warnings(pm_parser_t *parser, rb_encoding *encoding, VALUE source) { } VALUE warning_argv[] = { + ID2SYM(rb_intern(pm_diagnostic_id_human(warning->diag_id))), rb_enc_str_new_cstr(warning->message, encoding), rb_class_new_instance(3, location_argv, rb_cPrismLocation), level }; - rb_ary_push(warnings, rb_class_new_instance(3, warning_argv, rb_cPrismParseWarning)); + rb_ary_push(warnings, rb_class_new_instance(4, warning_argv, rb_cPrismParseWarning)); } return warnings; } +/** + * Create a new parse result from the given parser, value, encoding, and source. + */ +static VALUE +parse_result_create(VALUE class, pm_parser_t *parser, VALUE value, rb_encoding *encoding, VALUE source) { + VALUE result_argv[] = { + value, + parser_comments(parser, source), + parser_magic_comments(parser, source), + parser_data_loc(parser, source), + parser_errors(parser, encoding, source), + parser_warnings(parser, encoding, source), + source + }; + + return rb_class_new_instance(7, result_argv, class); +} + /******************************************************************************/ /* Lexing Ruby code */ /******************************************************************************/ @@ -608,19 +637,11 @@ parse_lex_input(pm_string_t *input, const pm_options_t *options, bool return_nod value = parse_lex_data.tokens; } - VALUE result_argv[] = { - value, - parser_comments(&parser, source), - parser_magic_comments(&parser, source), - parser_data_loc(&parser, source), - parser_errors(&parser, parse_lex_data.encoding, source), - parser_warnings(&parser, parse_lex_data.encoding, source), - source - }; - + VALUE result = parse_result_create(rb_cPrismParseLexResult, &parser, value, parse_lex_data.encoding, source); pm_node_destroy(&parser, node); pm_parser_free(&parser); - return rb_class_new_instance(7, result_argv, rb_cPrismParseResult); + + return result; } /** @@ -680,17 +701,8 @@ parse_input(pm_string_t *input, const pm_options_t *options) { rb_encoding *encoding = rb_enc_find(parser.encoding->name); VALUE source = pm_source_new(&parser, encoding); - VALUE result_argv[] = { - pm_ast_new(&parser, node, encoding, source), - parser_comments(&parser, source), - parser_magic_comments(&parser, source), - parser_data_loc(&parser, source), - parser_errors(&parser, encoding, source), - parser_warnings(&parser, encoding, source), - source - }; - - VALUE result = rb_class_new_instance(7, result_argv, rb_cPrismParseResult); + VALUE value = pm_ast_new(&parser, node, encoding, source); + VALUE result = parse_result_create(rb_cPrismParseResult, &parser, value, encoding, source) ; pm_node_destroy(&parser, node); pm_parser_free(&parser); @@ -720,7 +732,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"`. @@ -731,7 +743,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); @@ -740,7 +752,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 @@ -749,6 +761,60 @@ parse(int argc, VALUE *argv, VALUE self) { return value; } +/** + * An implementation of fgets that is suitable for use with Ruby IO objects. + */ +static char * +parse_stream_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; +} + +/** + * call-seq: + * Prism::parse_stream(stream, **options) -> ParseResult + * + * Parse the given object that responds to `gets` and return a ParseResult + * instance. The options that are supported are the same as Prism::parse. + */ +static VALUE +parse_stream(int argc, VALUE *argv, VALUE self) { + VALUE stream; + VALUE keywords; + rb_scan_args(argc, argv, "1:", &stream, &keywords); + + pm_options_t options = { 0 }; + extract_options(&options, Qnil, keywords); + + pm_parser_t parser; + pm_buffer_t buffer; + + pm_node_t *node = pm_parse_stream(&parser, &buffer, (void *) stream, parse_stream_fgets, &options); + rb_encoding *encoding = rb_enc_find(parser.encoding->name); + + VALUE source = pm_source_new(&parser, encoding); + VALUE value = pm_ast_new(&parser, node, encoding, source); + VALUE result = parse_result_create(rb_cPrismParseResult, &parser, value, encoding, source); + + pm_node_destroy(&parser, node); + pm_buffer_free(&buffer); + pm_parser_free(&parser); + + return result; +} + /** * call-seq: * Prism::parse_file(filepath, **options) -> ParseResult @@ -905,7 +971,7 @@ parse_input_success_p(pm_string_t *input, const pm_options_t *options) { /** * call-seq: - * Prism::parse_success?(source, **options) -> Array + * Prism::parse_success?(source, **options) -> bool * * Parse the given string and return true if it parses without errors. For * supported options, see Prism::parse. @@ -925,7 +991,19 @@ parse_success_p(int argc, VALUE *argv, VALUE self) { /** * call-seq: - * Prism::parse_file_success?(filepath, **options) -> Array + * Prism::parse_failure?(source, **options) -> bool + * + * Parse the given string and return true if it parses with errors. For + * supported options, see Prism::parse. + */ +static VALUE +parse_failure_p(int argc, VALUE *argv, VALUE self) { + return RTEST(parse_success_p(argc, argv, self)) ? Qfalse : Qtrue; +} + +/** + * call-seq: + * Prism::parse_file_success?(filepath, **options) -> bool * * Parse the given file and return true if it parses without errors. For * supported options, see Prism::parse. @@ -944,6 +1022,18 @@ parse_file_success_p(int argc, VALUE *argv, VALUE self) { return result; } +/** + * call-seq: + * Prism::parse_file_failure?(filepath, **options) -> bool + * + * Parse the given file and return true if it parses with errors. For + * supported options, see Prism::parse. + */ +static VALUE +parse_file_failure_p(int argc, VALUE *argv, VALUE self) { + return RTEST(parse_file_success_p(argc, argv, self)) ? Qfalse : Qtrue; +} + /******************************************************************************/ /* Utility functions exposed to make testing easier */ /******************************************************************************/ @@ -990,26 +1080,16 @@ integer_parse(VALUE self, VALUE source) { pm_integer_t integer = { 0 }; pm_integer_parse(&integer, PM_INTEGER_BASE_UNKNOWN, start, start + length); - VALUE number = UINT2NUM(integer.head.value); - size_t shift = 0; - - for (pm_integer_word_t *node = integer.head.next; node != NULL; node = node->next) { - VALUE receiver = rb_funcall(UINT2NUM(node->value), rb_intern("<<"), 1, ULONG2NUM(++shift * 32)); - number = rb_funcall(receiver, rb_intern("|"), 1, number); - } - - if (integer.negative) number = rb_funcall(number, rb_intern("-@"), 0); - pm_buffer_t buffer = { 0 }; pm_integer_string(&buffer, &integer); VALUE string = rb_str_new(pm_buffer_value(&buffer), pm_buffer_length(&buffer)); pm_buffer_free(&buffer); - pm_integer_free(&integer); VALUE result = rb_ary_new_capa(2); - rb_ary_push(result, number); + rb_ary_push(result, pm_integer_new(&integer)); rb_ary_push(result, string); + pm_integer_free(&integer); return result; } @@ -1079,6 +1159,8 @@ profile_file(VALUE self, VALUE filepath) { return Qnil; } +#ifndef PRISM_EXCLUDE_PRETTYPRINT + /** * call-seq: * Debug::inspect_node(source) -> inspected @@ -1109,6 +1191,8 @@ inspect_node(VALUE self, VALUE source) { return string; } +#endif + /** * call-seq: * Debug::format_errors(source, colorize) -> String @@ -1126,7 +1210,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); @@ -1139,6 +1223,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.newline_list, parser.start_line, parser.encoding->name, 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] * @@ -1246,17 +1364,20 @@ Init_prism(void) { rb_cPrismMagicComment = rb_define_class_under(rb_cPrism, "MagicComment", rb_cObject); rb_cPrismParseError = rb_define_class_under(rb_cPrism, "ParseError", rb_cObject); rb_cPrismParseWarning = rb_define_class_under(rb_cPrism, "ParseWarning", rb_cObject); - rb_cPrismParseResult = rb_define_class_under(rb_cPrism, "ParseResult", rb_cObject); + + rb_cPrismResult = rb_define_class_under(rb_cPrism, "Result", rb_cObject); + rb_cPrismParseResult = rb_define_class_under(rb_cPrism, "ParseResult", rb_cPrismResult); + rb_cPrismParseLexResult = rb_define_class_under(rb_cPrism, "ParseLexResult", rb_cPrismResult); // 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. @@ -1264,18 +1385,24 @@ 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); + rb_define_singleton_method(rb_cPrism, "parse_stream", parse_stream, -1); rb_define_singleton_method(rb_cPrism, "parse_file", parse_file, -1); rb_define_singleton_method(rb_cPrism, "parse_comments", parse_comments, -1); rb_define_singleton_method(rb_cPrism, "parse_file_comments", parse_file_comments, -1); rb_define_singleton_method(rb_cPrism, "parse_lex", parse_lex, -1); rb_define_singleton_method(rb_cPrism, "parse_lex_file", parse_lex_file, -1); rb_define_singleton_method(rb_cPrism, "parse_success?", parse_success_p, -1); + rb_define_singleton_method(rb_cPrism, "parse_failure?", parse_failure_p, -1); rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1); + rb_define_singleton_method(rb_cPrism, "parse_file_failure?", parse_file_failure_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. @@ -1284,8 +1411,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 6e5a3450122a93..3e407d97aec57d 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.26.0" #include #include @@ -10,6 +10,7 @@ VALUE pm_source_new(const pm_parser_t *parser, rb_encoding *encoding); VALUE pm_token_new(const pm_parser_t *parser, const pm_token_t *token, rb_encoding *encoding, VALUE source); VALUE pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encoding, VALUE source); +VALUE pm_integer_new(const pm_integer_t *integer); void Init_prism_api_node(void); void Init_prism_pack(void); 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..4d0d6dbc49a70c 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; } /** @@ -47,29 +47,40 @@ pm_options_command_line_set(pm_options_t *options, uint8_t command_line) { */ PRISM_EXPORTED_FUNCTION bool pm_options_version_set(pm_options_t *options, const char *version, size_t length) { - if (version == NULL && length == 0) { - options->version = PM_OPTIONS_VERSION_LATEST; - return true; - } + switch (length) { + case 0: + if (version == NULL) { + options->version = PM_OPTIONS_VERSION_LATEST; + return true; + } - if (length == 5) { - if (strncmp(version, "3.3.0", length) == 0) { - options->version = PM_OPTIONS_VERSION_CRUBY_3_3_0; - return true; - } + return false; + case 5: + assert(version != NULL); - if (strncmp(version, "3.4.0", length) == 0) { - options->version = PM_OPTIONS_VERSION_LATEST; - return true; - } - } + if (strncmp(version, "3.3.0", length) == 0) { + options->version = PM_OPTIONS_VERSION_CRUBY_3_3_0; + return true; + } - if (length == 6 && strncmp(version, "latest", length) == 0) { - options->version = PM_OPTIONS_VERSION_LATEST; - return true; - } + if (strncmp(version, "3.4.0", length) == 0) { + options->version = PM_OPTIONS_VERSION_LATEST; + return true; + } + + return false; + case 6: + assert(version != NULL); - return false; + if (strncmp(version, "latest", length) == 0) { + options->version = PM_OPTIONS_VERSION_LATEST; + return true; + } + + return false; + default: + return false; + } } // For some reason, GCC analyzer thinks we're leaking allocated scopes and @@ -201,7 +212,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 80521e4ad943af..8054e332f73b30 100644 --- a/prism/parser.h +++ b/prism/parser.h @@ -6,14 +6,14 @@ #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/static_literals.h" #include "prism/util/pm_constant_pool.h" #include "prism/util/pm_list.h" #include "prism/util/pm_newline_list.h" -#include "prism/util/pm_state_stack.h" #include "prism/util/pm_string.h" #include @@ -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 { @@ -234,6 +234,9 @@ typedef struct pm_lex_mode { * a tilde heredoc. */ size_t common_whitespace; + + /** True if the previous token ended with a line continuation. */ + bool line_continuation; } heredoc; } as; @@ -265,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, @@ -280,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, @@ -298,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, @@ -319,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, @@ -337,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, @@ -445,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. @@ -454,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 @@ -484,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. @@ -505,6 +612,11 @@ static const uint8_t PM_SCOPE_PARAMETERS_FORWARDING_ALL = 0x40; static const int8_t PM_SCOPE_NUMBERED_PARAMETERS_DISALLOWED = -1; static const int8_t PM_SCOPE_NUMBERED_PARAMETERS_NONE = 0; +/** + * A struct that represents a stack of boolean values. + */ +typedef uint32_t pm_state_stack_t; + /** * This struct represents the overall parser. It contains a reference to the * source file, as well as pointers that indicate where in the source it's @@ -606,6 +718,15 @@ struct pm_parser { /** The current parsing context. */ pm_context_node_t *current_context; + /** + * The hash keys for the hash that is currently being parsed. This is not + * usually necessary because it can pass it down the various call chains, + * but in the event that you're parsing a hash that is being directly + * pushed into another hash with **, we need to share the hash keys so that + * we can warn for the nested hash as well. + */ + pm_static_literals_t *current_hash_keys; + /** * The encoding functions for the current file is attached to the parser as * it's parsing so that it can change with a magic comment. @@ -697,8 +818,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; @@ -706,6 +837,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; @@ -735,10 +882,10 @@ struct pm_parser { bool semantic_token_seen; /** - * Whether or not we have found a frozen_string_literal magic comment with - * a true value. + * True if the current regular expression being lexed contains only ASCII + * characters. */ - bool frozen_string_literal; + bool current_regular_expression_ascii_only; }; #endif 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 6717488882edec..2e7a80505ba319 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 */ /******************************************************************************/ @@ -743,25 +1171,241 @@ pm_check_value_expression(pm_node_t *node) { } } - return NULL; + return NULL; +} + +static inline void +pm_assert_value_expression(pm_parser_t *parser, pm_node_t *node) { + pm_node_t *void_node = pm_check_value_expression(node); + if (void_node != NULL) { + pm_parser_err_node(parser, void_node, PM_ERR_VOID_EXPRESSION); + } +} + +/** + * Warn if the given node is a "void" statement. + */ +static void +pm_void_statement_check(pm_parser_t *parser, const pm_node_t *node) { + const char *type = NULL; + int length = 0; + + switch (PM_NODE_TYPE(node)) { + case PM_BACK_REFERENCE_READ_NODE: + case PM_CLASS_VARIABLE_READ_NODE: + case PM_GLOBAL_VARIABLE_READ_NODE: + case PM_INSTANCE_VARIABLE_READ_NODE: + case PM_LOCAL_VARIABLE_READ_NODE: + case PM_NUMBERED_REFERENCE_READ_NODE: + type = "a variable"; + length = 10; + break; + case PM_CALL_NODE: { + const pm_call_node_t *cast = (const pm_call_node_t *) node; + if (cast->call_operator_loc.start != NULL || cast->message_loc.start == NULL) break; + + const pm_constant_t *message = pm_constant_pool_id_to_constant(&parser->constant_pool, cast->name); + switch (message->length) { + case 1: + switch (message->start[0]) { + case '+': + case '-': + case '*': + case '/': + case '%': + case '|': + case '^': + case '&': + case '>': + case '<': + type = (const char *) message->start; + length = 1; + break; + } + break; + case 2: + switch (message->start[1]) { + case '=': + if (message->start[0] == '<' || message->start[0] == '>' || message->start[0] == '!' || message->start[0] == '=') { + type = (const char *) message->start; + length = 2; + } + break; + case '@': + if (message->start[0] == '+' || message->start[0] == '-') { + type = (const char *) message->start; + length = 2; + } + break; + case '*': + if (message->start[0] == '*') { + type = (const char *) message->start; + length = 2; + } + break; + } + break; + case 3: + if (memcmp(message->start, "<=>", 3) == 0) { + type = "<=>"; + length = 3; + } + break; + } + + break; + } + case PM_CONSTANT_PATH_NODE: + type = "::"; + length = 2; + break; + case PM_CONSTANT_READ_NODE: + type = "a constant"; + length = 10; + break; + case PM_DEFINED_NODE: + type = "defined?"; + length = 8; + break; + case PM_FALSE_NODE: + type = "false"; + length = 5; + break; + case PM_FLOAT_NODE: + case PM_IMAGINARY_NODE: + case PM_INTEGER_NODE: + case PM_INTERPOLATED_REGULAR_EXPRESSION_NODE: + case PM_INTERPOLATED_STRING_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: + type = "a literal"; + length = 9; + break; + case PM_NIL_NODE: + type = "nil"; + length = 3; + break; + case PM_RANGE_NODE: { + const pm_range_node_t *cast = (const pm_range_node_t *) node; + + if (PM_NODE_FLAG_P(cast, PM_RANGE_FLAGS_EXCLUDE_END)) { + type = "..."; + length = 3; + } else { + type = ".."; + length = 2; + } + + break; + } + case PM_SELF_NODE: + type = "self"; + length = 4; + break; + case PM_TRUE_NODE: + type = "true"; + length = 4; + break; + default: + break; + } + + if (type != NULL) { + PM_PARSER_WARN_NODE_FORMAT(parser, node, PM_WARN_VOID_STATEMENT, length, type); + } +} + +/** + * Warn if any of the statements that are not the last statement in the list are + * a "void" statement. + */ +static void +pm_void_statements_check(pm_parser_t *parser, const pm_statements_node_t *node) { + assert(node->body.size > 0); + for (size_t index = 0; index < node->body.size - 1; index++) { + pm_void_statement_check(parser, node->body.nodes[index]); + } +} + +/** + * 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; + } } -static inline void -pm_assert_value_expression(pm_parser_t *parser, pm_node_t *node) { - pm_node_t *void_node = pm_check_value_expression(node); - if (void_node != NULL) { - pm_parser_err_node(parser, void_node, PM_ERR_VOID_EXPRESSION); +/** + * 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; } } /** - * Check one side of a flip-flop for integer literals. If -e was not supplied at - * the command-line, then warn. + * 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 +1417,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 +1441,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 +1476,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 +1488,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 +1544,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 +1733,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 +1845,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 +1856,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 +1874,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; @@ -1147,7 +1895,7 @@ static pm_statements_node_t * pm_statements_node_create(pm_parser_t *parser); static void -pm_statements_node_body_append(pm_statements_node_t *node, pm_node_t *statement); +pm_statements_node_body_append(pm_parser_t *parser, pm_statements_node_t *node, pm_node_t *statement); static size_t pm_statements_node_body_length(pm_statements_node_t *node); @@ -1402,9 +2150,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 +2528,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 +2575,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 +2626,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 +2653,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 +2766,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)); @@ -2015,6 +2774,7 @@ pm_call_node_not_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t *me if (arguments->closing_loc.start != NULL) { node->base.location.end = arguments->closing_loc.end; } else { + assert(receiver != NULL); node->base.location.end = receiver->location.end; } @@ -2089,30 +2849,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 +3427,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 +3965,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 +4268,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); } @@ -3755,60 +4500,6 @@ pm_hash_node_closing_loc_set(pm_hash_node_t *hash, pm_token_t *token) { 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; - } -} - -/** - * Check whether the predicate contains an assigment where the assigned value is a - * literal. If such an assignment is found, it generates a warning. - */ -static void -pm_predicate_check(pm_parser_t *parser, const pm_node_t *predicate) { - const pm_node_t *value = pm_write_node_value(predicate); - - 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); - } -} - /** * Allocate a new IfNode node. */ @@ -3821,8 +4512,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,12 +4551,11 @@ 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); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); *node = (pm_if_node_t) { { @@ -3894,14 +4583,13 @@ 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); + pm_statements_node_body_append(parser, if_statements, true_expression); pm_statements_node_t *else_statements = pm_statements_node_create(parser); - pm_statements_node_body_append(else_statements, false_expression); + pm_statements_node_body_append(parser, else_statements, false_expression); pm_token_t end_keyword = not_provided(parser); pm_else_node_t *else_node = pm_else_node_create(parser, colon, else_statements, &end_keyword); @@ -4244,6 +4932,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 +4954,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 +5015,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 +5027,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 +5079,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 +5091,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 +5125,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 +5382,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 +5400,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, 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_create(pm_parser_t *parser, const pm_token_t *name, uint32_t depth) { +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); + return pm_local_variable_read_node_create_constant_id(parser, name, name_id, depth, true); } /** @@ -4712,7 +5463,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 +5498,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 +5514,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 +5796,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 +5855,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 +6167,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 +6222,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 +6287,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 +6425,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 +6495,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 @@ -5812,8 +6610,25 @@ pm_statements_node_body_update(pm_statements_node_t *node, pm_node_t *statement) * Append a new node to the given StatementsNode node's body. */ static void -pm_statements_node_body_append(pm_statements_node_t *node, pm_node_t *statement) { +pm_statements_node_body_append(pm_parser_t *parser, pm_statements_node_t *node, pm_node_t *statement) { pm_statements_node_body_update(node, statement); + + if (node->body.size > 0) { + const pm_node_t *previous = node->body.nodes[node->body.size - 1]; + + switch (PM_NODE_TYPE(previous)) { + case PM_BREAK_NODE: + case PM_NEXT_NODE: + case PM_REDO_NODE: + case PM_RETRY_NODE: + case PM_RETURN_NODE: + pm_parser_warn_node(parser, previous, PM_WARN_UNREACHABLE_STATEMENT); + break; + default: + break; + } + } + pm_node_list_append(&node->body, statement); pm_node_flag_set(statement, PM_NODE_FLAG_NEWLINE); } @@ -5836,8 +6651,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) { @@ -5949,6 +6769,129 @@ parse_symbol_encoding(const pm_parser_t *parser, const pm_string_t *contents) { return 0; } +static pm_node_flags_t +parse_and_validate_regular_expression_encoding_modifier(pm_parser_t *parser, const pm_string_t *source, bool ascii_only, pm_node_flags_t flags, char modifier, const pm_encoding_t *modifier_encoding) { + assert ((modifier == 'n' && modifier_encoding == PM_ENCODING_ASCII_8BIT_ENTRY) || + (modifier == 'u' && modifier_encoding == PM_ENCODING_UTF_8_ENTRY) || + (modifier == 'e' && modifier_encoding == PM_ENCODING_EUC_JP_ENTRY) || + (modifier == 's' && modifier_encoding == PM_ENCODING_WINDOWS_31J_ENTRY)); + + // There's special validation logic used if a string does not contain any character escape sequences. + if (parser->explicit_encoding == NULL) { + // If an ASCII-only string without character escapes is used with an encoding modifier, then resulting Regexp + // has the modifier encoding, unless the ASCII-8BIT modifier is used, in which case the Regexp "downgrades" to + // the US-ASCII encoding. + if (ascii_only) { + return modifier == 'n' ? PM_REGULAR_EXPRESSION_FLAGS_FORCED_US_ASCII_ENCODING : flags; + } + + if (parser->encoding == PM_ENCODING_US_ASCII_ENTRY) { + if (!ascii_only) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_INVALID_MULTIBYTE_CHAR, parser->encoding->name); + } + } else if (parser->encoding != modifier_encoding) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_REGEXP_ENCODING_OPTION_MISMATCH, modifier, parser->encoding->name); + + if (modifier == 'n' && !ascii_only) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_REGEXP_NON_ESCAPED_MBC, (int) pm_string_length(source), (const char *) pm_string_source(source)); + } + } + + return flags; + } + + // TODO (nirvdrum 21-Feb-2024): To validate regexp sources with character escape sequences we need to know whether hex or Unicode escape sequences were used and Prism doesn't currently provide that data. We handle a subset of unambiguous cases in the meanwhile. + bool mixed_encoding = false; + + if (mixed_encoding) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_INVALID_MULTIBYTE_ESCAPE, (int) pm_string_length(source), (const char *) pm_string_source(source)); + } else if (modifier != 'n' && parser->explicit_encoding == PM_ENCODING_ASCII_8BIT_ENTRY) { + // TODO (nirvdrum 21-Feb-2024): Validate the content is valid in the modifier encoding. Do this on-demand so we don't pay the cost of computation unnecessarily. + bool valid_string_in_modifier_encoding = true; + + if (!valid_string_in_modifier_encoding) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_INVALID_MULTIBYTE_ESCAPE, (int) pm_string_length(source), (const char *) pm_string_source(source)); + } + } else if (modifier != 'u' && parser->explicit_encoding == PM_ENCODING_UTF_8_ENTRY) { + // TODO (nirvdrum 21-Feb-2024): There's currently no way to tell if the source used hex or Unicode character escapes from `explicit_encoding` alone. If the source encoding was already UTF-8, both character escape types would set `explicit_encoding` to UTF-8, but need to be processed differently. Skip for now. + if (parser->encoding != PM_ENCODING_UTF_8_ENTRY) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_REGEXP_INCOMPAT_CHAR_ENCODING, (int) pm_string_length(source), (const char *) pm_string_source(source)); + } + } + + // We've determined the encoding would naturally be EUC-JP and there is no need to force the encoding to anything else. + return flags; +} + +/** + * Ruby "downgrades" the encoding of Regexps to US-ASCII if the associated encoding is ASCII-compatible and + * the unescaped representation of a Regexp source consists only of US-ASCII code points. This is true even + * when the Regexp is explicitly given an ASCII-8BIT encoding via the (/n) modifier. Otherwise, the encoding + * may be explicitly set with an escape sequence. + */ +static pm_node_flags_t +parse_and_validate_regular_expression_encoding(pm_parser_t *parser, const pm_string_t *source, bool ascii_only, pm_node_flags_t flags) { + // TODO (nirvdrum 22-Feb-2024): CRuby reports a special Regexp-specific error for invalid Unicode ranges. We either need to scan again or modify the "invalid Unicode escape sequence" message we already report. + bool valid_unicode_range = true; + if (parser->explicit_encoding == PM_ENCODING_UTF_8_ENTRY && !valid_unicode_range) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_REGEXP_INVALID_UNICODE_RANGE, (int) pm_string_length(source), (const char *) pm_string_source(source)); + return flags; + } + + // US-ASCII strings do not admit multi-byte character literals. However, character escape sequences corresponding + // to multi-byte characters are allowed. + if (parser->encoding == PM_ENCODING_US_ASCII_ENTRY && parser->explicit_encoding == NULL && !ascii_only) { + // CRuby will continue processing even though a SyntaxError has already been detected. It may result in the + // following error message appearing twice. We do the same for compatibility. + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_INVALID_MULTIBYTE_CHAR, parser->encoding->name); + } + + /** + * Start checking modifier flags. We need to process these before considering any explicit encodings that may have + * been set by character literals. The order in which the encoding modifiers is checked does not matter. In the + * event that both an encoding modifier and an explicit encoding would result in the same encoding we do not set + * the corresponding "forced_" flag. Instead, the caller should check the encoding modifier flag and + * determine the encoding that way. + */ + + if (flags & PM_REGULAR_EXPRESSION_FLAGS_ASCII_8BIT) { + return parse_and_validate_regular_expression_encoding_modifier(parser, source, ascii_only, flags, 'n', PM_ENCODING_ASCII_8BIT_ENTRY); + } + + if (flags & PM_REGULAR_EXPRESSION_FLAGS_UTF_8) { + return parse_and_validate_regular_expression_encoding_modifier(parser, source, ascii_only, flags, 'u', PM_ENCODING_UTF_8_ENTRY); + } + + if (flags & PM_REGULAR_EXPRESSION_FLAGS_EUC_JP) { + return parse_and_validate_regular_expression_encoding_modifier(parser, source, ascii_only, flags, 'e', PM_ENCODING_EUC_JP_ENTRY); + } + + if (flags & PM_REGULAR_EXPRESSION_FLAGS_WINDOWS_31J) { + return parse_and_validate_regular_expression_encoding_modifier(parser, source, ascii_only, flags, 's', PM_ENCODING_WINDOWS_31J_ENTRY); + } + + // At this point no encoding modifiers will be present on the regular expression as they would have already + // been processed. Ruby stipulates that all source files must use an ASCII-compatible encoding. Thus, all + // regular expressions without an encoding modifier appearing in source are eligible for "downgrading" to US-ASCII. + if (ascii_only) { + return PM_REGULAR_EXPRESSION_FLAGS_FORCED_US_ASCII_ENCODING; + } + + // A Regexp may optionally have its encoding explicitly set via a character escape sequence in the source string + // or by specifying a modifier. + // + // NB: an explicitly set encoding is ignored by Ruby if the Regexp consists of only US ASCII code points. + if (parser->explicit_encoding != NULL) { + if (parser->explicit_encoding == PM_ENCODING_UTF_8_ENTRY) { + return PM_REGULAR_EXPRESSION_FLAGS_FORCED_UTF8_ENCODING; + } else if (parser->encoding == PM_ENCODING_US_ASCII_ENTRY) { + return PM_REGULAR_EXPRESSION_FLAGS_FORCED_BINARY_ENCODING; + } + } + + return 0; +} + /** * Allocate and initialize a new SymbolNode node with the given unescaped * string. @@ -6113,8 +7056,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) { @@ -6204,8 +7152,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; @@ -6240,12 +7187,11 @@ 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); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); *node = (pm_unless_node_t) { { @@ -6279,7 +7225,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) { { @@ -6305,7 +7251,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) { { @@ -6385,7 +7331,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) { { @@ -6411,7 +7357,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) { { @@ -6458,160 +7404,71 @@ pm_while_node_synthesized_create(pm_parser_t *parser, pm_node_t *predicate, pm_s */ static pm_x_string_node_t * pm_xstring_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *content, const pm_token_t *closing, const pm_string_t *unescaped) { - pm_x_string_node_t *node = PM_ALLOC_NODE(parser, pm_x_string_node_t); - - *node = (pm_x_string_node_t) { - { - .type = PM_X_STRING_NODE, - .flags = PM_STRING_FLAGS_FROZEN, - .location = { - .start = opening->start, - .end = closing->end - }, - }, - .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), - .content_loc = PM_LOCATION_TOKEN_VALUE(content), - .closing_loc = PM_LOCATION_TOKEN_VALUE(closing), - .unescaped = *unescaped - }; - - return node; -} - -/** - * Allocate and initialize a new XStringNode node. - */ -static inline pm_x_string_node_t * -pm_xstring_node_create(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *content, const pm_token_t *closing) { - return pm_xstring_node_create_unescaped(parser, opening, content, closing, &PM_STRING_EMPTY); -} - -/** - * Allocate a new YieldNode node. - */ -static pm_yield_node_t * -pm_yield_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_location_t *lparen_loc, pm_arguments_node_t *arguments, const pm_location_t *rparen_loc) { - pm_yield_node_t *node = PM_ALLOC_NODE(parser, pm_yield_node_t); - - const uint8_t *end; - if (rparen_loc->start != NULL) { - end = rparen_loc->end; - } else if (arguments != NULL) { - end = arguments->base.location.end; - } else if (lparen_loc->start != NULL) { - end = lparen_loc->end; - } else { - end = keyword->end; - } - - *node = (pm_yield_node_t) { - { - .type = PM_YIELD_NODE, - .location = { - .start = keyword->start, - .end = end - }, - }, - .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), - .lparen_loc = *lparen_loc, - .arguments = arguments, - .rparen_loc = *rparen_loc - }; - - return node; -} - -#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); -} + pm_x_string_node_t *node = PM_ALLOC_NODE(parser, pm_x_string_node_t); -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); -} + *node = (pm_x_string_node_t) { + { + .type = PM_X_STRING_NODE, + .flags = PM_STRING_FLAGS_FROZEN, + .location = { + .start = opening->start, + .end = closing->end + }, + }, + .opening_loc = PM_LOCATION_TOKEN_VALUE(opening), + .content_loc = PM_LOCATION_TOKEN_VALUE(content), + .closing_loc = PM_LOCATION_TOKEN_VALUE(closing), + .unescaped = *unescaped + }; -/** - * 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; + return node; } /** - * Save the current param name as the return value and clear it. + * Allocate and initialize a new XStringNode node. */ -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); +static inline pm_x_string_node_t * +pm_xstring_node_create(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *content, const pm_token_t *closing) { + return pm_xstring_node_create_unescaped(parser, opening, content, closing, &PM_STRING_EMPTY); } /** - * Restore the current param name from the given value. + * Allocate a new YieldNode node. */ -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; +static pm_yield_node_t * +pm_yield_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_location_t *lparen_loc, pm_arguments_node_t *arguments, const pm_location_t *rparen_loc) { + pm_yield_node_t *node = PM_ALLOC_NODE(parser, pm_yield_node_t); + + const uint8_t *end; + if (rparen_loc->start != NULL) { + end = rparen_loc->end; + } else if (arguments != NULL) { + end = arguments->base.location.end; + } else if (lparen_loc->start != NULL) { + end = lparen_loc->end; + } else { + end = keyword->end; + } + + *node = (pm_yield_node_t) { + { + .type = PM_YIELD_NODE, + .location = { + .start = keyword->start, + .end = end + }, + }, + .keyword_loc = PM_LOCATION_TOKEN_VALUE(keyword), + .lparen_loc = *lparen_loc, + .arguments = arguments, + .rparen_loc = *rparen_loc + }; + + return node; } +#undef PM_ALLOC_NODE + /** * Check if any of the currently visible scopes contain a local variable * described by the given constant id. @@ -6622,7 +7479,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; @@ -6646,28 +7503,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); } /** @@ -6676,7 +7531,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; } @@ -6686,7 +7541,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; } @@ -6708,9 +7563,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); } /** @@ -6752,10 +7607,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; } @@ -6763,14 +7618,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); } @@ -6778,6 +7632,30 @@ pm_parser_scope_pop(pm_parser_t *parser) { /* Stack helpers */ /******************************************************************************/ +/** + * Pushes a value onto the stack. + */ +static inline void +pm_state_stack_push(pm_state_stack_t *stack, bool value) { + *stack = (*stack << 1) | (value & 1); +} + +/** + * Pops a value off the stack. + */ +static inline void +pm_state_stack_pop(pm_state_stack_t *stack) { + *stack >>= 1; +} + +/** + * Returns the value at the top of the stack. + */ +static inline bool +pm_state_stack_p(const pm_state_stack_t *stack) { + return *stack & 1; +} + static inline void pm_accepts_block_stack_push(pm_parser_t *parser, bool value) { // Use the negation of the value to prevent stack overflow. @@ -6847,7 +7725,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) { @@ -6990,9 +7868,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; } } @@ -7134,19 +8012,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; @@ -7167,6 +8069,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; @@ -7184,8 +8089,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; @@ -7205,11 +8115,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; @@ -7282,13 +8202,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; @@ -7317,11 +8246,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"; @@ -7333,11 +8275,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"; @@ -7402,26 +8349,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; @@ -7572,8 +8526,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; } @@ -7643,11 +8596,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; @@ -7864,7 +8822,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]) { @@ -7896,7 +8854,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; @@ -8130,34 +9088,55 @@ escape_write_escape_encoded(pm_parser_t *parser, pm_buffer_t *buffer) { * source so that the regular expression engine will perform its own unescaping. */ static inline void -escape_write_byte(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags, uint8_t byte) { +escape_write_byte(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expression_buffer, uint8_t flags, uint8_t byte) { if (flags & PM_ESCAPE_FLAG_REGEXP) { - pm_buffer_append_bytes(buffer, (const uint8_t *) "\\x", 2); + pm_buffer_append_bytes(regular_expression_buffer, (const uint8_t *) "\\x", 2); uint8_t byte1 = (uint8_t) ((byte >> 4) & 0xF); uint8_t byte2 = (uint8_t) (byte & 0xF); if (byte1 >= 0xA) { - pm_buffer_append_byte(buffer, (uint8_t) ((byte1 - 0xA) + 'A')); + pm_buffer_append_byte(regular_expression_buffer, (uint8_t) ((byte1 - 0xA) + 'A')); } else { - pm_buffer_append_byte(buffer, (uint8_t) (byte1 + '0')); + pm_buffer_append_byte(regular_expression_buffer, (uint8_t) (byte1 + '0')); } if (byte2 >= 0xA) { - pm_buffer_append_byte(buffer, (uint8_t) (byte2 - 0xA + 'A')); + pm_buffer_append_byte(regular_expression_buffer, (uint8_t) (byte2 - 0xA + 'A')); } else { - pm_buffer_append_byte(buffer, (uint8_t) (byte2 + '0')); + pm_buffer_append_byte(regular_expression_buffer, (uint8_t) (byte2 + '0')); } - } else { - escape_write_byte_encoded(parser, buffer, byte); } + + 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. */ static void -escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { +escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expression_buffer, uint8_t flags) { switch (peek(parser)) { case '\\': { parser->current.end++; @@ -8248,10 +9227,10 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { } if (flags & PM_ESCAPE_FLAG_REGEXP) { - pm_buffer_append_bytes(buffer, start, (size_t) (parser->current.end - start)); - } else { - escape_write_byte_encoded(parser, buffer, value); + pm_buffer_append_bytes(regular_expression_buffer, start, (size_t) (parser->current.end - start)); } + + escape_write_byte_encoded(parser, buffer, value); } else { pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_HEXADECIMAL); } @@ -8272,10 +9251,9 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { uint32_t value = escape_unicode(parser->current.end, 4); if (flags & PM_ESCAPE_FLAG_REGEXP) { - pm_buffer_append_bytes(buffer, start, (size_t) (parser->current.end + 4 - start)); - } else { - escape_write_unicode(parser, buffer, flags, start, parser->current.end + 4, value); + pm_buffer_append_bytes(regular_expression_buffer, start, (size_t) (parser->current.end + 4 - start)); } + escape_write_unicode(parser, buffer, flags, start, parser->current.end + 4, value); parser->current.end += 4; } else if (peek(parser) == '{') { @@ -8306,10 +9284,8 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { extra_codepoints_start = unicode_start; } - if (!(flags & PM_ESCAPE_FLAG_REGEXP)) { - uint32_t value = escape_unicode(unicode_start, hexadecimal_length); - escape_write_unicode(parser, buffer, flags, unicode_start, parser->current.end, value); - } + uint32_t value = escape_unicode(unicode_start, hexadecimal_length); + escape_write_unicode(parser, buffer, flags, unicode_start, parser->current.end, value); parser->current.end += pm_strspn_whitespace(parser->current.end, parser->end - parser->current.end); } @@ -8327,7 +9303,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { } if (flags & PM_ESCAPE_FLAG_REGEXP) { - pm_buffer_append_bytes(buffer, unicode_codepoints_start, (size_t) (parser->current.end - unicode_codepoints_start)); + pm_buffer_append_bytes(regular_expression_buffer, unicode_codepoints_start, (size_t) (parser->current.end - unicode_codepoints_start)); } } else { pm_parser_err_current(parser, PM_ERR_ESCAPE_INVALID_UNICODE); @@ -8346,7 +9322,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { switch (peeked) { case '?': { parser->current.end++; - escape_write_byte(parser, buffer, flags, escape_byte(0x7f, flags)); + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(0x7f, flags)); return; } case '\\': @@ -8355,7 +9331,17 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { return; } parser->current.end++; - escape_read(parser, buffer, flags | PM_ESCAPE_FLAG_CONTROL); + 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)) { @@ -8364,7 +9350,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { } parser->current.end++; - escape_write_byte(parser, buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL)); + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL)); return; } } @@ -8386,7 +9372,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { switch (peeked) { case '?': { parser->current.end++; - escape_write_byte(parser, buffer, flags, escape_byte(0x7f, flags)); + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(0x7f, flags)); return; } case '\\': @@ -8395,7 +9381,17 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { return; } parser->current.end++; - escape_read(parser, buffer, flags | PM_ESCAPE_FLAG_CONTROL); + 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)) { @@ -8404,7 +9400,7 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { } parser->current.end++; - escape_write_byte(parser, buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL)); + escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_CONTROL)); return; } } @@ -8423,24 +9419,35 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, uint8_t flags) { } 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, 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, flags, escape_byte(peeked, flags | PM_ESCAPE_FLAG_META)); - return; } case '\r': { if (peek_offset(parser, 1) == '\n') { @@ -8510,7 +9517,7 @@ lex_question_mark(pm_parser_t *parser) { pm_buffer_t buffer; pm_buffer_init_capacity(&buffer, 3); - escape_read(parser, &buffer, PM_ESCAPE_FLAG_SINGLE); + escape_read(parser, &buffer, NULL, PM_ESCAPE_FLAG_SINGLE); pm_string_owned_init(&parser->current_string, (uint8_t *) buffer.value, buffer.length); return PM_TOKEN_CHARACTER_LITERAL; @@ -8551,7 +9558,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; @@ -8559,6 +9566,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 @@ -8585,7 +9595,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) { @@ -8620,15 +9630,23 @@ lex_embdoc(pm_parser_t *parser) { pm_comment_t *comment = parser_comment(parser, PM_COMMENT_EMBDOC); if (comment == NULL) return PM_TOKEN_EOF; - // Now, loop until we find the end of the embedded documentation or the end of - // the file. + // Now, loop until we find the end of the embedded documentation or the end + // of the file. while (parser->current.end + 4 <= parser->end) { parser->current.start = parser->current.end; - // If we've hit the end of the embedded documentation then we'll return that - // token here. - if (memcmp(parser->current.end, "=end", 4) == 0 && - (parser->current.end + 4 == parser->end || pm_char_is_whitespace(parser->current.end[4]))) { + // If we've hit the end of the embedded documentation then we'll return + // that token here. + if ( + (memcmp(parser->current.end, "=end", 4) == 0) && + ( + (parser->current.end + 4 == parser->end) || // end of file + pm_char_is_whitespace(parser->current.end[4]) || // whitespace + (parser->current.end[4] == '\0') || // NUL or end of script + (parser->current.end[4] == '\004') || // ^D + (parser->current.end[4] == '\032') // ^Z + ) + ) { const uint8_t *newline = next_newline(parser->current.end, parser->end - parser->current.end); if (newline == NULL) { @@ -8724,7 +9742,7 @@ parser_end_of_line_p(const pm_parser_t *parser) { * "foo\n" * * then the bytes in the string are "f", "o", "o", "\", "n", but we want to - * provide out consumers with the string content "f", "o", "o", "\n". In these + * provide our consumers with the string content "f", "o", "o", "\n". In these * cases, when we find the first escape sequence, we initialize a pm_buffer_t * to keep track of the string content. Then in the parser, it will * automatically attach the string content to the node that it belongs to. @@ -8743,6 +9761,26 @@ typedef struct { const uint8_t *cursor; } pm_token_buffer_t; +/** + * In order to properly set a regular expression's encoding and to validate + * the byte sequence for the underlying encoding we must process any escape + * sequences. The unescaped byte sequence will be stored in `buffer` just like + * for other string-like types. However, we also need to store the regular + * expression's source string. That string may be different from what we see + * during lexing because some escape sequences rewrite the source. + * + * This value will only be initialized for regular expressions and only if we + * receive an escape sequence. It will contain the regular expression's source + * string's byte sequence. + */ +typedef struct { + /** The embedded base buffer. */ + pm_token_buffer_t base; + + /** The buffer holding the regexp source. */ + pm_buffer_t regexp_buffer; +} pm_regexp_token_buffer_t; + /** * Push the given byte into the token buffer. */ @@ -8751,20 +9789,16 @@ pm_token_buffer_push_byte(pm_token_buffer_t *token_buffer, uint8_t byte) { pm_buffer_append_byte(&token_buffer->buffer, byte); } -/** - * Append the given bytes into the token buffer. - */ static inline void -pm_token_buffer_push_bytes(pm_token_buffer_t *token_buffer, const uint8_t *bytes, size_t length) { - pm_buffer_append_bytes(&token_buffer->buffer, bytes, length); +pm_regexp_token_buffer_push_byte(pm_regexp_token_buffer_t *token_buffer, uint8_t byte) { + pm_buffer_append_byte(&token_buffer->regexp_buffer, byte); } /** - * Push an escaped character into the token buffer. + * Return the width of the character at the end of the current token. */ -static inline void -pm_token_buffer_push_escaped(pm_token_buffer_t *token_buffer, pm_parser_t *parser) { - // First, determine the width of the character to be escaped. +static inline size_t +parser_char_width(const pm_parser_t *parser) { size_t width; if (parser->encoding_changed) { width = parser->encoding->char_width(parser->current.end, parser->end - parser->current.end); @@ -8774,13 +9808,36 @@ pm_token_buffer_push_escaped(pm_token_buffer_t *token_buffer, pm_parser_t *parse // TODO: If the character is invalid in the given encoding, then we'll just // push one byte into the buffer. This should actually be an error. - width = (width == 0 ? 1 : width); + return (width == 0 ? 1 : width); +} + +/** + * Push an escaped character into the token buffer. + */ +static void +pm_token_buffer_push_escaped(pm_token_buffer_t *token_buffer, pm_parser_t *parser) { + size_t width = parser_char_width(parser); + pm_buffer_append_bytes(&token_buffer->buffer, parser->current.end, width); + parser->current.end += width; +} - // Now, push the bytes into the buffer. - pm_token_buffer_push_bytes(token_buffer, parser->current.end, width); +static void +pm_regexp_token_buffer_push_escaped(pm_regexp_token_buffer_t *token_buffer, pm_parser_t *parser) { + size_t width = parser_char_width(parser); + pm_buffer_append_bytes(&token_buffer->base.buffer, parser->current.end, width); + pm_buffer_append_bytes(&token_buffer->regexp_buffer, parser->current.end, width); parser->current.end += width; } +static bool +pm_slice_ascii_only_p(const uint8_t *value, size_t length) { + for (size_t index = 0; index < length; index++) { + if (value[index] & 0x80) return false; + } + + return true; +} + /** * When we're about to return from lexing the current token and we know for sure * that we have found an escape sequence, this function is called to copy the @@ -8789,7 +9846,14 @@ pm_token_buffer_push_escaped(pm_token_buffer_t *token_buffer, pm_parser_t *parse */ static inline void pm_token_buffer_copy(pm_parser_t *parser, pm_token_buffer_t *token_buffer) { - pm_string_owned_init(&parser->current_string, (uint8_t *) token_buffer->buffer.value, token_buffer->buffer.length); + pm_string_owned_init(&parser->current_string, (uint8_t *) pm_buffer_value(&token_buffer->buffer), pm_buffer_length(&token_buffer->buffer)); +} + +static inline void +pm_regexp_token_buffer_copy(pm_parser_t *parser, pm_regexp_token_buffer_t *token_buffer) { + pm_string_owned_init(&parser->current_string, (uint8_t *) pm_buffer_value(&token_buffer->base.buffer), pm_buffer_length(&token_buffer->base.buffer)); + parser->current_regular_expression_ascii_only = pm_slice_ascii_only_p((const uint8_t *) pm_buffer_value(&token_buffer->regexp_buffer), pm_buffer_length(&token_buffer->regexp_buffer)); + pm_buffer_free(&token_buffer->regexp_buffer); } /** @@ -8811,6 +9875,20 @@ pm_token_buffer_flush(pm_parser_t *parser, pm_token_buffer_t *token_buffer) { } } +static void +pm_regexp_token_buffer_flush(pm_parser_t *parser, pm_regexp_token_buffer_t *token_buffer) { + if (token_buffer->base.cursor == NULL) { + pm_string_shared_init(&parser->current_string, parser->current.start, parser->current.end); + parser->current_regular_expression_ascii_only = pm_slice_ascii_only_p(parser->current.start, (size_t) (parser->current.end - parser->current.start)); + } else { + pm_buffer_append_bytes(&token_buffer->base.buffer, token_buffer->base.cursor, (size_t) (parser->current.end - token_buffer->base.cursor)); + pm_buffer_append_bytes(&token_buffer->regexp_buffer, token_buffer->base.cursor, (size_t) (parser->current.end - token_buffer->base.cursor)); + pm_regexp_token_buffer_copy(parser, token_buffer); + } +} + +#define PM_TOKEN_BUFFER_DEFAULT_SIZE 16 + /** * When we've found an escape sequence, we need to copy everything up to this * point into the buffer because we're about to provide a string that has @@ -8823,7 +9901,7 @@ static void pm_token_buffer_escape(pm_parser_t *parser, pm_token_buffer_t *token_buffer) { const uint8_t *start; if (token_buffer->cursor == NULL) { - pm_buffer_init_capacity(&token_buffer->buffer, 16); + pm_buffer_init_capacity(&token_buffer->buffer, PM_TOKEN_BUFFER_DEFAULT_SIZE); start = parser->current.start; } else { start = token_buffer->cursor; @@ -8831,8 +9909,30 @@ pm_token_buffer_escape(pm_parser_t *parser, pm_token_buffer_t *token_buffer) { const uint8_t *end = parser->current.end - 1; pm_buffer_append_bytes(&token_buffer->buffer, start, (size_t) (end - start)); + + token_buffer->cursor = end; +} + +static void +pm_regexp_token_buffer_escape(pm_parser_t *parser, pm_regexp_token_buffer_t *token_buffer) { + const uint8_t *start; + if (token_buffer->base.cursor == NULL) { + pm_buffer_init_capacity(&token_buffer->base.buffer, PM_TOKEN_BUFFER_DEFAULT_SIZE); + pm_buffer_init_capacity(&token_buffer->regexp_buffer, PM_TOKEN_BUFFER_DEFAULT_SIZE); + start = parser->current.start; + } else { + start = token_buffer->base.cursor; + } + + const uint8_t *end = parser->current.end - 1; + pm_buffer_append_bytes(&token_buffer->base.buffer, start, (size_t) (end - start)); + pm_buffer_append_bytes(&token_buffer->regexp_buffer, start, (size_t) (end - start)); + + token_buffer->base.cursor = end; } +#undef PM_TOKEN_BUFFER_DEFAULT_SIZE + /** * Effectively the same thing as pm_strspn_inline_whitespace, but in the case of * a tilde heredoc expands out tab characters to the nearest tab boundaries. @@ -8964,6 +10064,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; } @@ -9137,7 +10238,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); @@ -9295,7 +10396,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; } @@ -9354,9 +10458,13 @@ parser_lex(pm_parser_t *parser) { // = => =~ == === =begin case '=': - if (current_token_starts_line(parser) && (parser->current.end + 5 <= parser->end) && memcmp(parser->current.end, "begin", 5) == 0 && pm_char_is_whitespace(peek_offset(parser, 5))) { + if ( + current_token_starts_line(parser) && + (parser->current.end + 5 <= parser->end) && + memcmp(parser->current.end, "begin", 5) == 0 && + (pm_char_is_whitespace(peek_offset(parser, 5)) || (peek_offset(parser, 5) == '\0')) + ) { pm_token_type_t type = lex_embdoc(parser); - if (type == PM_TOKEN_EOF) { LEX(type); } @@ -9450,7 +10558,8 @@ parser_lex(pm_parser_t *parser) { .next_start = parser->current.end, .quote = quote, .indent = indent, - .common_whitespace = (size_t) -1 + .common_whitespace = (size_t) -1, + .line_continuation = false } }); @@ -9593,7 +10702,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; } @@ -9980,7 +11092,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); @@ -10020,16 +11132,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; } @@ -10038,19 +11177,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++); @@ -10140,15 +11279,9 @@ parser_lex(pm_parser_t *parser) { // If we haven't found an escape yet, then this buffer will be // unallocated since we can refer directly to the source string. - pm_token_buffer_t token_buffer = { { 0 }, 0 }; + 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)) { @@ -10185,6 +11318,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. @@ -10239,7 +11378,7 @@ parser_lex(pm_parser_t *parser) { pm_token_buffer_push_byte(&token_buffer, peeked); parser->current.end++; } else if (lex_mode->as.list.interpolation) { - escape_read(parser, &token_buffer.buffer, PM_ESCAPE_FLAG_NONE); + escape_read(parser, &token_buffer.buffer, NULL, PM_ESCAPE_FLAG_NONE); } else { pm_token_buffer_push_byte(&token_buffer, '\\'); pm_token_buffer_push_escaped(&token_buffer, parser); @@ -10303,8 +11442,8 @@ parser_lex(pm_parser_t *parser) { parser->next_start = NULL; } - // We'll check if we're at the end of the file. If we are, then we need to - // return the EOF token. + // We'll check if we're at the end of the file. If we are, then we + // need to return the EOF token. if (parser->current.end >= parser->end) { LEX(PM_TOKEN_EOF); } @@ -10317,39 +11456,9 @@ parser_lex(pm_parser_t *parser) { // characters. const uint8_t *breakpoints = lex_mode->as.regexp.breakpoints; const uint8_t *breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - pm_token_buffer_t token_buffer = { { 0 }, 0 }; + 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) { @@ -10365,13 +11474,21 @@ parser_lex(pm_parser_t *parser) { // first. if (breakpoint > parser->current.start) { parser->current.end = breakpoint; - pm_token_buffer_flush(parser, &token_buffer); + pm_regexp_token_buffer_flush(parser, &token_buffer); 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); @@ -10379,123 +11496,163 @@ 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_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, '\\'); - } - pm_token_buffer_push_byte(&token_buffer, '\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_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.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_token_buffer_push_byte(&token_buffer, '\\'); - break; - default: - break; + } + + 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, '\\'); + } + 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_token_buffer_push_byte(&token_buffer, peeked); parser->current.end++; break; - } - - if (peeked < 0x80) pm_token_buffer_push_byte(&token_buffer, '\\'); - pm_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.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_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) { - pm_token_buffer_flush(parser, &token_buffer); + pm_regexp_token_buffer_flush(parser, &token_buffer); LEX(PM_TOKEN_STRING_CONTENT); } // If we were unable to find a breakpoint, then this token hits the // end of the file. parser->current.end = parser->end; - pm_token_buffer_flush(parser, &token_buffer); + pm_regexp_token_buffer_flush(parser, &token_buffer); LEX(PM_TOKEN_STRING_CONTENT); } case PM_LEX_STRING: { @@ -10522,7 +11679,7 @@ parser_lex(pm_parser_t *parser) { // If we haven't found an escape yet, then this buffer will be // unallocated since we can refer directly to the source string. - pm_token_buffer_t token_buffer = { { 0 }, 0 }; + pm_token_buffer_t token_buffer = { 0 }; while (breakpoint != NULL) { // If we hit the incrementor, then we'll increment then nesting and @@ -10577,29 +11734,43 @@ parser_lex(pm_parser_t *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); - } - } - switch (*breakpoint) { case '\0': // Skip directly past the null character. 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; @@ -10657,7 +11828,7 @@ parser_lex(pm_parser_t *parser) { pm_token_buffer_push_byte(&token_buffer, peeked); parser->current.end++; } else if (lex_mode->as.string.interpolation) { - escape_read(parser, &token_buffer.buffer, PM_ESCAPE_FLAG_NONE); + escape_read(parser, &token_buffer.buffer, NULL, PM_ESCAPE_FLAG_NONE); } else { pm_token_buffer_push_byte(&token_buffer, '\\'); pm_token_buffer_push_escaped(&token_buffer, parser); @@ -10719,6 +11890,9 @@ parser_lex(pm_parser_t *parser) { // current lex mode. pm_lex_mode_t *lex_mode = parser->lex_modes.current; + bool line_continuation = lex_mode->as.heredoc.line_continuation; + lex_mode->as.heredoc.line_continuation = false; + // We'll check if we're at the end of the file. If we are, then we // will add an error (because we weren't able to find the // terminator) but still continue parsing so that content after the @@ -10736,7 +11910,7 @@ parser_lex(pm_parser_t *parser) { // If we are immediately following a newline and we have hit the // terminator, then we need to return the ending of the heredoc. - if (current_token_starts_line(parser)) { + if (!line_continuation && current_token_starts_line(parser)) { const uint8_t *start = parser->current.start; if (start + ident_length <= parser->end) { const uint8_t *newline = next_newline(start, parser->end - start); @@ -10799,16 +11973,16 @@ 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); - pm_token_buffer_t token_buffer = { { 0 }, 0 }; - bool was_escaped_newline = false; + pm_token_buffer_t token_buffer = { 0 }; + bool was_line_continuation = false; while (breakpoint != NULL) { switch (*breakpoint) { @@ -10817,6 +11991,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); @@ -10831,7 +12020,7 @@ parser_lex(pm_parser_t *parser) { // some leading whitespace. const uint8_t *start = breakpoint + 1; - if (!was_escaped_newline && (start + ident_length <= parser->end)) { + if (!was_line_continuation && (start + ident_length <= parser->end)) { // We want to match the terminator starting from the end of the line in case // there is whitespace in the ident such as <<-' DOC' or <<~' DOC'. const uint8_t *newline = next_newline(start, parser->end - start); @@ -10873,7 +12062,6 @@ parser_lex(pm_parser_t *parser) { // heredoc here as string content. Then, the next time a // token is lexed, it will match again and return the // end of the heredoc. - if (lex_mode->as.heredoc.indent == PM_HEREDOC_INDENT_TILDE) { if ((lex_mode->as.heredoc.common_whitespace > whitespace) && peek_at(parser, start) != '\n') { lex_mode->as.heredoc.common_whitespace = whitespace; @@ -10881,7 +12069,7 @@ parser_lex(pm_parser_t *parser) { parser->current.end = breakpoint + 1; - if (!was_escaped_newline) { + if (!was_line_continuation) { pm_token_buffer_flush(parser, &token_buffer); LEX(PM_TOKEN_STRING_CONTENT); } @@ -10896,7 +12084,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. @@ -10943,12 +12131,31 @@ parser_lex(pm_parser_t *parser) { } /* fallthrough */ case '\n': - was_escaped_newline = true; + // If we are in a tilde here, we should + // break out of the loop and return the + // string content. + if (lex_mode->as.heredoc.indent == PM_HEREDOC_INDENT_TILDE) { + const uint8_t *end = parser->current.end; + pm_newline_list_append(&parser->newline_list, end); + + // Here we want the buffer to only + // include up to the backslash. + parser->current.end = breakpoint; + pm_token_buffer_flush(parser, &token_buffer); + + // Now we can advance the end of the + // token past the newline. + parser->current.end = end + 1; + lex_mode->as.heredoc.line_continuation = true; + LEX(PM_TOKEN_STRING_CONTENT); + } + + was_line_continuation = true; token_buffer.cursor = parser->current.end + 1; breakpoint = parser->current.end; continue; default: - escape_read(parser, &token_buffer.buffer, PM_ESCAPE_FLAG_NONE); + escape_read(parser, &token_buffer.buffer, NULL, PM_ESCAPE_FLAG_NONE); break; } } @@ -10980,7 +12187,7 @@ parser_lex(pm_parser_t *parser) { assert(false && "unreachable"); } - was_escaped_newline = false; + was_line_continuation = false; } if (parser->current.end > parser->current.start) { @@ -11089,7 +12296,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, @@ -11334,7 +12541,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) { @@ -11358,7 +12566,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. */ @@ -11391,7 +12598,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); @@ -11485,13 +12692,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; @@ -11511,7 +12724,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] != '!') && @@ -11530,20 +12744,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)) { @@ -11555,7 +12761,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); } } @@ -11589,6 +12795,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. */ @@ -11603,15 +12824,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: @@ -11626,13 +12849,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); @@ -11679,7 +12903,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); @@ -11715,7 +12939,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); } @@ -11746,6 +12970,32 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod } } +/** + * Certain expressions are not writable, but in order to provide a better + * experience we give a specific error message. In order to maintain as much + * information in the tree as possible, we replace them with local variable + * writes. + */ +static pm_node_t * +parse_unwriteable_write(pm_parser_t *parser, pm_node_t *target, const pm_token_t *equals, pm_node_t *value) { + switch (PM_NODE_TYPE(target)) { + case PM_SOURCE_ENCODING_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_ENCODING); break; + case PM_FALSE_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_FALSE); break; + case PM_SOURCE_FILE_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_FILE); break; + case PM_SOURCE_LINE_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_LINE); break; + case PM_NIL_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_NIL); break; + case PM_SELF_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_SELF); break; + case PM_TRUE_NODE: pm_parser_err_token(parser, equals, PM_ERR_EXPRESSION_NOT_WRITABLE_TRUE); break; + default: break; + } + + pm_constant_id_t name = pm_parser_constant_id_location(parser, target->location.start, target->location.end); + pm_local_variable_write_node_t *result = pm_local_variable_write_node_create(parser, name, 0, value, &target->location, equals); + + pm_node_destroy(parser, target); + return (pm_node_t *) result; +} + /** * Parse a list of targets for assignment. This is used in the case of a for * loop or a multi-assignment. For example, in the following code: @@ -11837,7 +13087,7 @@ parse_statements(pm_parser_t *parser, pm_context_t context) { while (true) { pm_node_t *node = parse_expression(parser, PM_BINDING_POWER_STATEMENT, true, PM_ERR_CANNOT_PARSE_EXPRESSION); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); // If we're recovering from a syntax error, then we need to stop parsing the // statements now. @@ -11891,6 +13141,8 @@ parse_statements(pm_parser_t *parser, pm_context_t context) { } context_pop(parser); + pm_void_statements_check(parser, statements); + return statements; } @@ -11900,18 +13152,23 @@ parse_statements(pm_parser_t *parser, pm_context_t context) { */ static void pm_hash_key_static_literals_add(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node) { - const pm_node_t *duplicated = pm_static_literals_add(parser, literals, node); + const pm_node_t *duplicated = pm_static_literals_add(&parser->newline_list, parser->start_line, literals, node); if (duplicated != NULL) { + pm_buffer_t buffer = { 0 }; + pm_static_literal_inspect(&buffer, &parser->newline_list, parser->start_line, parser->encoding->name, 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); } } @@ -11921,7 +13178,7 @@ pm_hash_key_static_literals_add(pm_parser_t *parser, pm_static_literals_t *liter */ static void pm_when_clause_static_literals_add(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node) { - if (pm_static_literals_add(parser, literals, node) != NULL) { + if (pm_static_literals_add(&parser->newline_list, parser->start_line, literals, node) != NULL) { pm_diagnostic_list_append_format( &parser->warning_list, node->location.start, @@ -11933,7 +13190,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) { @@ -11949,10 +13206,16 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod pm_token_t operator = parser->previous; pm_node_t *value = NULL; - if (token_begins_expression_p(parser->current.type)) { + if (match1(parser, PM_TOKEN_BRACE_LEFT)) { + // If we're about to parse a nested hash that is being + // pushed into this hash directly with **, then we want the + // inner hash to share the static literals with the outer + // hash. + parser->current_hash_keys = literals; value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH); - } - else { + } else if (token_begins_expression_p(parser->current.type)) { + value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH); + } else { pm_parser_scope_forwarding_keywords_check(parser, &operator); } @@ -11977,9 +13240,15 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod pm_token_t constant = { .type = PM_TOKEN_CONSTANT, .start = label.start, .end = label.end - 1 }; value = (pm_node_t *) pm_constant_read_node_create(parser, &constant); } else { - int depth = pm_parser_local_depth(parser, &((pm_token_t) { .type = PM_TOKEN_IDENTIFIER, .start = label.start, .end = label.end - 1 })); + int depth = -1; pm_token_t identifier = { .type = PM_TOKEN_IDENTIFIER, .start = label.start, .end = label.end - 1 }; + if (identifier.end[-1] == '!' || identifier.end[-1] == '?') { + PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, identifier, PM_ERR_INVALID_LOCAL_VARIABLE_READ); + } else { + depth = pm_parser_local_depth(parser, &identifier); + } + if (depth == -1) { value = (pm_node_t *) pm_call_node_variable_call_create(parser, &identifier); } else { @@ -12097,15 +13366,15 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for pm_keyword_hash_node_t *hash = pm_keyword_hash_node_create(parser); argument = (pm_node_t *) hash; - pm_static_literals_t literals = { 0 }; - bool contains_keyword_splat = parse_assocs(parser, &literals, (pm_node_t *) hash); + pm_static_literals_t hash_keys = { 0 }; + bool contains_keyword_splat = parse_assocs(parser, &hash_keys, (pm_node_t *) hash); parse_arguments_append(parser, arguments, argument); if (contains_keyword_splat) { - pm_node_flag_set((pm_node_t *)arguments->arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORD_SPLAT); + pm_node_flag_set((pm_node_t *) arguments->arguments, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORD_SPLAT); } - pm_static_literals_free(&literals); + pm_static_literals_free(&hash_keys); parsed_bare_hash = true; break; @@ -12118,7 +13387,6 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for if (token_begins_expression_p(parser->current.type)) { expression = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_EXPECT_ARGUMENT); } else { - // A block forwarding in a method having `...` parameter (e.g. `def foo(...); bar(&); end`) is available. pm_parser_scope_forwarding_block_check(parser, &operator); } @@ -12198,10 +13466,10 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for pm_keyword_hash_node_t *bare_hash = pm_keyword_hash_node_create(parser); // Create the set of static literals for this hash. - pm_static_literals_t literals = { 0 }; - pm_hash_key_static_literals_add(parser, &literals, argument); + pm_static_literals_t hash_keys = { 0 }; + pm_hash_key_static_literals_add(parser, &hash_keys, 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); @@ -12213,10 +13481,10 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for token_begins_expression_p(parser->current.type) || match2(parser, PM_TOKEN_USTAR_STAR, PM_TOKEN_LABEL) )) { - contains_keyword_splat = parse_assocs(parser, &literals, (pm_node_t *) bare_hash); + contains_keyword_splat = parse_assocs(parser, &hash_keys, (pm_node_t *) bare_hash); } - pm_static_literals_free(&literals); + pm_static_literals_free(&hash_keys); parsed_bare_hash = true; } else if (accept1(parser, PM_TOKEN_KEYWORD_IN)) { // TODO: Could we solve this with binding powers instead? @@ -12300,7 +13568,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); @@ -12312,7 +13580,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); @@ -12371,7 +13639,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; @@ -12433,7 +13701,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; @@ -12461,7 +13729,6 @@ parse_parameters( update_parameter_state(parser, &parser->current, &order); parser_lex(parser); - parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_BLOCK; parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_ALL; pm_forwarding_parameter_node_t *param = pm_forwarding_parameter_node_create(parser, &parser->previous); @@ -12515,22 +13782,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, @@ -12566,7 +13841,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: @@ -12599,12 +13874,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 { @@ -12640,7 +13918,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; @@ -12676,7 +13954,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; @@ -12735,12 +14013,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)) { @@ -12803,10 +14091,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); } @@ -12822,7 +14122,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; @@ -12839,8 +14139,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); } @@ -12855,8 +14169,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); } @@ -12872,13 +14200,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; } /** @@ -12903,18 +14237,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; @@ -12962,7 +14320,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); @@ -13006,19 +14363,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); } @@ -13041,9 +14398,14 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept } else { pm_accepts_block_stack_push(parser, true); parse_arguments(parser, arguments, true, PM_TOKEN_PARENTHESIS_RIGHT); - expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_ARGUMENT_TERM_PAREN); - pm_accepts_block_stack_pop(parser); + if (!accept1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_ARGUMENT_TERM_PAREN, pm_token_type_human(parser->current.type)); + parser->previous.start = parser->previous.end; + parser->previous.type = PM_TOKEN_MISSING; + } + + pm_accepts_block_stack_pop(parser); arguments->closing_loc = PM_LOCATION_TOKEN_VALUE(&parser->previous); } } else if (accepts_command_call && (token_begins_expression_p(parser->current.type) || match3(parser, PM_TOKEN_USTAR, PM_TOKEN_USTAR_STAR, PM_TOKEN_UAMPERSAND)) && !match1(parser, PM_TOKEN_BRACE_LEFT)) { @@ -13103,6 +14465,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); @@ -13127,6 +14643,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); @@ -13206,7 +14725,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); } @@ -13243,6 +14763,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; } @@ -13547,14 +15070,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); } } @@ -13565,7 +15086,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; @@ -13586,14 +15108,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); @@ -13601,7 +15123,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 }; @@ -13777,7 +15301,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); } } @@ -13825,6 +15349,7 @@ parse_method_definition_name(pm_parser_t *parser) { parser_lex(parser); return parser->previous; default: + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_DEF_NAME, pm_token_type_human(parser->current.type)); return (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->current.start, .end = parser->current.end }; } } @@ -13873,7 +15398,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 @@ -13881,9 +15406,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. @@ -13912,13 +15436,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)) { @@ -13926,7 +15467,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 @@ -13945,7 +15486,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); } @@ -13957,7 +15498,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); } @@ -14040,18 +15581,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. @@ -14062,7 +15615,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); @@ -14074,8 +15627,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); @@ -14086,30 +15651,51 @@ 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; + int depth = -1; + if (value_loc->end[-1] == '!' || value_loc->end[-1] == '?') { + pm_parser_err(parser, key->base.location.start, key->base.location.end, PM_ERR_PATTERN_HASH_KEY_LOCALS); + PM_PARSER_ERR_LOCATION_FORMAT(parser, value_loc, PM_ERR_INVALID_LOCAL_VARIABLE_WRITE, (int) (value_loc->end - value_loc->start), (const char *) value_loc->start); } else { - depth = (uint32_t) current_depth; + depth = pm_parser_local_depth_constant_id(parser, constant_id); + } + + if (depth == -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->newline_list, parser->start_line, 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)) { @@ -14119,16 +15705,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); @@ -14162,7 +15749,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; @@ -14173,14 +15760,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); @@ -14197,6 +15784,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; } @@ -14204,18 +15792,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; @@ -14224,15 +15819,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; @@ -14294,7 +15888,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); @@ -14308,7 +15902,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); @@ -14388,7 +15982,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); } } @@ -14455,14 +16049,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); @@ -14475,7 +16069,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 { @@ -14492,23 +16086,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: { @@ -14530,16 +16130,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); } @@ -14550,7 +16157,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; @@ -14560,30 +16167,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)) { @@ -14603,7 +16210,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 @@ -14614,7 +16221,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); @@ -14675,7 +16282,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) { @@ -14763,6 +16370,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)) { @@ -14817,6 +16426,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 @@ -14840,6 +16451,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) { @@ -14868,11 +16481,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); } } @@ -14902,6 +16515,173 @@ pm_parser_err_prefix(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { } } +/** + * 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; + } +} + /** * Parse an expression that begins with the previous node that we just lexed. */ @@ -14950,13 +16730,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } element = (pm_node_t *) pm_keyword_hash_node_create(parser); - pm_static_literals_t literals = { 0 }; + pm_static_literals_t hash_keys = { 0 }; if (!match8(parser, PM_TOKEN_EOF, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_KEYWORD_DO, PM_TOKEN_PARENTHESIS_RIGHT)) { - parse_assocs(parser, &literals, element); + parse_assocs(parser, &hash_keys, element); } - pm_static_literals_free(&literals); + pm_static_literals_free(&hash_keys); parsed_bare_hash = true; } else { element = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_ARRAY_EXPRESSION); @@ -14967,8 +16747,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_keyword_hash_node_t *hash = pm_keyword_hash_node_create(parser); - pm_static_literals_t literals = { 0 }; - pm_hash_key_static_literals_add(parser, &literals, element); + pm_static_literals_t hash_keys = { 0 }; + pm_hash_key_static_literals_add(parser, &hash_keys, element); pm_token_t operator; if (parser->previous.type == PM_TOKEN_EQUAL_GREATER) { @@ -14983,10 +16763,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b element = (pm_node_t *) hash; if (accept1(parser, PM_TOKEN_COMMA) && !match1(parser, PM_TOKEN_BRACKET_RIGHT)) { - parse_assocs(parser, &literals, element); + parse_assocs(parser, &hash_keys, element); } - pm_static_literals_free(&literals); + pm_static_literals_free(&hash_keys); parsed_bare_hash = true; } } @@ -15005,6 +16785,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)); @@ -15012,6 +16796,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); } @@ -15037,9 +16825,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 @@ -15075,7 +16867,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // and we didn't return a multiple assignment node, then we can return a // regular parentheses node now. pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous); } @@ -15085,7 +16877,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // We'll do that here. context_push(parser, PM_CONTEXT_PARENS); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); // If we didn't find a terminator and we didn't find a right // parenthesis, then this is a syntax error. @@ -15096,7 +16888,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // Parse each statement within the parentheses. while (true) { pm_node_t *node = parse_expression(parser, PM_BINDING_POWER_STATEMENT, true, PM_ERR_CANNOT_PARSE_EXPRESSION); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); // If we're recovering from a syntax error, then we need to stop // parsing the statements now. @@ -15127,17 +16919,37 @@ 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); + + pm_void_statements_check(parser, statements); return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous); } case PM_TOKEN_BRACE_LEFT: { + // If we were passed a current_hash_keys via the parser, then that + // means we're already parsing a hash and we want to share the set + // of hash keys with this inner hash we're about to parse for the + // sake of warnings. We'll set it to NULL after we grab it to make + // sure subsequent expressions don't use it. Effectively this is a + // way of getting around passing it to every call to + // parse_expression. + pm_static_literals_t *current_hash_keys = parser->current_hash_keys; + parser->current_hash_keys = NULL; + pm_accepts_block_stack_push(parser, true); parser_lex(parser); pm_hash_node_t *node = pm_hash_node_create(parser, &parser->previous); - pm_static_literals_t literals = { 0 }; if (!match2(parser, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_EOF)) { - parse_assocs(parser, &literals, (pm_node_t *) node); + if (current_hash_keys != NULL) { + parse_assocs(parser, current_hash_keys, (pm_node_t *) node); + } else { + pm_static_literals_t hash_keys = { 0 }; + parse_assocs(parser, &hash_keys, (pm_node_t *) node); + pm_static_literals_free(&hash_keys); + } + accept1(parser, PM_TOKEN_NEWLINE); } @@ -15145,7 +16957,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_HASH_TERM); pm_hash_node_closing_loc_set(node, &parser->previous); - pm_static_literals_free(&literals); return (pm_node_t *) node; } case PM_TOKEN_CHARACTER_LITERAL: { @@ -15448,6 +17259,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); @@ -15533,7 +17345,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); @@ -15557,6 +17369,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; @@ -15570,12 +17385,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; @@ -15653,8 +17471,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; @@ -15664,11 +17483,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); @@ -15679,9 +17503,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)) { @@ -15694,8 +17518,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; @@ -15703,8 +17527,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); } @@ -15743,6 +17567,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: { @@ -15750,6 +17577,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)) { @@ -15760,7 +17590,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; @@ -15770,6 +17600,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: { @@ -15811,16 +17644,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); } @@ -15853,7 +17692,20 @@ 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); + // It's possible that we've parsed a block argument through our + // call to parse_arguments_list. If we found one, we should mark it + // as invalid and destroy it, as we don't have a place for it on the + // yield node. + if (arguments.block != NULL) { + pm_parser_err_node(parser, arguments.block, PM_ERR_UNEXPECTED_BLOCK_ARGUMENT); + pm_node_destroy(parser, arguments.block); + arguments.block = NULL; + } + + 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); @@ -15864,7 +17716,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); @@ -15877,19 +17728,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) { @@ -15912,7 +17767,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) { @@ -15930,7 +17784,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); @@ -15939,16 +17793,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: { @@ -15956,19 +17813,16 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *receiver = NULL; pm_token_t operator = not_provided(parser); - pm_token_t name = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = def_keyword.end, .end = def_keyword.end }; + pm_token_t name; // This context is necessary for lexing `...` in a bare params // 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); @@ -15982,7 +17836,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); @@ -15990,7 +17843,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); @@ -16010,7 +17862,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); @@ -16044,7 +17895,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b receiver = (pm_node_t *) pm_true_node_create(parser, &identifier); break; case PM_TOKEN_KEYWORD_FALSE: - receiver = (pm_node_t *)pm_false_node_create(parser, &identifier); + receiver = (pm_node_t *) pm_false_node_create(parser, &identifier); break; case PM_TOKEN_KEYWORD___FILE__: receiver = (pm_node_t *) pm_source_file_node_create(parser, &identifier); @@ -16066,9 +17917,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b break; } case PM_TOKEN_PARENTHESIS_LEFT: { - // The current context is `PM_CONTEXT_DEF_PARAMS`, however the inner expression - // of this parenthesis should not be processed under this context. - // Thus, the context is popped here. + // The current context is `PM_CONTEXT_DEF_PARAMS`, however + // the inner expression of this parenthesis should not be + // processed under this context. Thus, the context is popped + // here. context_pop(parser); parser_lex(parser); @@ -16085,28 +17937,19 @@ 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); + // To push `PM_CONTEXT_DEF_PARAMS` again is for the same + // reason as described the above. pm_parser_scope_push(parser, true); - - // To push `PM_CONTEXT_DEF_PARAMS` again is for the same reason as described the above. 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; } - // If, after all that, we were unable to find a method name, add an - // error to the error list. - if (name.type == PM_TOKEN_MISSING) { - pm_parser_err_previous(parser, PM_ERR_DEF_NAME); - } - pm_token_t lparen; pm_token_t rparen; pm_parameters_node_t *params; @@ -16167,13 +18010,16 @@ 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); + pm_statements_node_body_append(parser, (pm_statements_node_t *) statements, statement); pm_do_loop_stack_pop(parser); context_pop(parser); end_keyword = not_provided(parser); @@ -16199,7 +18045,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); @@ -16208,10 +18054,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 @@ -16243,6 +18088,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; @@ -16261,6 +18107,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, @@ -16401,8 +18248,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); @@ -16412,8 +18257,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); @@ -16422,15 +18265,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); } @@ -16452,9 +18301,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; @@ -16466,30 +18313,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); @@ -16626,7 +18486,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_interpolated_symbol_node_append(interpolated, first_string); pm_interpolated_symbol_node_append(interpolated, second_string); - free(current); + xfree(current); current = (pm_node_t *) interpolated; } else { assert(false && "unreachable"); @@ -16810,15 +18670,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"); @@ -16843,7 +18703,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 @@ -16852,7 +18712,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: { @@ -16872,7 +18732,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 @@ -16883,7 +18743,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: @@ -16924,10 +18784,14 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b }; parser_lex(parser); - return (pm_node_t *) pm_regular_expression_node_create(parser, &opening, &content, &parser->previous); + + pm_node_t *node = (pm_node_t *) pm_regular_expression_node_create(parser, &opening, &content, &parser->previous); + pm_node_flag_set(node, PM_REGULAR_EXPRESSION_FLAGS_FORCED_US_ASCII_ENCODING); + + return node; } - pm_interpolated_regular_expression_node_t *node; + pm_interpolated_regular_expression_node_t *interpolated; if (match1(parser, PM_TOKEN_STRING_CONTENT)) { // In this case we've hit string content so we know the regular @@ -16936,28 +18800,31 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // regular expression) or if it's not then it has interpolation. pm_string_t unescaped = parser->current_string; pm_token_t content = parser->current; + bool ascii_only = parser->current_regular_expression_ascii_only; parser_lex(parser); // If we hit an end, then we can create a regular expression node // without interpolation, which can be represented more succinctly and // more easily compiled. if (accept1(parser, PM_TOKEN_REGEXP_END)) { - return (pm_node_t *) pm_regular_expression_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped); + pm_node_t *node = (pm_node_t *) pm_regular_expression_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped); + pm_node_flag_set(node, parse_and_validate_regular_expression_encoding(parser, &unescaped, ascii_only, node->flags)); + return node; } // If we get here, then we have interpolation so we'll need to create // a regular expression node with interpolation. - node = pm_interpolated_regular_expression_node_create(parser, &opening); + interpolated = pm_interpolated_regular_expression_node_create(parser, &opening); pm_token_t opening = not_provided(parser); pm_token_t closing = not_provided(parser); pm_node_t *part = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &parser->previous, &closing, &unescaped); - pm_interpolated_regular_expression_node_append(node, part); + pm_interpolated_regular_expression_node_append(interpolated, part); } else { // If the first part of the body of the regular expression is not a // string content, then we have interpolation and we need to create an // interpolated regular expression node. - node = pm_interpolated_regular_expression_node_create(parser, &opening); + interpolated = pm_interpolated_regular_expression_node_create(parser, &opening); } // Now that we're here and we have interpolation, we'll parse all of the @@ -16965,7 +18832,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *part; while (!match2(parser, PM_TOKEN_REGEXP_END, PM_TOKEN_EOF)) { if ((part = parse_string_part(parser)) != NULL) { - pm_interpolated_regular_expression_node_append(node, part); + pm_interpolated_regular_expression_node_append(interpolated, part); } } @@ -16976,9 +18843,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } else { expect1(parser, PM_TOKEN_REGEXP_END, PM_ERR_REGEXP_TERM); } - pm_interpolated_regular_expression_node_closing_set(node, &closing); - return (pm_node_t *) node; + pm_interpolated_regular_expression_node_closing_set(parser, interpolated, &closing); + return (pm_node_t *) interpolated; } case PM_TOKEN_BACKTICK: case PM_TOKEN_PERCENT_LOWER_X: { @@ -17090,8 +18957,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: { @@ -17121,7 +18987,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)) { @@ -17147,7 +19013,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; @@ -17211,18 +19076,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); } @@ -17274,15 +19139,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); } @@ -17290,14 +19169,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); @@ -17306,14 +19234,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); @@ -17329,6 +19262,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); } @@ -17337,7 +19271,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 @@ -17358,18 +19292,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; } @@ -17388,7 +19336,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 }; @@ -17403,14 +19351,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. @@ -17421,11 +19368,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) { @@ -17434,19 +19376,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); } } @@ -17479,8 +19424,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 */ @@ -17497,13 +19442,25 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_values(parser, previous_binding_power, PM_BINDING_POWER_MULTI_ASSIGNMENT + 1, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL); return parse_write(parser, (pm_node_t *) multi_target, &token, value); } + case PM_SOURCE_ENCODING_NODE: + case PM_FALSE_NODE: + case PM_SOURCE_FILE_NODE: + case PM_SOURCE_LINE_NODE: + case PM_NIL_NODE: + case PM_SELF_NODE: + case PM_TRUE_NODE: { + // In these special cases, we have specific error messages + // and we will replace them with local variable writes. + parser_lex(parser); + pm_node_t *value = parse_assignment_values(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL); + return parse_unwriteable_write(parser, node, &token, value); + } default: + // In this case we have an = sign, but we don't know what + // it's for. We need to treat it as an error. We'll mark it + // as an error and skip past it. parser_lex(parser); - - // In this case we have an = sign, but we don't know what it's for. We - // need to treat it as an error. For now, we'll mark it as an error - // and just skip right past it. - pm_parser_err_token(parser, &token, PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL); + pm_parser_err_token(parser, &token, PM_ERR_EXPRESSION_NOT_WRITABLE); return node; } } @@ -17535,16 +19492,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); @@ -17572,11 +19531,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); @@ -17587,7 +19546,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); } @@ -17646,16 +19605,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); @@ -17683,11 +19644,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); @@ -17698,7 +19659,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); } @@ -17767,16 +19728,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); @@ -17804,11 +19767,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); @@ -17819,7 +19782,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); } @@ -17876,7 +19839,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 @@ -17892,9 +19855,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 { @@ -17908,8 +19870,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); @@ -17941,10 +19903,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: @@ -17957,9 +19915,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: { @@ -17986,7 +19955,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t break; } default: { - pm_parser_err_current(parser, PM_ERR_DEF_NAME); + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_EXPECT_MESSAGE, pm_token_type_human(parser->current.type)); message = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end }; } } @@ -18033,7 +20002,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t case PM_TOKEN_KEYWORD_UNTIL_MODIFIER: { parser_lex(parser); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); pm_node_t *predicate = parse_value_expression(parser, binding_power, true, PM_ERR_CONDITIONAL_UNTIL_PREDICATE); return (pm_node_t *) pm_until_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0); @@ -18041,14 +20010,19 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t case PM_TOKEN_KEYWORD_WHILE_MODIFIER: { parser_lex(parser); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); pm_node_t *predicate = parse_value_expression(parser, binding_power, true, PM_ERR_CONDITIONAL_WHILE_PREDICATE); 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) { @@ -18061,6 +20035,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); } @@ -18070,6 +20048,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: { @@ -18145,9 +20127,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); } @@ -18205,11 +20190,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); } @@ -18220,11 +20207,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); } @@ -18265,8 +20254,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; } @@ -18284,6 +20273,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; @@ -18297,6 +20287,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. @@ -18357,22 +20348,22 @@ 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, (pm_node_t *) pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$_", 2)) ); - pm_statements_node_body_append(statements, (pm_node_t *) pm_call_node_fcall_synthesized_create( + pm_statements_node_body_append(parser, statements, (pm_node_t *) pm_call_node_fcall_synthesized_create( parser, arguments, pm_parser_constant_id_constant(parser, "print", 5) )); } - 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, @@ -18397,7 +20388,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, @@ -18410,7 +20401,7 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { } pm_statements_node_t *wrapped_statements = pm_statements_node_create(parser); - pm_statements_node_body_append(wrapped_statements, (pm_node_t *) pm_while_node_synthesized_create( + pm_statements_node_body_append(parser, wrapped_statements, (pm_node_t *) pm_while_node_synthesized_create( parser, (pm_node_t *) pm_call_node_fcall_synthesized_create(parser, arguments, pm_parser_constant_id_constant(parser, "gets", 4)), statements @@ -18436,10 +20427,19 @@ parse_program(pm_parser_t *parser) { parser_lex(parser); pm_statements_node_t *statements = parse_statements(parser, PM_CONTEXT_MAIN); - if (!statements) { + + if (statements == NULL) { statements = pm_statements_node_create(parser); + } else if (!parser->parsing_eval) { + // If we have statements, then the top-level statement should be + // explicitly checked as well. We have to do this here because + // everywhere else we check all but the last statement. + assert(statements->body.size > 0); + pm_void_statement_check(parser, statements->body.nodes[statements->body.size - 1]); } - 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 @@ -18462,6 +20462,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. */ @@ -18506,14 +20538,16 @@ 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 }; // Initialize the constant pool. We're going to completely guess as to the @@ -18555,9 +20589,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; @@ -18566,6 +20598,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); @@ -18595,16 +20629,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); } /** @@ -18664,7 +20763,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); } @@ -18681,6 +20779,134 @@ pm_parse(pm_parser_t *parser) { return parse_program(parser); } +/** + * Read into the stream until the gets callback returns false. If the last read + * line from the stream matches an __END__ marker, then halt and return false, + * otherwise return true. + */ +static bool +pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *fgets) { +#define LINE_SIZE 4096 + char line[LINE_SIZE]; + + while (fgets(line, LINE_SIZE, stream) != NULL) { + size_t length = strlen(line); + + if (length == LINE_SIZE && line[length - 1] != '\n') { + // If we read a line that is the maximum size and it doesn't end + // with a newline, then we'll just append it to the buffer and + // continue reading. + pm_buffer_append_string(buffer, line, length); + continue; + } + + // Append the line to the buffer. + pm_buffer_append_string(buffer, line, length); + + // Check if the line matches the __END__ marker. If it does, then stop + // reading and return false. In most circumstances, this means we should + // stop reading from the stream so that the DATA constant can pick it + // up. + switch (length) { + case 7: + if (strncmp(line, "__END__", 7) == 0) return false; + break; + case 8: + if (strncmp(line, "__END__\n", 8) == 0) return false; + break; + case 9: + if (strncmp(line, "__END__\r\n", 9) == 0) return false; + break; + } + } + + return true; +#undef LINE_SIZE +} + +/** + * Determine if there was an unterminated heredoc at the end of the input, which + * would mean the stream isn't finished and we should keep reading. + * + * For the other lex modes we can check if the lex mode has been closed, but for + * heredocs when we hit EOF we close the lex mode and then go back to parse the + * rest of the line after the heredoc declaration so that we get more of the + * syntax tree. + */ +static bool +pm_parse_stream_unterminated_heredoc_p(pm_parser_t *parser) { + pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) parser->error_list.head; + + for (; diagnostic != NULL; diagnostic = (pm_diagnostic_t *) diagnostic->node.next) { + if (diagnostic->diag_id == PM_ERR_HEREDOC_TERM) { + return true; + } + } + + return false; +} + +/** + * Parse a stream of Ruby source and return the tree. + * + * Prism is designed around having the entire source in memory at once, but you + * can stream stdin in to Ruby so we need to support a streaming API. + */ +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) { + pm_buffer_init(buffer); + + bool eof = pm_parse_stream_read(buffer, stream, fgets); + pm_parser_init(parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options); + pm_node_t *node = pm_parse(parser); + + while (!eof && parser->error_list.size > 0 && (parser->lex_modes.index > 0 || pm_parse_stream_unterminated_heredoc_p(parser))) { + pm_node_destroy(parser, node); + eof = pm_parse_stream_read(buffer, stream, fgets); + + pm_parser_free(parser); + pm_parser_init(parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options); + node = pm_parse(parser); + } + + 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; + 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); @@ -18723,6 +20949,28 @@ pm_serialize_parse(pm_buffer_t *buffer, const uint8_t *source, size_t size, cons pm_options_free(&options); } +/** + * Parse and serialize the AST represented by the source that is read out of the + * given stream into to the given buffer. + */ +PRISM_EXPORTED_FUNCTION void +pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *fgets, const char *data) { + pm_parser_t parser; + pm_options_t options = { 0 }; + pm_options_read(&options, data); + + pm_buffer_t parser_buffer; + pm_node_t *node = pm_parse_stream(&parser, &parser_buffer, stream, fgets, &options); + pm_serialize_header(buffer); + pm_serialize_content(&parser, node, buffer); + pm_buffer_append_byte(buffer, '\0'); + + pm_node_destroy(&parser, node); + pm_buffer_free(&parser_buffer); + pm_parser_free(&parser); + pm_options_free(&options); +} + /** * Parse and serialize the comments in the given source to the given buffer. */ @@ -18745,14 +20993,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 { @@ -18789,7 +21030,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) { @@ -18845,7 +21086,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; @@ -18868,8 +21113,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 @@ -18882,9 +21126,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) { @@ -18966,14 +21218,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)) { @@ -18992,51 +21244,62 @@ 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, ' '); + assert(error->error != NULL); + + 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 7d9b96fa829e99..59067c3021a70a 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -79,6 +80,41 @@ PRISM_EXPORTED_FUNCTION void pm_parser_free(pm_parser_t *parser); */ PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse(pm_parser_t *parser); +/** + * This function is used in pm_parse_stream to retrieve a line of input from a + * stream. It closely mirrors that of fgets so that fgets can be used as the + * default implementation. + */ +typedef char * (pm_parse_stream_fgets_t)(char *string, int size, void *stream); + +/** + * Parse a stream of Ruby source and return the tree. + * + * @param parser The parser to use. + * @param buffer The buffer to use. + * @param stream The stream to parse. + * @param fgets The function to use to read from the stream. + * @param options The optional options to use when parsing. + * @return The AST representing the source. + */ +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. + * + * @param buffer The buffer to serialize to. + * @param stream The stream to parse. + * @param fgets The function to use to read from the stream. + * @param data The optional data to pass to the parser. + */ +PRISM_EXPORTED_FUNCTION void pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *fgets, const char *data); + /** * Serialize the given list of comments to the given buffer. * @@ -155,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. * @@ -185,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. @@ -199,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 713721bb73acd5..08f61c81fa1607 100644 --- a/prism/static_literals.c +++ b/prism/static_literals.c @@ -1,5 +1,21 @@ #include "prism/static_literals.h" +/** + * A small struct used for passing around a subset of the information that is + * stored on the parser. We use this to avoid having static literals explicitly + * depend on the parser struct. + */ +typedef struct { + /** The list of newline offsets to use to calculate line numbers. */ + const pm_newline_list_t *newline_list; + + /** The line number that the parser starts on. */ + int32_t start_line; + + /** The name of the encoding that the parser is using. */ + const char *encoding_name; +} pm_static_literals_metadata_t; + static inline uint32_t murmur_scramble(uint32_t value) { value *= 0xcc9e2d51; @@ -48,17 +64,16 @@ murmur_hash(const uint8_t *key, size_t length) { * these hashes to look for duplicates. */ static uint32_t -node_hash(const pm_parser_t *parser, const pm_node_t *node) { +node_hash(const pm_static_literals_metadata_t *metadata, const pm_node_t *node) { switch (PM_NODE_TYPE(node)) { case PM_INTEGER_NODE: { // Integers hash their value. const pm_integer_t *integer = &((const pm_integer_node_t *) node)->value; - const uint32_t *value = &integer->head.value; - - uint32_t hash = murmur_hash((const uint8_t *) value, sizeof(uint32_t)); - for (const pm_integer_word_t *word = integer->head.next; word != NULL; word = word->next) { - value = &word->value; - hash ^= murmur_hash((const uint8_t *) value, sizeof(uint32_t)); + uint32_t hash; + if (integer->values) { + hash = murmur_hash((const uint8_t *) integer->values, sizeof(uint32_t) * integer->length); + } else { + hash = murmur_hash((const uint8_t *) &integer->value, sizeof(uint32_t)); } if (integer->negative) { @@ -69,7 +84,7 @@ node_hash(const pm_parser_t *parser, const pm_node_t *node) { } case PM_SOURCE_LINE_NODE: { // Source lines hash their line number. - const pm_line_column_t line_column = pm_newline_list_line_column(&parser->newline_list, node->location.start, parser->start_line); + const pm_line_column_t line_column = pm_newline_list_line_column(metadata->newline_list, node->location.start, metadata->start_line); const int32_t *value = &line_column.line; return murmur_hash((const uint8_t *) value, sizeof(int32_t)); } @@ -83,26 +98,30 @@ node_hash(const pm_parser_t *parser, const pm_node_t *node) { // is stored as a subnode, we hash that node and then mix in the // fact that this is a rational node. const pm_node_t *numeric = ((const pm_rational_node_t *) node)->numeric; - return node_hash(parser, numeric) ^ murmur_scramble((uint32_t) node->type); + return node_hash(metadata, numeric) ^ murmur_scramble((uint32_t) node->type); } case PM_IMAGINARY_NODE: { // Imaginaries hash their numeric value. Because their numeric value // is stored as a subnode, we hash that node and then mix in the // fact that this is an imaginary node. const pm_node_t *numeric = ((const pm_imaginary_node_t *) node)->numeric; - return node_hash(parser, numeric) ^ murmur_scramble((uint32_t) node->type); + return node_hash(metadata, numeric) ^ murmur_scramble((uint32_t) node->type); } case PM_STRING_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 @@ -129,7 +148,7 @@ node_hash(const pm_parser_t *parser, const pm_node_t *node) { * and must be able to compare all node types that will be stored in this hash. */ static pm_node_t * -pm_node_hash_insert(pm_node_hash_t *hash, const pm_parser_t *parser, pm_node_t *node, int (*compare)(const pm_parser_t *parser, const pm_node_t *left, const pm_node_t *right)) { +pm_node_hash_insert(pm_node_hash_t *hash, const pm_static_literals_metadata_t *metadata, pm_node_t *node, int (*compare)(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right)) { // If we are out of space, we need to resize the hash. This will cause all // of the nodes to be rehashed and reinserted into the new hash. if (hash->size * 2 >= hash->capacity) { @@ -149,7 +168,7 @@ pm_node_hash_insert(pm_node_hash_t *hash, const pm_parser_t *parser, pm_node_t * pm_node_t *node = hash->nodes[index]; if (node != NULL) { - uint32_t index = node_hash(parser, node) & mask; + uint32_t index = node_hash(metadata, node) & mask; new_nodes[index] = node; } } @@ -162,14 +181,14 @@ pm_node_hash_insert(pm_node_hash_t *hash, const pm_parser_t *parser, pm_node_t * // Now, insert the node into the hash. uint32_t mask = hash->capacity - 1; - uint32_t index = node_hash(parser, node) & mask; + uint32_t index = node_hash(metadata, node) & mask; // We use linear probing to resolve collisions. This means that if the // current index is occupied, we will move to the next index and try again. // We are guaranteed that this will eventually find an empty slot because we // resize the hash when it gets too full. while (hash->nodes[index] != NULL) { - if (compare(parser, hash->nodes[index], node) == 0) break; + if (compare(metadata, hash->nodes[index], node) == 0) break; index = (index + 1) & mask; } @@ -200,17 +219,17 @@ pm_node_hash_free(pm_node_hash_t *hash) { * Return the integer value of the given node as an int64_t. */ static int64_t -pm_int64_value(const pm_parser_t *parser, const pm_node_t *node) { +pm_int64_value(const pm_static_literals_metadata_t *metadata, const pm_node_t *node) { switch (PM_NODE_TYPE(node)) { case PM_INTEGER_NODE: { const pm_integer_t *integer = &((const pm_integer_node_t *) node)->value; - if (integer->length > 0) return integer->negative ? INT64_MIN : INT64_MAX; + if (integer->values) return integer->negative ? INT64_MIN : INT64_MAX; - int64_t value = (int64_t) integer->head.value; + int64_t value = (int64_t) integer->value; return integer->negative ? -value : value; } case PM_SOURCE_LINE_NODE: - return (int64_t) pm_newline_list_line_column(&parser->newline_list, node->location.start, parser->start_line).line; + return (int64_t) pm_newline_list_line_column(metadata->newline_list, node->location.start, metadata->start_line).line; default: assert(false && "unreachable"); return 0; @@ -222,10 +241,10 @@ pm_int64_value(const pm_parser_t *parser, const pm_node_t *node) { * instances. */ static int -pm_compare_integer_nodes(const pm_parser_t *parser, const pm_node_t *left, const pm_node_t *right) { +pm_compare_integer_nodes(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) { if (PM_NODE_TYPE_P(left, PM_SOURCE_LINE_NODE) || PM_NODE_TYPE_P(right, PM_SOURCE_LINE_NODE)) { - int64_t left_value = pm_int64_value(parser, left); - int64_t right_value = pm_int64_value(parser, right); + int64_t left_value = pm_int64_value(metadata, left); + int64_t right_value = pm_int64_value(metadata, right); return PM_NUMERIC_COMPARISON(left_value, right_value); } @@ -238,7 +257,7 @@ pm_compare_integer_nodes(const pm_parser_t *parser, const pm_node_t *left, const * A comparison function for comparing two FloatNode instances. */ static int -pm_compare_float_nodes(PRISM_ATTRIBUTE_UNUSED const pm_parser_t *parser, const pm_node_t *left, const pm_node_t *right) { +pm_compare_float_nodes(PRISM_ATTRIBUTE_UNUSED const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) { const double left_value = ((const pm_float_node_t *) left)->value; const double right_value = ((const pm_float_node_t *) right)->value; return PM_NUMERIC_COMPARISON(left_value, right_value); @@ -248,20 +267,20 @@ pm_compare_float_nodes(PRISM_ATTRIBUTE_UNUSED const pm_parser_t *parser, const p * A comparison function for comparing two nodes that have attached numbers. */ static int -pm_compare_number_nodes(const pm_parser_t *parser, const pm_node_t *left, const pm_node_t *right) { +pm_compare_number_nodes(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) { if (PM_NODE_TYPE(left) != PM_NODE_TYPE(right)) { return PM_NUMERIC_COMPARISON(PM_NODE_TYPE(left), PM_NODE_TYPE(right)); } switch (PM_NODE_TYPE(left)) { case PM_IMAGINARY_NODE: - return pm_compare_number_nodes(parser, ((const pm_imaginary_node_t *) left)->numeric, ((const pm_imaginary_node_t *) right)->numeric); + return pm_compare_number_nodes(metadata, ((const pm_imaginary_node_t *) left)->numeric, ((const pm_imaginary_node_t *) right)->numeric); case PM_RATIONAL_NODE: - return pm_compare_number_nodes(parser, ((const pm_rational_node_t *) left)->numeric, ((const pm_rational_node_t *) right)->numeric); + return pm_compare_number_nodes(metadata, ((const pm_rational_node_t *) left)->numeric, ((const pm_rational_node_t *) right)->numeric); case PM_INTEGER_NODE: - return pm_compare_integer_nodes(parser, left, right); + return pm_compare_integer_nodes(metadata, left, right); case PM_FLOAT_NODE: - return pm_compare_float_nodes(parser, left, right); + return pm_compare_float_nodes(metadata, left, right); default: assert(false && "unreachable"); return 0; @@ -290,7 +309,7 @@ pm_string_value(const pm_node_t *node) { * A comparison function for comparing two nodes that have attached strings. */ static int -pm_compare_string_nodes(PRISM_ATTRIBUTE_UNUSED const pm_parser_t *parser, const pm_node_t *left, const pm_node_t *right) { +pm_compare_string_nodes(PRISM_ATTRIBUTE_UNUSED const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) { const pm_string_t *left_string = pm_string_value(left); const pm_string_t *right_string = pm_string_value(right); return pm_string_compare(left_string, right_string); @@ -300,7 +319,7 @@ pm_compare_string_nodes(PRISM_ATTRIBUTE_UNUSED const pm_parser_t *parser, const * A comparison function for comparing two RegularExpressionNode instances. */ static int -pm_compare_regular_expression_nodes(PRISM_ATTRIBUTE_UNUSED const pm_parser_t *parser, const pm_node_t *left, const pm_node_t *right) { +pm_compare_regular_expression_nodes(PRISM_ATTRIBUTE_UNUSED const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) { const pm_regular_expression_node_t *left_regexp = (const pm_regular_expression_node_t *) left; const pm_regular_expression_node_t *right_regexp = (const pm_regular_expression_node_t *) right; @@ -316,25 +335,77 @@ pm_compare_regular_expression_nodes(PRISM_ATTRIBUTE_UNUSED const pm_parser_t *pa * Add a node to the set of static literals. */ 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; - +pm_static_literals_add(const pm_newline_list_t *newline_list, int32_t start_line, pm_static_literals_t *literals, pm_node_t *node) { switch (PM_NODE_TYPE(node)) { case PM_INTEGER_NODE: case PM_SOURCE_LINE_NODE: - return pm_node_hash_insert(&literals->integer_nodes, parser, node, pm_compare_integer_nodes); + return pm_node_hash_insert( + &literals->integer_nodes, + &(pm_static_literals_metadata_t) { + .newline_list = newline_list, + .start_line = start_line, + .encoding_name = NULL + }, + node, + pm_compare_integer_nodes + ); case PM_FLOAT_NODE: - return pm_node_hash_insert(&literals->float_nodes, parser, node, pm_compare_float_nodes); + return pm_node_hash_insert( + &literals->float_nodes, + &(pm_static_literals_metadata_t) { + .newline_list = newline_list, + .start_line = start_line, + .encoding_name = NULL + }, + node, + pm_compare_float_nodes + ); case PM_RATIONAL_NODE: case PM_IMAGINARY_NODE: - return pm_node_hash_insert(&literals->number_nodes, parser, node, pm_compare_number_nodes); + return pm_node_hash_insert( + &literals->number_nodes, + &(pm_static_literals_metadata_t) { + .newline_list = newline_list, + .start_line = start_line, + .encoding_name = NULL + }, + node, + pm_compare_number_nodes + ); case PM_STRING_NODE: case PM_SOURCE_FILE_NODE: - return pm_node_hash_insert(&literals->string_nodes, parser, node, pm_compare_string_nodes); + return pm_node_hash_insert( + &literals->string_nodes, + &(pm_static_literals_metadata_t) { + .newline_list = newline_list, + .start_line = start_line, + .encoding_name = NULL + }, + node, + pm_compare_string_nodes + ); case PM_REGULAR_EXPRESSION_NODE: - return pm_node_hash_insert(&literals->regexp_nodes, parser, node, pm_compare_regular_expression_nodes); + return pm_node_hash_insert( + &literals->regexp_nodes, + &(pm_static_literals_metadata_t) { + .newline_list = newline_list, + .start_line = start_line, + .encoding_name = NULL + }, + node, + pm_compare_regular_expression_nodes + ); case PM_SYMBOL_NODE: - return pm_node_hash_insert(&literals->symbol_nodes, parser, node, pm_compare_string_nodes); + return pm_node_hash_insert( + &literals->symbol_nodes, + &(pm_static_literals_metadata_t) { + .newline_list = newline_list, + .start_line = start_line, + .encoding_name = NULL + }, + node, + pm_compare_string_nodes + ); case PM_TRUE_NODE: { pm_node_t *duplicated = literals->true_node; literals->true_node = node; @@ -372,3 +443,196 @@ 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. + */ +static inline void +pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_metadata_t *metadata, 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_node(buffer, metadata, 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_node(buffer, metadata, 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, "#", metadata->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(metadata->newline_list, node->location.start, metadata->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; + } +} + +/** + * Create a string-based representation of the given static literal. + */ +PRISM_EXPORTED_FUNCTION void +pm_static_literal_inspect(pm_buffer_t *buffer, const pm_newline_list_t *newline_list, int32_t start_line, const char *encoding_name, const pm_node_t *node) { + pm_static_literal_inspect_node( + buffer, + &(pm_static_literals_metadata_t) { + .newline_list = newline_list, + .start_line = start_line, + .encoding_name = encoding_name + }, + node + ); +} diff --git a/prism/static_literals.h b/prism/static_literals.h index 2a3d815fa9ef86..72706054c13fec 100644 --- a/prism/static_literals.h +++ b/prism/static_literals.h @@ -8,8 +8,7 @@ #include "prism/defines.h" #include "prism/ast.h" -#include "prism/node.h" -#include "prism/parser.h" +#include "prism/util/pm_newline_list.h" #include #include @@ -92,12 +91,13 @@ typedef struct { /** * Add a node to the set of static literals. * - * @param parser The parser that created the node. + * @param newline_list The list of newline offsets to use to calculate lines. + * @param start_line The line number that the parser starts on. * @param literals The set of static literals to add the node to. * @param node The node to add to the set. * @return A pointer to the node that is being overwritten, if there is one. */ -pm_node_t * pm_static_literals_add(const pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node); +pm_node_t * pm_static_literals_add(const pm_newline_list_t *newline_list, int32_t start_line, pm_static_literals_t *literals, pm_node_t *node); /** * Free the internal memory associated with the given static literals set. @@ -106,4 +106,15 @@ 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 newline_list The list of newline offsets to use to calculate lines. + * @param start_line The line number that the parser starts on. + * @param encoding_name The name of the encoding of the source being parsed. + * @param node The node to create a string representation of. + */ +PRISM_EXPORTED_FUNCTION void pm_static_literal_inspect(pm_buffer_t *buffer, const pm_newline_list_t *newline_list, int32_t start_line, const char *encoding_name, const pm_node_t *node); + #endif diff --git a/prism/templates/ext/prism/api_node.c.erb b/prism/templates/ext/prism/api_node.c.erb index d39952a9b3237a..419236ef782013 100644 --- a/prism/templates/ext/prism/api_node.c.erb +++ b/prism/templates/ext/prism/api_node.c.erb @@ -37,14 +37,26 @@ pm_string_new(const pm_string_t *string, rb_encoding *encoding) { return rb_enc_str_new((const char *) pm_string_source(string), pm_string_length(string), encoding); } -static VALUE +VALUE pm_integer_new(const pm_integer_t *integer) { - VALUE result = UINT2NUM(integer->head.value); - size_t shift = 0; + VALUE result; + if (integer->values == NULL) { + result = UINT2NUM(integer->value); + } else { + VALUE string = rb_str_new(NULL, integer->length * 8); + unsigned char *bytes = (unsigned char *) RSTRING_PTR(string); + + size_t offset = integer->length * 8; + for (size_t value_index = 0; value_index < integer->length; value_index++) { + uint32_t value = integer->values[value_index]; + + for (int index = 0; index < 8; index++) { + int byte = (value >> (4 * index)) & 0xf; + bytes[--offset] = byte < 10 ? byte + '0' : byte - 10 + 'a'; + } + } - for (const pm_integer_word_t *node = integer->head.next; node != NULL; node = node->next) { - VALUE receiver = rb_funcall(UINT2NUM(node->value), rb_intern("<<"), 1, ULONG2NUM(++shift * 32)); - result = rb_funcall(receiver, rb_intern("|"), 1, result); + result = rb_funcall(string, rb_intern("to_i"), 1, UINT2NUM(16)); } if (integer->negative) { @@ -96,21 +108,21 @@ pm_node_stack_pop(pm_node_stack_node_t **stack) { VALUE pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encoding, VALUE source) { - ID *constants = xcalloc(parser->constant_pool.size, sizeof(ID)); + VALUE constants = rb_ary_new_capa(parser->constant_pool.size); for (uint32_t index = 0; index < parser->constant_pool.size; index++) { pm_constant_t *constant = &parser->constant_pool.constants[index]; int state = 0; VALUE string = rb_enc_str_new((const char *) constant->start, constant->length, encoding); - ID value = rb_protect(rb_intern_str, string, &state); + VALUE value = rb_protect(rb_str_intern, string, &state); if (state != 0) { - value = rb_intern_const("?"); + value = ID2SYM(rb_intern_const("?")); rb_set_errinfo(Qnil); } - constants[index] = value; + rb_ary_push(constants, value); } pm_node_stack_node_t *node_stack = NULL; @@ -130,15 +142,15 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi switch (PM_NODE_TYPE(node)) { <%- nodes.each do |node| -%> - <%- if node.fields.any? { |field| [Prism::NodeField, Prism::OptionalNodeField, Prism::NodeListField].include?(field.class) } -%> + <%- if node.fields.any? { |field| [Prism::Template::NodeField, Prism::Template::OptionalNodeField, Prism::Template::NodeListField].include?(field.class) } -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" case <%= node.type %>: { pm_<%= node.human %>_t *cast = (pm_<%= node.human %>_t *) node; <%- node.fields.each do |field| -%> <%- case field -%> - <%- when Prism::NodeField, Prism::OptionalNodeField -%> + <%- when Prism::Template::NodeField, Prism::Template::OptionalNodeField -%> pm_node_stack_push(&node_stack, (pm_node_t *) cast-><%= field.name %>); - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> for (size_t index = 0; index < cast-><%= field.name %>.size; index++) { pm_node_stack_push(&node_stack, (pm_node_t *) cast-><%= field.name %>.nodes[index]); } @@ -159,7 +171,7 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi <%- nodes.each do |node| -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" case <%= node.type %>: { - <%- if node.fields.any? { |field| ![Prism::NodeField, Prism::OptionalNodeField, Prism::FlagsField].include?(field.class) } -%> + <%- if node.fields.any? { |field| ![Prism::Template::NodeField, Prism::Template::OptionalNodeField, Prism::Template::FlagsField].include?(field.class) } -%> pm_<%= node.human %>_t *cast = (pm_<%= node.human %>_t *) node; <%- end -%> VALUE argv[<%= node.fields.length + 2 %>]; @@ -170,50 +182,50 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi // <%= field.name %> <%- case field -%> - <%- when Prism::NodeField, Prism::OptionalNodeField -%> + <%- when Prism::Template::NodeField, Prism::Template::OptionalNodeField -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = rb_ary_pop(value_stack); - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = rb_ary_new_capa(cast-><%= field.name %>.size); for (size_t index = 0; index < cast-><%= field.name %>.size; index++) { rb_ary_push(argv[<%= index %>], rb_ary_pop(value_stack)); } - <%- when Prism::StringField -%> + <%- when Prism::Template::StringField -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = pm_string_new(&cast-><%= field.name %>, encoding); - <%- when Prism::ConstantField -%> + <%- when Prism::Template::ConstantField -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" assert(cast-><%= field.name %> != 0); - argv[<%= index %>] = rb_id2sym(constants[cast-><%= field.name %> - 1]); - <%- when Prism::OptionalConstantField -%> - argv[<%= index %>] = cast-><%= field.name %> == 0 ? Qnil : rb_id2sym(constants[cast-><%= field.name %> - 1]); - <%- when Prism::ConstantListField -%> + argv[<%= index %>] = RARRAY_AREF(constants, cast-><%= field.name %> - 1); + <%- when Prism::Template::OptionalConstantField -%> + argv[<%= index %>] = cast-><%= field.name %> == 0 ? Qnil : RARRAY_AREF(constants, cast-><%= field.name %> - 1); + <%- when Prism::Template::ConstantListField -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = rb_ary_new_capa(cast-><%= field.name %>.size); for (size_t index = 0; index < cast-><%= field.name %>.size; index++) { assert(cast-><%= field.name %>.ids[index] != 0); - rb_ary_push(argv[<%= index %>], rb_id2sym(constants[cast-><%= field.name %>.ids[index] - 1])); + rb_ary_push(argv[<%= index %>], RARRAY_AREF(constants, cast-><%= field.name %>.ids[index] - 1)); } - <%- when Prism::LocationField -%> + <%- when Prism::Template::LocationField -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = pm_location_new(parser, cast-><%= field.name %>.start, cast-><%= field.name %>.end); - <%- when Prism::OptionalLocationField -%> + <%- when Prism::Template::OptionalLocationField -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = cast-><%= field.name %>.start == NULL ? Qnil : pm_location_new(parser, cast-><%= field.name %>.start, cast-><%= field.name %>.end); - <%- when Prism::UInt8Field -%> + <%- when Prism::Template::UInt8Field -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = UINT2NUM(cast-><%= field.name %>); - <%- when Prism::UInt32Field -%> + <%- when Prism::Template::UInt32Field -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = ULONG2NUM(cast-><%= field.name %>); - <%- when Prism::FlagsField -%> + <%- when Prism::Template::FlagsField -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = ULONG2NUM(node->flags & ~PM_NODE_FLAG_COMMON_MASK); - <%- when Prism::IntegerField -%> + <%- when Prism::Template::IntegerField -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = pm_integer_new(&cast-><%= field.name %>); - <%- when Prism::DoubleField -%> + <%- when Prism::Template::DoubleField -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" argv[<%= index %>] = DBL2NUM(cast-><%= field.name %>); <%- else -%> @@ -234,9 +246,7 @@ pm_ast_new(const pm_parser_t *parser, const pm_node_t *node, rb_encoding *encodi } } - VALUE result = rb_ary_pop(value_stack); - xfree(constants); - return result; + return rb_ary_pop(value_stack); } void diff --git a/prism/templates/include/prism/ast.h.erb b/prism/templates/include/prism/ast.h.erb index ac528a086bb61c..0fe7905e404bd5 100644 --- a/prism/templates/include/prism/ast.h.erb +++ b/prism/templates/include/prism/ast.h.erb @@ -151,7 +151,7 @@ typedef struct pm_node { * <%= node.name %> * * Type: <%= node.type %> -<%- if (node_flags = node.fields.find { |field| field.is_a? Prism::FlagsField }) -%> +<%- if (node_flags = node.fields.find { |field| field.is_a? Prism::Template::FlagsField }) -%> * Flags: <%- found = flags.find { |flag| flag.name == node_flags.kind }.tap { |found| raise "Expected to find #{field.kind}" unless found } -%> <%- found.values.each do |value| -%> @@ -164,7 +164,7 @@ typedef struct pm_node { typedef struct pm_<%= node.human %> { /** The embedded base node. */ pm_node_t base; -<%- node.fields.grep_v(Prism::FlagsField).each do |field| -%> +<%- node.fields.grep_v(Prism::Template::FlagsField).each do |field| -%> /** * <%= node.name %>#<%= field.name %> @@ -176,16 +176,16 @@ typedef struct pm_<%= node.human %> { <%- end -%> */ <%= case field - when Prism::NodeField, Prism::OptionalNodeField then "struct #{field.c_type} *#{field.name}" - when Prism::NodeListField then "struct pm_node_list #{field.name}" - when Prism::ConstantField, Prism::OptionalConstantField then "pm_constant_id_t #{field.name}" - when Prism::ConstantListField then "pm_constant_id_list_t #{field.name}" - when Prism::StringField then "pm_string_t #{field.name}" - when Prism::LocationField, Prism::OptionalLocationField then "pm_location_t #{field.name}" - when Prism::UInt8Field then "uint8_t #{field.name}" - when Prism::UInt32Field then "uint32_t #{field.name}" - when Prism::IntegerField then "pm_integer_t #{field.name}" - when Prism::DoubleField then "double #{field.name}" + when Prism::Template::NodeField, Prism::Template::OptionalNodeField then "struct #{field.c_type} *#{field.name}" + when Prism::Template::NodeListField then "struct pm_node_list #{field.name}" + when Prism::Template::ConstantField, Prism::Template::OptionalConstantField then "pm_constant_id_t #{field.name}" + when Prism::Template::ConstantListField then "pm_constant_id_list_t #{field.name}" + when Prism::Template::StringField then "pm_string_t #{field.name}" + when Prism::Template::LocationField, Prism::Template::OptionalLocationField then "pm_location_t #{field.name}" + when Prism::Template::UInt8Field then "uint8_t #{field.name}" + when Prism::Template::UInt32Field then "uint32_t #{field.name}" + when Prism::Template::IntegerField then "pm_integer_t #{field.name}" + when Prism::Template::DoubleField then "double #{field.name}" else raise field.class.name end %>; @@ -212,6 +212,6 @@ typedef enum pm_<%= flag.human %> { * to specify that through the environment. It will never be true except for in * those build systems. */ -#define PRISM_SERIALIZE_ONLY_SEMANTICS_FIELDS <%= Prism::SERIALIZE_ONLY_SEMANTICS_FIELDS %> +#define PRISM_SERIALIZE_ONLY_SEMANTICS_FIELDS <%= Prism::Template::SERIALIZE_ONLY_SEMANTICS_FIELDS %> #endif diff --git a/prism/templates/include/prism/diagnostic.h.erb b/prism/templates/include/prism/diagnostic.h.erb new file mode 100644 index 00000000000000..07bbc8fae79264 --- /dev/null +++ b/prism/templates/include/prism/diagnostic.h.erb @@ -0,0 +1,130 @@ +/** + * @file diagnostic.h + * + * A list of diagnostics generated during parsing. + */ +#ifndef PRISM_DIAGNOSTIC_H +#define PRISM_DIAGNOSTIC_H + +#include "prism/ast.h" +#include "prism/defines.h" +#include "prism/util/pm_list.h" + +#include +#include +#include + +/** + * The diagnostic IDs of all of the diagnostics, used to communicate the types + * of errors between the parser and the user. + */ +typedef enum { + // These are the error diagnostics. + <%- errors.each do |error| -%> + PM_ERR_<%= error.name %>, + <%- end -%> + + // These are the warning diagnostics. + <%- warnings.each do |warning| -%> + PM_WARN_<%= warning.name %>, + <%- end -%> +} pm_diagnostic_id_t; + +/** + * This struct represents a diagnostic generated during parsing. + * + * @extends pm_list_node_t + */ +typedef struct { + /** The embedded base node. */ + pm_list_node_t node; + + /** The location of the diagnostic in the source. */ + pm_location_t location; + + /** The ID of the diagnostic. */ + pm_diagnostic_id_t diag_id; + + /** The message associated with the diagnostic. */ + const char *message; + + /** + * Whether or not the memory related to the message of this diagnostic is + * owned by this diagnostic. If it is, it needs to be freed when the + * diagnostic is freed. + */ + bool owned; + + /** + * The level of the diagnostic, see `pm_error_level_t` and + * `pm_warning_level_t` for possible values. + */ + uint8_t level; +} pm_diagnostic_t; + +/** + * The levels of errors generated during parsing. + */ +typedef enum { + /** 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, + + /** For errors that should raise a load error. */ + PM_ERROR_LEVEL_LOAD = 2 +} pm_error_level_t; + +/** + * The levels of warnings generated during parsing. + */ +typedef enum { + /** For warnings which should be emitted if $VERBOSE != nil. */ + PM_WARNING_LEVEL_DEFAULT = 0, + + /** For warnings which should be emitted if $VERBOSE == true. */ + PM_WARNING_LEVEL_VERBOSE = 1 +} pm_warning_level_t; + +/** + * Get the human-readable name of the given diagnostic ID. + * + * @param diag_id The diagnostic ID. + * @return The human-readable name of the diagnostic ID. + */ +const char * pm_diagnostic_id_human(pm_diagnostic_id_t diag_id); + +/** + * Append a diagnostic to the given list of diagnostics that is using shared + * memory for its message. + * + * @param list The list to append to. + * @param start The start of the diagnostic. + * @param end The end of the diagnostic. + * @param diag_id The diagnostic ID. + * @return Whether the diagnostic was successfully appended. + */ +bool pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id); + +/** + * Append a diagnostic to the given list of diagnostics that is using a format + * string for its message. + * + * @param list The list to append to. + * @param start The start of the diagnostic. + * @param end The end of the diagnostic. + * @param diag_id The diagnostic ID. + * @param ... The arguments to the format string for the message. + * @return Whether the diagnostic was successfully appended. + */ +bool pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id, ...); + +/** + * Deallocate the internal state of the given diagnostic list. + * + * @param list The list to deallocate. + */ +void pm_diagnostic_list_free(pm_list_t *list); + +#endif diff --git a/prism/templates/lib/prism/compiler.rb.erb b/prism/templates/lib/prism/compiler.rb.erb index 2c947f6ed2007f..45ed88d8de8589 100644 --- a/prism/templates/lib/prism/compiler.rb.erb +++ b/prism/templates/lib/prism/compiler.rb.erb @@ -16,7 +16,7 @@ module Prism # Prism.parse("1 + 2").value.accept(SExpressions.new) # # => [:program, [[[:call, [[:integer], [:arguments, [[:integer]]]]]]]] # - class Compiler + class Compiler < Visitor # Visit an individual node. def visit(node) node&.accept(self) diff --git a/prism/templates/lib/prism/dot_visitor.rb.erb b/prism/templates/lib/prism/dot_visitor.rb.erb index 5a9b4954442884..93c94b19eedd30 100644 --- a/prism/templates/lib/prism/dot_visitor.rb.erb +++ b/prism/templates/lib/prism/dot_visitor.rb.erb @@ -113,15 +113,15 @@ module Prism # <%= field.name %> <%- case field -%> - <%- when Prism::NodeField -%> + <%- when Prism::Template::NodeField -%> table.field("<%= field.name %>", port: true) digraph.edge("#{id}:<%= field.name %> -> #{node_id(node.<%= field.name %>)};") - <%- when Prism::OptionalNodeField -%> + <%- when Prism::Template::OptionalNodeField -%> unless (<%= field.name %> = node.<%= field.name %>).nil? table.field("<%= field.name %>", port: true) digraph.edge("#{id}:<%= field.name %> -> #{node_id(<%= field.name %>)};") end - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> if node.<%= field.name %>.any? table.field("<%= field.name %>", port: true) @@ -133,15 +133,15 @@ module Prism else table.field("<%= field.name %>", "[]") end - <%- when Prism::StringField, Prism::ConstantField, Prism::OptionalConstantField, Prism::UInt8Field, Prism::UInt32Field, Prism::ConstantListField, Prism::IntegerField, Prism::DoubleField -%> + <%- when Prism::Template::StringField, Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::ConstantListField, Prism::Template::IntegerField, Prism::Template::DoubleField -%> table.field("<%= field.name %>", node.<%= field.name %>.inspect) - <%- when Prism::LocationField -%> + <%- when Prism::Template::LocationField -%> table.field("<%= field.name %>", location_inspect(node.<%= field.name %>)) - <%- when Prism::OptionalLocationField -%> + <%- when Prism::Template::OptionalLocationField -%> unless (<%= field.name %> = node.<%= field.name %>).nil? table.field("<%= field.name %>", location_inspect(<%= field.name %>)) end - <%- when Prism::FlagsField -%> + <%- when Prism::Template::FlagsField -%> <%- flag = flags.find { |flag| flag.name == field.kind }.tap { |flag| raise "Expected to find #{field.kind}" unless flag } -%> table.field("<%= field.name %>", <%= flag.human %>_inspect(node)) <%- else -%> diff --git a/prism/templates/lib/prism/inspect_visitor.rb.erb b/prism/templates/lib/prism/inspect_visitor.rb.erb new file mode 100644 index 00000000000000..8e7902f0f1ff2d --- /dev/null +++ b/prism/templates/lib/prism/inspect_visitor.rb.erb @@ -0,0 +1,137 @@ +module Prism + # This visitor is responsible for composing the strings that get returned by + # the various #inspect methods defined on each of the nodes. + class InspectVisitor < Visitor + # Most of the time, we can simply pass down the indent to the next node. + # However, when we are inside a list we want some extra special formatting + # when we hit an element in that list. In this case, we have a special + # command that replaces the subsequent indent with the given value. + class Replace # :nodoc: + attr_reader :value + + def initialize(value) + @value = value + end + end + + private_constant :Replace + + # The current prefix string. + attr_reader :indent + + # The list of commands that we need to execute in order to compose the + # final string. + attr_reader :commands + + # Initializes a new instance of the InspectVisitor. + def initialize(indent = +"") + @indent = indent + @commands = [] + end + + # Compose an inspect string for the given node. + def self.compose(node) + visitor = new + node.accept(visitor) + visitor.compose + end + + # Compose the final string. + def compose + buffer = +"" + replace = nil + + until commands.empty? + # @type var command: String | node | Replace + # @type var indent: String + command, indent = *commands.shift + + case command + when String + buffer << (replace || indent) + buffer << command + replace = nil + when Node + visitor = InspectVisitor.new(indent) + command.accept(visitor) + @commands = [*visitor.commands, *@commands] + when Replace + replace = command.value + else + raise "Unknown command: #{command.inspect}" + end + end + + buffer + end + <%- nodes.each do |node| -%> + + # Inspect a <%= node.name %> node. + def visit_<%= node.human %>(node) + commands << [inspect_node(<%= node.name.inspect %>, node), indent] + <%- node.fields.each_with_index do |field, index| -%> + <%- pointer = index == node.fields.length - 1 ? "└── " : "├── " -%> + <%- preadd = index == node.fields.length - 1 ? " " : "│ " -%> + <%- case field -%> + <%- when Prism::Template::NodeListField -%> + commands << ["<%= pointer %><%= field.name %>: (length: #{(<%= field.name %> = node.<%= field.name %>).length})\n", indent] + if <%= field.name %>.any? + <%= field.name %>[0...-1].each do |child| + commands << [Replace.new("#{indent}<%= preadd %>├── "), indent] + commands << [child, "#{indent}<%= preadd %>│ "] + end + commands << [Replace.new("#{indent}<%= preadd %>└── "), indent] + commands << [<%= field.name %>[-1], "#{indent}<%= preadd %> "] + end + <%- when Prism::Template::NodeField -%> + commands << ["<%= pointer %><%= field.name %>:\n", indent] + commands << [node.<%= field.name %>, "#{indent}<%= preadd %>"] + <%- when Prism::Template::OptionalNodeField -%> + if (<%= field.name %> = node.<%= field.name %>).nil? + commands << ["<%= pointer %><%= field.name %>: ∅\n", indent] + else + commands << ["<%= pointer %><%= field.name %>:\n", indent] + commands << [<%= field.name %>, "#{indent}<%= preadd %>"] + end + <%- when Prism::Template::ConstantField, Prism::Template::ConstantListField, Prism::Template::StringField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::IntegerField, Prism::Template::DoubleField -%> + commands << ["<%= pointer %><%= field.name %>: #{node.<%= field.name %>.inspect}\n", indent] + <%- when Prism::Template::OptionalConstantField -%> + if (<%= field.name %> = node.<%= field.name %>).nil? + commands << ["<%= pointer %><%= field.name %>: ∅\n", indent] + else + commands << ["<%= pointer %><%= field.name %>: #{<%= field.name %>.inspect}\n", indent] + end + <%- when Prism::Template::FlagsField -%> + <%- flag = flags.find { |flag| flag.name == field.kind }.tap { |flag| raise unless flag } -%> + flags = [<%= flag.values.map { |value| "(\"#{value.name.downcase}\" if node.#{value.name.downcase}?)" }.join(", ") %>].compact + commands << ["<%= pointer %><%= field.name %>: #{flags.empty? ? "∅" : flags.join(", ")}\n", indent] + <%- when Prism::Template::LocationField, Prism::Template::OptionalLocationField -%> + commands << ["<%= pointer %><%= field.name %>: #{inspect_location(node.<%= field.name %>)}\n", indent] + <%- end -%> + <%- end -%> + end + <%- end -%> + + private + + # Compose a header for the given node. + def inspect_node(name, node) + result = +"@ #{name} (" + + location = node.location + result << "location: (#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column})" + result << ", newline: true" if node.newline? + + result << ")\n" + end + + # Compose a string representing the given inner location field. + def inspect_location(location) + if location + "(#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column}) = #{location.slice.inspect}" + else + "∅" + end + end + end +end diff --git a/prism/templates/lib/prism/mutation_compiler.rb.erb b/prism/templates/lib/prism/mutation_compiler.rb.erb index 9a81b704ebcbf7..565ee4e315d47d 100644 --- a/prism/templates/lib/prism/mutation_compiler.rb.erb +++ b/prism/templates/lib/prism/mutation_compiler.rb.erb @@ -7,9 +7,9 @@ module Prism <%= "\n" if index != 0 -%> # Copy a <%= node.name %> node def visit_<%= node.human %>(node) - <%- fields = node.fields.select { |field| [Prism::NodeField, Prism::OptionalNodeField, Prism::NodeListField].include?(field.class) } -%> + <%- fields = node.fields.select { |field| [Prism::Template::NodeField, Prism::Template::OptionalNodeField, Prism::Template::NodeListField].include?(field.class) } -%> <%- if fields.any? -%> - node.copy(<%= fields.map { |field| "#{field.name}: #{field.is_a?(Prism::NodeListField) ? "visit_all" : "visit"}(node.#{field.name})" }.join(", ") %>) + node.copy(<%= fields.map { |field| "#{field.name}: #{field.is_a?(Prism::Template::NodeListField) ? "visit_all" : "visit"}(node.#{field.name})" }.join(", ") %>) <%- else -%> node.copy <%- end -%> diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index 4762963bf6f584..2606af570430fa 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -60,6 +60,17 @@ module Prism DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot end + # Returns a list of the fields that exist for this node class. Fields + # describe the structure of the node. This kind of reflection is useful for + # things like recursively visiting each node _and_ field in the tree. + def self.fields + # This method should only be called on subclasses of Node, not Node + # itself. + raise NoMethodError, "undefined method `fields' for #{inspect}" if self == Node + + Reflection.fields_for(self) + end + # -------------------------------------------------------------------------- # :section: Node interface # These methods are effectively abstract methods that must be implemented by @@ -97,6 +108,16 @@ module Prism def type raise NoMethodError, "undefined method `type' for #{inspect}" end + + # Returns a string representation of the node. + def inspect + raise NoMethodError, "undefined method `inspect' for #{inspect}" + end + + # Returns the type of the node as a symbol. + def self.type + raise NoMethodError, "undefined method `type' for #{inspect}" + end end <%- nodes.each do |node| -%> @@ -110,7 +131,7 @@ module Prism @newline = false @location = location <%- node.fields.each do |field| -%> - <%- if Prism::CHECK_FIELD_KIND && field.respond_to?(:check_field_kind) -%> + <%- if Prism::Template::CHECK_FIELD_KIND && field.respond_to?(:check_field_kind) -%> raise <%= field.name %>.inspect unless <%= field.check_field_kind %> <%- end -%> @<%= field.name %> = <%= field.name %> @@ -131,9 +152,9 @@ module Prism def set_newline_flag(newline_marked) # :nodoc: <%- field = node.fields.find { |f| f.name == node.newline } or raise node.newline -%> <%- case field -%> - <%- when Prism::NodeField -%> + <%- when Prism::Template::NodeField -%> <%= field.name %>.set_newline_flag(newline_marked) - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> first = <%= field.name %>.first first.set_newline_flag(newline_marked) if first <%- else raise field.class.name -%> @@ -145,23 +166,23 @@ module Prism def child_nodes [<%= node.fields.map { |field| case field - when Prism::NodeField, Prism::OptionalNodeField then field.name - when Prism::NodeListField then "*#{field.name}" + when Prism::Template::NodeField, Prism::Template::OptionalNodeField then field.name + when Prism::Template::NodeListField then "*#{field.name}" end }.compact.join(", ") %>] end # def compact_child_nodes: () -> Array[Node] def compact_child_nodes - <%- if node.fields.any? { |field| field.is_a?(Prism::OptionalNodeField) } -%> + <%- if node.fields.any? { |field| field.is_a?(Prism::Template::OptionalNodeField) } -%> compact = [] #: Array[Prism::node] <%- node.fields.each do |field| -%> <%- case field -%> - <%- when Prism::NodeField -%> + <%- when Prism::Template::NodeField -%> compact << <%= field.name %> - <%- when Prism::OptionalNodeField -%> + <%- when Prism::Template::OptionalNodeField -%> compact << <%= field.name %> if <%= field.name %> - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> compact.concat(<%= field.name %>) <%- end -%> <%- end -%> @@ -169,8 +190,8 @@ module Prism <%- else -%> [<%= node.fields.map { |field| case field - when Prism::NodeField then field.name - when Prism::NodeListField then "*#{field.name}" + when Prism::Template::NodeField then field.name + when Prism::Template::NodeListField then "*#{field.name}" end }.compact.join(", ") %>] <%- end -%> @@ -180,8 +201,8 @@ module Prism def comment_targets [<%= node.fields.map { |field| case field - when Prism::NodeField, Prism::LocationField then field.name - when Prism::OptionalNodeField, Prism::NodeListField, Prism::OptionalLocationField then "*#{field.name}" + when Prism::Template::NodeField, Prism::Template::LocationField then field.name + when Prism::Template::OptionalNodeField, Prism::Template::NodeListField, Prism::Template::OptionalLocationField then "*#{field.name}" end }.compact.join(", ") %>] #: Array[Prism::node | Location] end @@ -198,23 +219,23 @@ module Prism def deconstruct_keys(keys) { <%= (node.fields.map { |field| "#{field.name}: #{field.name}" } + ["location: location"]).join(", ") %> } end - <%- node.fields.each do |field| -%> + <%- if field.comment.nil? -%> - # <%= "private " if field.is_a?(Prism::FlagsField) %>attr_reader <%= field.name %>: <%= field.rbs_class %> + # <%= "protected " if field.is_a?(Prism::Template::FlagsField) %>attr_reader <%= field.name %>: <%= field.rbs_class %> <%- else -%> <%- field.each_comment_line do |line| -%> #<%= line %> <%- end -%> <%- end -%> <%- case field -%> - <%- when Prism::LocationField -%> + <%- when Prism::Template::LocationField -%> def <%= field.name %> location = @<%= field.name %> return location if location.is_a?(Location) @<%= field.name %> = Location.new(source, location >> 32, location & 0xFFFFFFFF) end - <%- when Prism::OptionalLocationField -%> + <%- when Prism::Template::OptionalLocationField -%> def <%= field.name %> location = @<%= field.name %> case location @@ -227,13 +248,12 @@ module Prism end end <%- else -%> - attr_reader :<%= field.name -%><%= "\n private :#{field.name}" if field.is_a?(Prism::FlagsField) %> + attr_reader :<%= field.name -%><%= "\n protected :#{field.name}" if field.is_a?(Prism::Template::FlagsField) %> <%- end -%> - <%- end -%> <%- node.fields.each do |field| -%> <%- case field -%> - <%- when Prism::LocationField -%> + <%- when Prism::Template::LocationField -%> <%- raise unless field.name.end_with?("_loc") -%> <%- next if node.fields.any? { |other| other.name == field.name.delete_suffix("_loc") } -%> @@ -241,7 +261,7 @@ module Prism def <%= field.name.delete_suffix("_loc") %> <%= field.name %>.slice end - <%- when Prism::OptionalLocationField -%> + <%- when Prism::Template::OptionalLocationField -%> <%- raise unless field.name.end_with?("_loc") -%> <%- next if node.fields.any? { |other| other.name == field.name.delete_suffix("_loc") } -%> @@ -249,7 +269,7 @@ module Prism def <%= field.name.delete_suffix("_loc") %> <%= field.name %>&.slice end - <%- when Prism::FlagsField -%> + <%- when Prism::Template::FlagsField -%> <%- flags.find { |flag| flag.name == field.kind }.tap { |flag| raise "Expected to find #{field.kind}" unless flag }.values.each do |value| -%> # def <%= value.name.downcase %>?: () -> bool @@ -260,45 +280,9 @@ module Prism <%- end -%> <%- end -%> - # def inspect(NodeInspector inspector) -> String - def inspect(inspector = NodeInspector.new) - inspector << inspector.header(self) - <%- node.fields.each_with_index do |field, index| -%> - <%- pointer, preadd = index == node.fields.length - 1 ? ["└── ", " "] : ["├── ", "│ "] -%> - <%- case field -%> - <%- when Prism::NodeListField -%> - inspector << "<%= pointer %><%= field.name %>: #{inspector.list("#{inspector.prefix}<%= preadd %>", <%= field.name %>)}" - <%- when Prism::ConstantListField -%> - inspector << "<%= pointer %><%= field.name %>: #{<%= field.name %>.inspect}\n" - <%- when Prism::NodeField -%> - inspector << "<%= pointer %><%= field.name %>:\n" - inspector << inspector.child_node(<%= field.name %>, "<%= preadd %>") - <%- when Prism::OptionalNodeField -%> - if (<%= field.name %> = self.<%= field.name %>).nil? - inspector << "<%= pointer %><%= field.name %>: ∅\n" - else - inspector << "<%= pointer %><%= field.name %>:\n" - inspector << <%= field.name %>.inspect(inspector.child_inspector("<%= preadd %>")).delete_prefix(inspector.prefix) - end - <%- when Prism::ConstantField, Prism::StringField, Prism::UInt8Field, Prism::UInt32Field, Prism::IntegerField, Prism::DoubleField -%> - inspector << "<%= pointer %><%= field.name %>: #{<%= field.name %>.inspect}\n" - <%- when Prism::OptionalConstantField -%> - if (<%= field.name %> = self.<%= field.name %>).nil? - inspector << "<%= pointer %><%= field.name %>: ∅\n" - else - inspector << "<%= pointer %><%= field.name %>: #{<%= field.name %>.inspect}\n" - end - <%- when Prism::FlagsField -%> - <%- flag = flags.find { |flag| flag.name == field.kind }.tap { |flag| raise unless flag } -%> - flags = [<%= flag.values.map { |value| "(\"#{value.name.downcase}\" if #{value.name.downcase}?)" }.join(", ") %>].compact - inspector << "<%= pointer %><%= field.name %>: #{flags.empty? ? "∅" : flags.join(", ")}\n" - <%- when Prism::LocationField, Prism::OptionalLocationField -%> - inspector << "<%= pointer %><%= field.name %>: #{inspector.location(<%= field.name %>)}\n" - <%- else -%> - <%- raise -%> - <%- end -%> - <%- end -%> - inspector.to_str + # def inspect -> String + def inspect + InspectVisitor.compose(self) end # Sometimes you want to check an instance of a node against a list of @@ -328,6 +312,22 @@ module Prism def self.type :<%= node.human %> end + + # Implements case-equality for the node. This is effectively == but without + # comparing the value of locations. Locations are checked only for presence. + def ===(other) + other.is_a?(<%= node.name %>)<%= " &&" if node.fields.any? %> + <%- node.fields.each_with_index do |field, index| -%> + <%- if field.is_a?(Prism::Template::LocationField) || field.is_a?(Prism::Template::OptionalLocationField) -%> + (<%= field.name %>.nil? == other.<%= field.name %>.nil?)<%= " &&" if index != node.fields.length - 1 %> + <%- elsif field.is_a?(Prism::Template::NodeListField) || field.is_a?(Prism::Template::ConstantListField) -%> + (<%= field.name %>.length == other.<%= field.name %>.length) && + <%= field.name %>.zip(other.<%= field.name %>).all? { |left, right| left === right }<%= " &&" if index != node.fields.length - 1 %> + <%- else -%> + (<%= field.name %> === other.<%= field.name %>)<%= " &&" if index != node.fields.length - 1 %> + <%- end -%> + <%- end -%> + end end <%- end -%> <%- flags.each_with_index do |flag, flag_index| -%> diff --git a/prism/templates/lib/prism/reflection.rb.erb b/prism/templates/lib/prism/reflection.rb.erb new file mode 100644 index 00000000000000..3c1d61c6c1ac91 --- /dev/null +++ b/prism/templates/lib/prism/reflection.rb.erb @@ -0,0 +1,137 @@ +module Prism + # The Reflection module provides the ability to reflect on the structure of + # the syntax tree itself, as opposed to looking at a single syntax tree. This + # is useful in metaprogramming contexts. + module Reflection + # A field represents a single piece of data on a node. It is the base class + # for all other field types. + class Field + # The name of the field. + attr_reader :name + + # Initializes the field with the given name. + def initialize(name) + @name = name + end + end + + # A node field represents a single child node in the syntax tree. It + # resolves to a Prism::Node in Ruby. + class NodeField < Field + end + + # An optional node field represents a single child node in the syntax tree + # that may or may not be present. It resolves to either a Prism::Node or nil + # in Ruby. + class OptionalNodeField < Field + end + + # A node list field represents a list of child nodes in the syntax tree. It + # resolves to an array of Prism::Node instances in Ruby. + class NodeListField < Field + end + + # A constant field represents a constant value on a node. Effectively, it + # represents an identifier found within the source. It resolves to a symbol + # in Ruby. + class ConstantField < Field + end + + # An optional constant field represents a constant value on a node that may + # or may not be present. It resolves to either a symbol or nil in Ruby. + class OptionalConstantField < Field + end + + # A constant list field represents a list of constant values on a node. It + # resolves to an array of symbols in Ruby. + class ConstantListField < Field + end + + # A string field represents a string value on a node. It almost always + # represents the unescaped value of a string-like literal. It resolves to a + # string in Ruby. + class StringField < Field + end + + # A location field represents the location of some part of the node in the + # source code. For example, the location of a keyword or an operator. It + # resolves to a Prism::Location in Ruby. + class LocationField < Field + end + + # An optional location field represents the location of some part of the + # node in the source code that may or may not be present. It resolves to + # either a Prism::Location or nil in Ruby. + class OptionalLocationField < Field + end + + # An integer field represents an integer value. It is used to represent the + # value of an integer literal, the depth of local variables, and the number + # of a numbered reference. It resolves to an Integer in Ruby. + class IntegerField < Field + end + + # A float field represents a double-precision floating point value. It is + # used exclusively to represent the value of a floating point literal. It + # resolves to a Float in Ruby. + class FloatField < Field + end + + # A flags field represents a bitset of flags on a node. It resolves to an + # integer in Ruby. Note that the flags cannot be accessed directly on the + # node because the integer is kept private. Instead, the various flags in + # the bitset should be accessed through their query methods. + class FlagsField < Field + # The names of the flags in the bitset. + attr_reader :flags + + # Initializes the flags field with the given name and flags. + def initialize(name, flags) + super(name) + @flags = flags + end + end + + # Returns the fields for the given node. + def self.fields_for(node) + case node.type + <%- nodes.each do |node| -%> + when :<%= node.human %> + [<%= node.fields.map { |field| + case field + when Prism::Template::NodeField + "NodeField.new(:#{field.name})" + when Prism::Template::OptionalNodeField + "OptionalNodeField.new(:#{field.name})" + when Prism::Template::NodeListField + "NodeListField.new(:#{field.name})" + when Prism::Template::ConstantField + "ConstantField.new(:#{field.name})" + when Prism::Template::OptionalConstantField + "OptionalConstantField.new(:#{field.name})" + when Prism::Template::ConstantListField + "ConstantListField.new(:#{field.name})" + when Prism::Template::StringField + "StringField.new(:#{field.name})" + when Prism::Template::LocationField + "LocationField.new(:#{field.name})" + when Prism::Template::OptionalLocationField + "OptionalLocationField.new(:#{field.name})" + when Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::IntegerField + "IntegerField.new(:#{field.name})" + when Prism::Template::DoubleField + "FloatField.new(:#{field.name})" + when Prism::Template::FlagsField + found = flags.find { |flag| flag.name == field.kind }.tap { |found| raise "Expected to find #{field.kind}" unless found } + "FlagsField.new(:#{field.name}, [#{found.values.map { |value| ":#{value.name.downcase}?" }.join(", ")}])" + else + raise field.class.name + end + }.join(", ") %>] + <%- end -%> + else + raise "Unknown node type: #{node.type.inspect}" + end + end + end +end diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 4f4efe7c102912..0ba2e81c74e677 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -1,15 +1,5 @@ require "stringio" - -# Polyfill for String#unpack1 with the offset parameter. -if String.instance_method(:unpack1).parameters.none? { |_, name| name == :offset } - String.prepend( - Module.new { - def unpack1(format, offset: 0) # :nodoc: - offset == 0 ? super(format) : self[offset..].unpack1(format) - end - } - ) -end +require_relative "polyfill/string" module Prism # A module responsible for deserializing parse results. @@ -20,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 = 26 # The patch version of prism that we are expecting to find in the serialized # strings. @@ -126,12 +116,23 @@ module Prism end end + DIAGNOSTIC_TYPES = [ + <%- errors.each do |error| -%> + <%= error.name.downcase.to_sym.inspect %>, + <%- end -%> + <%- warnings.each do |warning| -%> + <%= warning.name.downcase.to_sym.inspect %>, + <%- end -%> + ].freeze + + private_constant :DIAGNOSTIC_TYPES + def load_metadata 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(load_embedded_string, load_location_object, load_error_level) } - warnings = Array.new(load_varuint) { ParseWarning.new(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 @@ -142,7 +143,7 @@ module Prism length = load_varuint lex_state = load_varuint location = Location.new(@source, start, length) - tokens << [Prism::Token.new(source, type, location.slice, location), lex_state] + tokens << [Token.new(source, type, location.slice, location), lex_state] end tokens @@ -157,7 +158,7 @@ module Prism tokens.each { |token,| token.value.force_encoding(encoding) } raise "Expected to consume all bytes while deserializing" unless @io.eof? - Prism::ParseResult.new(tokens, comments, magic_comments, data_loc, errors, warnings, @source) + LexResult.new(tokens, comments, magic_comments, data_loc, errors, warnings, @source) end def load_nodes @@ -176,7 +177,7 @@ module Prism def load_result node, comments, magic_comments, data_loc, errors, warnings = load_nodes - Prism::ParseResult.new(node, comments, magic_comments, data_loc, errors, warnings, @source) + ParseResult.new(node, comments, magic_comments, data_loc, errors, warnings, @source) end private @@ -295,9 +296,11 @@ module Prism case level when 0 - :fatal + :syntax when 1 :argument + when 2 + :load else raise "Unknown level: #{level}" end @@ -330,19 +333,19 @@ module Prism <%= node.name %>.new( source, <%= (node.fields.map { |field| case field - when Prism::NodeField then "load_node" - when Prism::OptionalNodeField then "load_optional_node" - when Prism::StringField then "load_string" - when Prism::NodeListField then "Array.new(load_varuint) { load_node }" - when Prism::ConstantField then "load_required_constant" - when Prism::OptionalConstantField then "load_optional_constant" - when Prism::ConstantListField then "Array.new(load_varuint) { load_required_constant }" - when Prism::LocationField then "load_location" - when Prism::OptionalLocationField then "load_optional_location" - when Prism::UInt8Field then "io.getbyte" - when Prism::UInt32Field, Prism::FlagsField then "load_varuint" - when Prism::IntegerField then "load_integer" - when Prism::DoubleField then "load_double" + when Prism::Template::NodeField then "load_node" + when Prism::Template::OptionalNodeField then "load_optional_node" + when Prism::Template::StringField then "load_string" + when Prism::Template::NodeListField then "Array.new(load_varuint) { load_node }" + when Prism::Template::ConstantField then "load_required_constant" + when Prism::Template::OptionalConstantField then "load_optional_constant" + when Prism::Template::ConstantListField then "Array.new(load_varuint) { load_required_constant }" + when Prism::Template::LocationField then "load_location" + when Prism::Template::OptionalLocationField then "load_optional_location" + when Prism::Template::UInt8Field then "io.getbyte" + when Prism::Template::UInt32Field, Prism::Template::FlagsField then "load_varuint" + when Prism::Template::IntegerField then "load_integer" + when Prism::Template::DoubleField then "load_double" else raise end } + ["location"]).join(", ") -%>) @@ -367,19 +370,19 @@ module Prism <%= node.name %>.new( source, <%= (node.fields.map { |field| case field - when Prism::NodeField then "load_node" - when Prism::OptionalNodeField then "load_optional_node" - when Prism::StringField then "load_string" - when Prism::NodeListField then "Array.new(load_varuint) { load_node }" - when Prism::ConstantField then "load_required_constant" - when Prism::OptionalConstantField then "load_optional_constant" - when Prism::ConstantListField then "Array.new(load_varuint) { load_required_constant }" - when Prism::LocationField then "load_location" - when Prism::OptionalLocationField then "load_optional_location" - when Prism::UInt8Field then "io.getbyte" - when Prism::UInt32Field, Prism::FlagsField then "load_varuint" - when Prism::IntegerField then "load_integer" - when Prism::DoubleField then "load_double" + when Prism::Template::NodeField then "load_node" + when Prism::Template::OptionalNodeField then "load_optional_node" + when Prism::Template::StringField then "load_string" + when Prism::Template::NodeListField then "Array.new(load_varuint) { load_node }" + when Prism::Template::ConstantField then "load_required_constant" + when Prism::Template::OptionalConstantField then "load_optional_constant" + when Prism::Template::ConstantListField then "Array.new(load_varuint) { load_required_constant }" + when Prism::Template::LocationField then "load_location" + when Prism::Template::OptionalLocationField then "load_optional_location" + when Prism::Template::UInt8Field then "io.getbyte" + when Prism::Template::UInt32Field, Prism::Template::FlagsField then "load_varuint" + when Prism::Template::IntegerField then "load_integer" + when Prism::Template::DoubleField then "load_double" else raise end } + ["location"]).join(", ") -%>) diff --git a/prism/diagnostic.c b/prism/templates/src/diagnostic.c.erb similarity index 54% rename from prism/diagnostic.c rename to prism/templates/src/diagnostic.c.erb index 3c43363fc50429..8736fb48a2f53a 100644 --- a/prism/diagnostic.c +++ b/prism/templates/src/diagnostic.c.erb @@ -1,5 +1,7 @@ #include "prism/diagnostic.h" +#define PM_DIAGNOSTIC_ID_MAX <%= errors.length + warnings.length %> + /** This struct holds the data for each diagnostic. */ typedef struct { /** The message associated with the diagnostic. */ @@ -63,262 +65,331 @@ 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: * * * `PM_WARNING_LEVEL_DEFAULT` - Warnings that appear for `ruby -c -e 'code'`. * * `PM_WARNING_LEVEL_VERBOSE` - Warnings that appear with `-w`, as in `ruby -w -c -e 'code'`. */ -static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = { +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_CHARACTER] = { "invalid multibyte character 0x%X", 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_TERM] = { "expected a closing delimiter for the regular expression", 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 `&`; no anonymous block parameter", 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] = { "unexpected %s; 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] = { "unexpected %s; expected a method name", 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] = { "embedded document meets end of file", 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_MESSAGE] = { "unexpected %s; expecting a message to send to the receiver", 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_EXPRESSION_NOT_WRITABLE] = { "unexpected '='; target cannot be written", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_ENCODING] = { "Can't assign to __ENCODING__", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_FALSE] = { "Can't assign to false", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_FILE] = { "Can't assign to __FILE__", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_LINE] = { "Can't assign to __LINE__", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_NIL] = { "Can't assign to nil", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_SELF] = { "Can't change the value of self", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_NOT_WRITABLE_TRUE] = { "Can't assign to true", 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_LOCAL_VARIABLE_READ] = { "identifier %.*s is not valid to get", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_LOCAL_VARIABLE_WRITE] = { "identifier %.*s is not valid to set", 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_HASH_KEY_LOCALS] = { "key must be valid as local variables", PM_ERROR_LEVEL_SYNTAX }, + [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_BLOCK_ARGUMENT] = { "block argument should not be given", 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_UNREACHABLE_STATEMENT] = { "statement not reached", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_UNUSED_LOCAL_VARIABLE] = { "assigned but unused variable - %.*s", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_VOID_STATEMENT] = { "possibly useless use of %.*s in void context", PM_WARNING_LEVEL_VERBOSE } }; +/** + * Get the human-readable name of the given diagnostic ID. + */ +const char * +pm_diagnostic_id_human(pm_diagnostic_id_t diag_id) { + switch (diag_id) { + <%- errors.each do |error| -%> + case PM_ERR_<%= error.name %>: return "<%= error.name.downcase %>"; + <%- end -%> + <%- warnings.each do |warning| -%> + case PM_WARN_<%= warning.name %>: return "<%= warning.name.downcase %>"; + <%- end -%> + } + + assert(false && "unreachable"); + return ""; +} + static inline const char * pm_diagnostic_message(pm_diagnostic_id_t diag_id) { - assert(diag_id < PM_DIAGNOSTIC_ID_LEN); + assert(diag_id < PM_DIAGNOSTIC_ID_MAX); const char *message = diagnostic_messages[diag_id].message; assert(message); @@ -328,7 +399,7 @@ pm_diagnostic_message(pm_diagnostic_id_t diag_id) { static inline uint8_t pm_diagnostic_level(pm_diagnostic_id_t diag_id) { - assert(diag_id < PM_DIAGNOSTIC_ID_LEN); + assert(diag_id < PM_DIAGNOSTIC_ID_MAX); return (uint8_t) diagnostic_messages[diag_id].level; } @@ -338,11 +409,12 @@ 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) { .location = { start, end }, + .diag_id = diag_id, .message = pm_diagnostic_message(diag_id), .owned = false, .level = pm_diagnostic_level(diag_id) @@ -369,7 +441,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; } @@ -387,6 +459,7 @@ pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const ui *diagnostic = (pm_diagnostic_t) { .location = { start, end }, + .diag_id = diag_id, .message = message, .owned = true, .level = pm_diagnostic_level(diag_id) diff --git a/prism/templates/src/node.c.erb b/prism/templates/src/node.c.erb index d689c2c66b3c37..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); } @@ -91,25 +121,25 @@ pm_node_destroy(pm_parser_t *parser, pm_node_t *node) { <%- nodes.each do |node| -%> #line <%= __LINE__ + 1 %> "<%= File.basename(__FILE__) %>" case <%= node.type %>: { - <%- if node.fields.any? { |field| ![Prism::LocationField, Prism::OptionalLocationField, Prism::UInt8Field, Prism::UInt32Field, Prism::FlagsField, Prism::ConstantField, Prism::OptionalConstantField, Prism::DoubleField].include?(field.class) } -%> + <%- if node.fields.any? { |field| ![Prism::Template::LocationField, Prism::Template::OptionalLocationField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::FlagsField, Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::DoubleField].include?(field.class) } -%> pm_<%= node.human %>_t *cast = (pm_<%= node.human %>_t *) node; <%- end -%> <%- node.fields.each do |field| -%> <%- case field -%> - <%- when Prism::LocationField, Prism::OptionalLocationField, Prism::UInt8Field, Prism::UInt32Field, Prism::FlagsField, Prism::ConstantField, Prism::OptionalConstantField, Prism::DoubleField -%> - <%- when Prism::NodeField -%> + <%- when Prism::Template::LocationField, Prism::Template::OptionalLocationField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::FlagsField, Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::DoubleField -%> + <%- when Prism::Template::NodeField -%> pm_node_destroy(parser, (pm_node_t *)cast-><%= field.name %>); - <%- when Prism::OptionalNodeField -%> + <%- when Prism::Template::OptionalNodeField -%> if (cast-><%= field.name %> != NULL) { pm_node_destroy(parser, (pm_node_t *)cast-><%= field.name %>); } - <%- when Prism::StringField -%> + <%- when Prism::Template::StringField -%> pm_string_free(&cast-><%= field.name %>); - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> pm_node_list_destroy(parser, &cast-><%= field.name %>); - <%- when Prism::ConstantListField -%> + <%- when Prism::Template::ConstantListField -%> pm_constant_id_list_free(&cast-><%= field.name %>); - <%- when Prism::IntegerField -%> + <%- when Prism::Template::IntegerField -%> pm_integer_free(&cast-><%= field.name %>); <%- else -%> <%- raise -%> @@ -142,20 +172,20 @@ pm_node_memsize_node(pm_node_t *node, pm_memsize_t *memsize) { memsize->memsize += sizeof(*cast); <%- node.fields.each do |field| -%> <%- case field -%> - <%- when Prism::ConstantField, Prism::OptionalConstantField, Prism::UInt8Field, Prism::UInt32Field, Prism::FlagsField, Prism::LocationField, Prism::OptionalLocationField, Prism::DoubleField -%> - <%- when Prism::NodeField -%> + <%- when Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::FlagsField, Prism::Template::LocationField, Prism::Template::OptionalLocationField, Prism::Template::DoubleField -%> + <%- when Prism::Template::NodeField -%> pm_node_memsize_node((pm_node_t *)cast-><%= field.name %>, memsize); - <%- when Prism::OptionalNodeField -%> + <%- when Prism::Template::OptionalNodeField -%> if (cast-><%= field.name %> != NULL) { pm_node_memsize_node((pm_node_t *)cast-><%= field.name %>, memsize); } - <%- when Prism::StringField -%> + <%- when Prism::Template::StringField -%> memsize->memsize += (pm_string_memsize(&cast-><%= field.name %>) - sizeof(pm_string_t)); - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> memsize->memsize += (pm_node_list_memsize(&cast-><%= field.name %>, memsize) - sizeof(pm_node_list_t)); - <%- when Prism::ConstantListField -%> + <%- when Prism::Template::ConstantListField -%> memsize->memsize += (pm_constant_id_list_memsize(&cast-><%= field.name %>) - sizeof(pm_constant_id_list_t)); - <%- when Prism::IntegerField -%> + <%- when Prism::Template::IntegerField -%> memsize->memsize += (pm_integer_memsize(&cast-><%= field.name %>) - sizeof(pm_integer_t)); <%- else -%> <%- raise -%> @@ -214,20 +244,20 @@ PRISM_EXPORTED_FUNCTION void pm_visit_child_nodes(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data) { switch (PM_NODE_TYPE(node)) { <%- nodes.each do |node| -%> - <%- if (fields = node.fields.select { |field| field.is_a?(Prism::NodeField) || field.is_a?(Prism::OptionalNodeField) || field.is_a?(Prism::NodeListField) }).any? -%> + <%- if (fields = node.fields.select { |field| field.is_a?(Prism::Template::NodeField) || field.is_a?(Prism::Template::OptionalNodeField) || field.is_a?(Prism::Template::NodeListField) }).any? -%> case <%= node.type %>: { const pm_<%= node.human %>_t *cast = (const pm_<%= node.human %>_t *) node; <%- fields.each do |field| -%> // Visit the <%= field.name %> field <%- case field -%> - <%- when Prism::NodeField -%> + <%- when Prism::Template::NodeField -%> pm_visit_node((const pm_node_t *) cast-><%= field.name %>, visitor, data); - <%- when Prism::OptionalNodeField -%> + <%- when Prism::Template::OptionalNodeField -%> if (cast-><%= field.name %> != NULL) { pm_visit_node((const pm_node_t *) cast-><%= field.name %>, visitor, data); } - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> const pm_node_list_t *<%= field.name %> = &cast-><%= field.name %>; for (size_t index = 0; index < <%= field.name %>->size; index++) { pm_visit_node(<%= field.name %>->nodes[index], visitor, data); @@ -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); @@ -280,15 +314,15 @@ pm_dump_json(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *no pm_buffer_append_byte(buffer, ','); pm_buffer_append_string(buffer, "\"<%= field.name %>\":", <%= field.name.bytesize + 3 %>); <%- case field -%> - <%- when Prism::NodeField -%> + <%- when Prism::Template::NodeField -%> pm_dump_json(buffer, parser, (const pm_node_t *) cast-><%= field.name %>); - <%- when Prism::OptionalNodeField -%> + <%- when Prism::Template::OptionalNodeField -%> if (cast-><%= field.name %> != NULL) { pm_dump_json(buffer, parser, (const pm_node_t *) cast-><%= field.name %>); } else { pm_buffer_append_string(buffer, "null", 4); } - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> const pm_node_list_t *<%= field.name %> = &cast-><%= field.name %>; pm_buffer_append_byte(buffer, '['); @@ -297,20 +331,20 @@ pm_dump_json(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *no pm_dump_json(buffer, parser, <%= field.name %>->nodes[index]); } pm_buffer_append_byte(buffer, ']'); - <%- when Prism::StringField -%> + <%- when Prism::Template::StringField -%> const pm_string_t *<%= field.name %> = &cast-><%= field.name %>; pm_buffer_append_byte(buffer, '"'); pm_buffer_append_source(buffer, pm_string_source(<%= field.name %>), pm_string_length(<%= field.name %>), PM_BUFFER_ESCAPING_JSON); pm_buffer_append_byte(buffer, '"'); - <%- when Prism::ConstantField -%> + <%- when Prism::Template::ConstantField -%> pm_dump_json_constant(buffer, parser, cast-><%= field.name %>); - <%- when Prism::OptionalConstantField -%> + <%- when Prism::Template::OptionalConstantField -%> if (cast-><%= field.name %> != PM_CONSTANT_ID_UNSET) { pm_dump_json_constant(buffer, parser, cast-><%= field.name %>); } else { pm_buffer_append_string(buffer, "null", 4); } - <%- when Prism::ConstantListField -%> + <%- when Prism::Template::ConstantListField -%> const pm_constant_id_list_t *<%= field.name %> = &cast-><%= field.name %>; pm_buffer_append_byte(buffer, '['); @@ -319,19 +353,19 @@ pm_dump_json(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *no pm_dump_json_constant(buffer, parser, <%= field.name %>->ids[index]); } pm_buffer_append_byte(buffer, ']'); - <%- when Prism::LocationField -%> + <%- when Prism::Template::LocationField -%> pm_dump_json_location(buffer, parser, &cast-><%= field.name %>); - <%- when Prism::OptionalLocationField -%> + <%- when Prism::Template::OptionalLocationField -%> if (cast-><%= field.name %>.start != NULL) { pm_dump_json_location(buffer, parser, &cast-><%= field.name %>); } else { pm_buffer_append_string(buffer, "null", 4); } - <%- when Prism::UInt8Field -%> + <%- when Prism::Template::UInt8Field -%> pm_buffer_append_format(buffer, "%" PRIu8, cast-><%= field.name %>); - <%- when Prism::UInt32Field -%> + <%- when Prism::Template::UInt32Field -%> pm_buffer_append_format(buffer, "%" PRIu32, cast-><%= field.name %>); - <%- when Prism::FlagsField -%> + <%- when Prism::Template::FlagsField -%> size_t flags = 0; pm_buffer_append_byte(buffer, '['); <%- found = flags.find { |flag| flag.name == field.kind }.tap { |found| raise "Expected to find #{field.kind}" unless found } -%> @@ -343,9 +377,9 @@ pm_dump_json(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *no } <%- end -%> pm_buffer_append_byte(buffer, ']'); - <%- when Prism::IntegerField -%> + <%- when Prism::Template::IntegerField -%> pm_integer_string(buffer, &cast-><%= field.name %>); - <%- when Prism::DoubleField -%> + <%- when Prism::Template::DoubleField -%> pm_buffer_append_format(buffer, "%f", cast-><%= field.name %>); <%- else -%> <%- raise %> @@ -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 2278d0d38ca4c0..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); @@ -36,7 +45,7 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm pm_buffer_concat(output_buffer, prefix_buffer); pm_buffer_append_string(output_buffer, "+-- <%= field.name %>:", <%= 4 + field.name.length + 1 %>); <%- case field -%> - <%- when Prism::NodeField -%> + <%- when Prism::Template::NodeField -%> pm_buffer_append_byte(output_buffer, '\n'); size_t prefix_length = prefix_buffer->length; @@ -44,7 +53,7 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm pm_buffer_concat(output_buffer, prefix_buffer); prettyprint_node(output_buffer, parser, (pm_node_t *) cast-><%= field.name %>, prefix_buffer); prefix_buffer->length = prefix_length; - <%- when Prism::OptionalNodeField -%> + <%- when Prism::Template::OptionalNodeField -%> if (cast-><%= field.name %> == NULL) { pm_buffer_append_string(output_buffer, " nil\n", 5); } else { @@ -56,11 +65,11 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm prettyprint_node(output_buffer, parser, (pm_node_t *) cast-><%= field.name %>, prefix_buffer); prefix_buffer->length = prefix_length; } - <%- when Prism::StringField -%> + <%- when Prism::Template::StringField -%> pm_buffer_append_string(output_buffer, " \"", 2); pm_buffer_append_source(output_buffer, pm_string_source(&cast-><%= field.name %>), pm_string_length(&cast-><%= field.name %>), PM_BUFFER_ESCAPING_RUBY); pm_buffer_append_string(output_buffer, "\"\n", 2); - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> pm_buffer_append_format(output_buffer, " (length: %lu)\n", (unsigned long) (cast-><%= field.name %>.size)); size_t last_index = cast-><%= field.name %>.size; @@ -73,11 +82,11 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm prettyprint_node(output_buffer, parser, (pm_node_t *) cast-><%= field.name %>.nodes[index], prefix_buffer); prefix_buffer->length = prefix_length; } - <%- when Prism::ConstantField -%> + <%- when Prism::Template::ConstantField -%> pm_buffer_append_byte(output_buffer, ' '); prettyprint_constant(output_buffer, parser, cast-><%= field.name %>); pm_buffer_append_byte(output_buffer, '\n'); - <%- when Prism::OptionalConstantField -%> + <%- when Prism::Template::OptionalConstantField -%> if (cast-><%= field.name %> == 0) { pm_buffer_append_string(output_buffer, " nil\n", 5); } else { @@ -85,21 +94,21 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm prettyprint_constant(output_buffer, parser, cast-><%= field.name %>); pm_buffer_append_byte(output_buffer, '\n'); } - <%- when Prism::ConstantListField -%> + <%- when Prism::Template::ConstantListField -%> pm_buffer_append_string(output_buffer, " [", 2); for (uint32_t index = 0; index < cast-><%= field.name %>.size; index++) { if (index != 0) pm_buffer_append_string(output_buffer, ", ", 2); prettyprint_constant(output_buffer, parser, cast-><%= field.name %>.ids[index]); } pm_buffer_append_string(output_buffer, "]\n", 2); - <%- when Prism::LocationField -%> + <%- when Prism::Template::LocationField -%> pm_location_t *location = &cast-><%= field.name %>; pm_buffer_append_byte(output_buffer, ' '); prettyprint_location(output_buffer, parser, location); pm_buffer_append_string(output_buffer, " = \"", 4); pm_buffer_append_source(output_buffer, location->start, (size_t) (location->end - location->start), PM_BUFFER_ESCAPING_RUBY); pm_buffer_append_string(output_buffer, "\"\n", 2); - <%- when Prism::OptionalLocationField -%> + <%- when Prism::Template::OptionalLocationField -%> pm_location_t *location = &cast-><%= field.name %>; if (location->start == NULL) { pm_buffer_append_string(output_buffer, " nil\n", 5); @@ -110,11 +119,11 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm pm_buffer_append_source(output_buffer, location->start, (size_t) (location->end - location->start), PM_BUFFER_ESCAPING_RUBY); pm_buffer_append_string(output_buffer, "\"\n", 2); } - <%- when Prism::UInt8Field -%> + <%- when Prism::Template::UInt8Field -%> pm_buffer_append_format(output_buffer, " %" PRIu8 "\n", cast-><%= field.name %>); - <%- when Prism::UInt32Field -%> + <%- when Prism::Template::UInt32Field -%> pm_buffer_append_format(output_buffer, " %" PRIu32 "\n", cast-><%= field.name %>); - <%- when Prism::FlagsField -%> + <%- when Prism::Template::FlagsField -%> bool found = false; <%- found = flags.find { |flag| flag.name == field.kind }.tap { |found| raise "Expected to find #{field.kind}" unless found } -%> <%- found.values.each do |value| -%> @@ -126,12 +135,12 @@ prettyprint_node(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm <%- end -%> if (!found) pm_buffer_append_string(output_buffer, " nil", 4); pm_buffer_append_byte(output_buffer, '\n'); - <%- when Prism::IntegerField -%> + <%- when Prism::Template::IntegerField -%> const pm_integer_t *integer = &cast-><%= field.name %>; pm_buffer_append_byte(output_buffer, ' '); pm_integer_string(output_buffer, integer); pm_buffer_append_byte(output_buffer, '\n'); - <%- when Prism::DoubleField -%> + <%- when Prism::Template::DoubleField -%> pm_buffer_append_format(output_buffer, " %f\n", cast-><%= field.name %>); <%- else -%> <%- raise -%> @@ -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 49fd5882ee54c6..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 @@ -52,10 +57,14 @@ pm_serialize_string(const pm_parser_t *parser, const pm_string_t *string, pm_buf static void pm_serialize_integer(const pm_integer_t *integer, pm_buffer_t *buffer) { pm_buffer_append_byte(buffer, integer->negative ? 1 : 0); - pm_buffer_append_varuint(buffer, pm_sizet_to_u32(integer->length + 1)); - - for (const pm_integer_word_t *node = &integer->head; node != NULL; node = node->next) { - pm_buffer_append_varuint(buffer, node->value); + if (integer->values == NULL) { + pm_buffer_append_varuint(buffer, pm_sizet_to_u32(1)); + pm_buffer_append_varuint(buffer, integer->value); + } else { + pm_buffer_append_varuint(buffer, pm_sizet_to_u32(integer->length)); + for (size_t i = 0; i < integer->length; i++) { + pm_buffer_append_varuint(buffer, integer->values[i]); + } } } @@ -82,35 +91,35 @@ pm_serialize_node(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) { <%- end -%> <%- node.fields.each do |field| -%> <%- case field -%> - <%- when Prism::NodeField -%> + <%- when Prism::Template::NodeField -%> pm_serialize_node(parser, (pm_node_t *)((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); - <%- when Prism::OptionalNodeField -%> + <%- when Prism::Template::OptionalNodeField -%> if (((pm_<%= node.human %>_t *)node)-><%= field.name %> == NULL) { pm_buffer_append_byte(buffer, 0); } else { pm_serialize_node(parser, (pm_node_t *)((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); } - <%- when Prism::StringField -%> + <%- when Prism::Template::StringField -%> pm_serialize_string(parser, &((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); - <%- when Prism::NodeListField -%> + <%- when Prism::Template::NodeListField -%> uint32_t <%= field.name %>_size = pm_sizet_to_u32(((pm_<%= node.human %>_t *)node)-><%= field.name %>.size); pm_buffer_append_varuint(buffer, <%= field.name %>_size); for (uint32_t index = 0; index < <%= field.name %>_size; index++) { pm_serialize_node(parser, (pm_node_t *) ((pm_<%= node.human %>_t *)node)-><%= field.name %>.nodes[index], buffer); } - <%- when Prism::ConstantField, Prism::OptionalConstantField -%> + <%- when Prism::Template::ConstantField, Prism::Template::OptionalConstantField -%> pm_buffer_append_varuint(buffer, pm_sizet_to_u32(((pm_<%= node.human %>_t *)node)-><%= field.name %>)); - <%- when Prism::ConstantListField -%> + <%- when Prism::Template::ConstantListField -%> uint32_t <%= field.name %>_size = pm_sizet_to_u32(((pm_<%= node.human %>_t *)node)-><%= field.name %>.size); pm_buffer_append_varuint(buffer, <%= field.name %>_size); for (uint32_t index = 0; index < <%= field.name %>_size; index++) { pm_buffer_append_varuint(buffer, pm_sizet_to_u32(((pm_<%= node.human %>_t *)node)-><%= field.name %>.ids[index])); } - <%- when Prism::LocationField -%> + <%- when Prism::Template::LocationField -%> <%- if field.should_be_serialized? -%> pm_serialize_location(parser, &((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); <%- end -%> - <%- when Prism::OptionalLocationField -%> + <%- when Prism::Template::OptionalLocationField -%> <%- if field.should_be_serialized? -%> if (((pm_<%= node.human %>_t *)node)-><%= field.name %>.start == NULL) { pm_buffer_append_byte(buffer, 0); @@ -119,15 +128,15 @@ pm_serialize_node(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) { pm_serialize_location(parser, &((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); } <%- end -%> - <%- when Prism::UInt8Field -%> + <%- when Prism::Template::UInt8Field -%> pm_buffer_append_byte(buffer, ((pm_<%= node.human %>_t *)node)-><%= field.name %>); - <%- when Prism::UInt32Field -%> + <%- when Prism::Template::UInt32Field -%> pm_buffer_append_varuint(buffer, ((pm_<%= node.human %>_t *)node)-><%= field.name %>); - <%- when Prism::FlagsField -%> + <%- when Prism::Template::FlagsField -%> pm_buffer_append_varuint(buffer, (uint32_t)(node->flags & ~PM_NODE_FLAG_COMMON_MASK)); - <%- when Prism::IntegerField -%> + <%- when Prism::Template::IntegerField -%> pm_serialize_integer(&((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); - <%- when Prism::DoubleField -%> + <%- when Prism::Template::DoubleField -%> pm_buffer_append_double(buffer, ((pm_<%= node.human %>_t *)node)-><%= field.name %>); <%- else -%> <%- raise -%> @@ -210,6 +219,9 @@ 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_varuint(buffer, (uint32_t) diagnostic->diag_id); + // serialize message size_t message_length = strlen(diagnostic->message); pm_buffer_append_varuint(buffer, pm_sizet_to_u32(message_length)); @@ -246,7 +258,7 @@ pm_serialize_metadata(pm_parser_t *parser, pm_buffer_t *buffer) { pm_serialize_encoding(parser->encoding, buffer); pm_buffer_append_varsint(buffer, parser->start_line); pm_serialize_newline_list(&parser->newline_list, buffer); -<%- unless Prism::SERIALIZE_ONLY_SEMANTICS_FIELDS -%> +<%- unless Prism::Template::SERIALIZE_ONLY_SEMANTICS_FIELDS -%> pm_serialize_comment_list(parser, &parser->comment_list, buffer); <%- end -%> pm_serialize_magic_comment_list(parser, &parser->magic_comment_list, buffer); @@ -387,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/templates/src/token_type.c.erb b/prism/templates/src/token_type.c.erb index 493d46f1880652..1aeecd72b24acb 100644 --- a/prism/templates/src/token_type.c.erb +++ b/prism/templates/src/token_type.c.erb @@ -208,7 +208,7 @@ pm_token_type_human(pm_token_type_t token_type) { case PM_TOKEN_KEYWORD_RESCUE: return "'rescue'"; case PM_TOKEN_KEYWORD_RESCUE_MODIFIER: - return "'rescue'"; + return "'rescue' modifier"; case PM_TOKEN_KEYWORD_RETRY: return "'retry'"; case PM_TOKEN_KEYWORD_RETURN: @@ -326,13 +326,13 @@ pm_token_type_human(pm_token_type_t token_type) { case PM_TOKEN_STAR_STAR_EQUAL: return "'**='"; case PM_TOKEN_STRING_BEGIN: - return "string beginning"; + return "string literal"; case PM_TOKEN_STRING_CONTENT: return "string content"; case PM_TOKEN_STRING_END: return "string ending"; case PM_TOKEN_SYMBOL_BEGIN: - return "symbol beginning"; + return "symbol literal"; case PM_TOKEN_TILDE: return "'~'"; case PM_TOKEN_UAMPERSAND: diff --git a/prism/templates/template.rb b/prism/templates/template.rb index b1bf50e2d807f2..6f79a853712bce 100755 --- a/prism/templates/template.rb +++ b/prism/templates/template.rb @@ -6,632 +6,657 @@ require "yaml" module Prism - SERIALIZE_ONLY_SEMANTICS_FIELDS = ENV.fetch("PRISM_SERIALIZE_ONLY_SEMANTICS_FIELDS", false) - CHECK_FIELD_KIND = ENV.fetch("CHECK_FIELD_KIND", false) - - JAVA_BACKEND = ENV["PRISM_JAVA_BACKEND"] || "truffleruby" - JAVA_STRING_TYPE = JAVA_BACKEND == "jruby" ? "org.jruby.RubySymbol" : "String" - - # This module contains methods for escaping characters in JavaDoc comments. - module JavaDoc - ESCAPES = { - "'" => "'", - "\"" => """, - "@" => "@", - "&" => "&", - "<" => "<", - ">" => ">" - }.freeze - - def self.escape(value) - value.gsub(/['&"<>@]/, ESCAPES) - end - end + module Template + SERIALIZE_ONLY_SEMANTICS_FIELDS = ENV.fetch("PRISM_SERIALIZE_ONLY_SEMANTICS_FIELDS", false) + CHECK_FIELD_KIND = ENV.fetch("CHECK_FIELD_KIND", false) - # A comment attached to a field or node. - class ConfigComment - attr_reader :value + JAVA_BACKEND = ENV["PRISM_JAVA_BACKEND"] || "truffleruby" + JAVA_STRING_TYPE = JAVA_BACKEND == "jruby" ? "org.jruby.RubySymbol" : "String" - def initialize(value) - @value = value - end + class Error + attr_reader :name - def each_line(&block) - value.each_line { |line| yield line.prepend(" ").rstrip } + def initialize(name) + @name = name + end end - def each_java_line(&block) - ConfigComment.new(JavaDoc.escape(value)).each_line(&block) + class Warning + attr_reader :name + + def initialize(name) + @name = name + end end - end - # This represents a field on a node. It contains all of the necessary - # information to template out the code for that field. - class Field - attr_reader :name, :comment, :options + # This module contains methods for escaping characters in JavaDoc comments. + module JavaDoc + ESCAPES = { + "'" => "'", + "\"" => """, + "@" => "@", + "&" => "&", + "<" => "<", + ">" => ">" + }.freeze - def initialize(name:, comment: nil, **options) - @name = name - @comment = comment - @options = options + def self.escape(value) + value.gsub(/['&"<>@]/, ESCAPES) + end end - def each_comment_line(&block) - ConfigComment.new(comment).each_line(&block) if comment - end + # A comment attached to a field or node. + class ConfigComment + attr_reader :value - def each_comment_java_line(&block) - ConfigComment.new(comment).each_java_line(&block) if comment - end + def initialize(value) + @value = value + end - def semantic_field? - true - end + def each_line(&block) + value.each_line { |line| yield line.prepend(" ").rstrip } + end - def should_be_serialized? - SERIALIZE_ONLY_SEMANTICS_FIELDS ? semantic_field? : true + def each_java_line(&block) + ConfigComment.new(JavaDoc.escape(value)).each_line(&block) + end end - end - # Some node fields can be specialized if they point to a specific kind of - # node and not just a generic node. - class NodeKindField < Field - def c_type - if specific_kind - "pm_#{specific_kind.gsub(/(?<=.)[A-Z]/, "_\\0").downcase}" - else - "pm_node" + # This represents a field on a node. It contains all of the necessary + # information to template out the code for that field. + class Field + attr_reader :name, :comment, :options + + def initialize(name:, comment: nil, **options) + @name = name + @comment = comment + @options = options end - end - def ruby_type - specific_kind || "Node" - end + def each_comment_line(&block) + ConfigComment.new(comment).each_line(&block) if comment + end - def java_type - specific_kind || "Node" - end + def each_comment_java_line(&block) + ConfigComment.new(comment).each_java_line(&block) if comment + end - def java_cast - if specific_kind - "(Nodes.#{options[:kind]}) " - else - "" + def semantic_field? + true end - end - def specific_kind - options[:kind] unless options[:kind].is_a?(Array) + def should_be_serialized? + SERIALIZE_ONLY_SEMANTICS_FIELDS ? semantic_field? : true + end end - def union_kind - options[:kind] if options[:kind].is_a?(Array) - end - end + # Some node fields can be specialized if they point to a specific kind of + # node and not just a generic node. + class NodeKindField < Field + def c_type + if specific_kind + "pm_#{specific_kind.gsub(/(?<=.)[A-Z]/, "_\\0").downcase}" + else + "pm_node" + end + end - # This represents a field on a node that is itself a node. We pass them as - # references and store them as references. - class NodeField < NodeKindField - def rbs_class - if specific_kind - specific_kind - elsif union_kind - union_kind.join(" | ") - else - "Prism::node" + def ruby_type + specific_kind || "Node" end - end - def rbi_class - if specific_kind - "Prism::#{specific_kind}" - elsif union_kind - "T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")})" - else - "Prism::Node" + def java_type + specific_kind || "Node" end - end - def check_field_kind - if union_kind - "[#{union_kind.join(', ')}].include?(#{name}.class)" - else - "#{name}.is_a?(#{ruby_type})" + def java_cast + if specific_kind + "(Nodes.#{options[:kind]}) " + else + "" + end end - end - end - # This represents a field on a node that is itself a node and can be - # optionally null. We pass them as references and store them as references. - class OptionalNodeField < NodeKindField - def rbs_class - if specific_kind - "#{specific_kind}?" - elsif union_kind - [*union_kind, "nil"].join(" | ") - else - "Prism::node?" + def specific_kind + options[:kind] unless options[:kind].is_a?(Array) end - end - def rbi_class - if specific_kind - "T.nilable(Prism::#{specific_kind})" - elsif union_kind - "T.nilable(T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")}))" - else - "T.nilable(Prism::Node)" + def union_kind + options[:kind] if options[:kind].is_a?(Array) end end - def check_field_kind - if union_kind - "[#{union_kind.join(', ')}, NilClass].include?(#{name}.class)" - else - "#{name}.nil? || #{name}.is_a?(#{ruby_type})" + # This represents a field on a node that is itself a node. We pass them as + # references and store them as references. + class NodeField < NodeKindField + def rbs_class + if specific_kind + specific_kind + elsif union_kind + union_kind.join(" | ") + else + "Prism::node" + end end - end - end - # This represents a field on a node that is a list of nodes. We pass them as - # references and store them directly on the struct. - class NodeListField < NodeKindField - def rbs_class - if specific_kind - "Array[#{specific_kind}]" - elsif union_kind - "Array[#{union_kind.join(" | ")}]" - else - "Array[Prism::node]" + def rbi_class + if specific_kind + "Prism::#{specific_kind}" + elsif union_kind + "T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")})" + else + "Prism::Node" + end end - end - def rbi_class - if specific_kind - "T::Array[Prism::#{specific_kind}]" - elsif union_kind - "T::Array[T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")})]" - else - "T::Array[Prism::Node]" + def check_field_kind + if union_kind + "[#{union_kind.join(', ')}].include?(#{name}.class)" + else + "#{name}.is_a?(#{ruby_type})" + end end end - def java_type - "#{super}[]" - end + # This represents a field on a node that is itself a node and can be + # optionally null. We pass them as references and store them as references. + class OptionalNodeField < NodeKindField + def rbs_class + if specific_kind + "#{specific_kind}?" + elsif union_kind + [*union_kind, "nil"].join(" | ") + else + "Prism::node?" + end + end - def check_field_kind - if union_kind - "#{name}.all? { |n| [#{union_kind.join(', ')}].include?(n.class) }" - else - "#{name}.all? { |n| n.is_a?(#{ruby_type}) }" + def rbi_class + if specific_kind + "T.nilable(Prism::#{specific_kind})" + elsif union_kind + "T.nilable(T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")}))" + else + "T.nilable(Prism::Node)" + end end - end - end - # This represents a field on a node that is the ID of a string interned - # through the parser's constant pool. - class ConstantField < Field - def rbs_class - "Symbol" + def check_field_kind + if union_kind + "[#{union_kind.join(', ')}, NilClass].include?(#{name}.class)" + else + "#{name}.nil? || #{name}.is_a?(#{ruby_type})" + end + end end - def rbi_class - "Symbol" - end + # This represents a field on a node that is a list of nodes. We pass them as + # references and store them directly on the struct. + class NodeListField < NodeKindField + def rbs_class + if specific_kind + "Array[#{specific_kind}]" + elsif union_kind + "Array[#{union_kind.join(" | ")}]" + else + "Array[Prism::node]" + end + end - def java_type - JAVA_STRING_TYPE - end - end + def rbi_class + if specific_kind + "T::Array[Prism::#{specific_kind}]" + elsif union_kind + "T::Array[T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")})]" + else + "T::Array[Prism::Node]" + end + end - # This represents a field on a node that is the ID of a string interned - # through the parser's constant pool and can be optionally null. - class OptionalConstantField < Field - def rbs_class - "Symbol?" - end + def java_type + "#{super}[]" + end - def rbi_class - "T.nilable(Symbol)" + def check_field_kind + if union_kind + "#{name}.all? { |n| [#{union_kind.join(', ')}].include?(n.class) }" + else + "#{name}.all? { |n| n.is_a?(#{ruby_type}) }" + end + end end - def java_type - JAVA_STRING_TYPE - end - end + # This represents a field on a node that is the ID of a string interned + # through the parser's constant pool. + class ConstantField < Field + def rbs_class + "Symbol" + end - # This represents a field on a node that is a list of IDs that are associated - # with strings interned through the parser's constant pool. - class ConstantListField < Field - def rbs_class - "Array[Symbol]" - end + def rbi_class + "Symbol" + end - def rbi_class - "T::Array[Symbol]" + def java_type + JAVA_STRING_TYPE + end end - def java_type - "#{JAVA_STRING_TYPE}[]" - end - end + # This represents a field on a node that is the ID of a string interned + # through the parser's constant pool and can be optionally null. + class OptionalConstantField < Field + def rbs_class + "Symbol?" + end - # This represents a field on a node that is a string. - class StringField < Field - def rbs_class - "String" - end + def rbi_class + "T.nilable(Symbol)" + end - def rbi_class - "String" + def java_type + JAVA_STRING_TYPE + end end - def java_type - "byte[]" - end - end + # This represents a field on a node that is a list of IDs that are associated + # with strings interned through the parser's constant pool. + class ConstantListField < Field + def rbs_class + "Array[Symbol]" + end - # This represents a field on a node that is a location. - class LocationField < Field - def semantic_field? - false - end + def rbi_class + "T::Array[Symbol]" + end - def rbs_class - "Location" + def java_type + "#{JAVA_STRING_TYPE}[]" + end end - def rbi_class - "Prism::Location" - end + # This represents a field on a node that is a string. + class StringField < Field + def rbs_class + "String" + end - def java_type - "Location" - end - end + def rbi_class + "String" + end - # This represents a field on a node that is a location that is optional. - class OptionalLocationField < Field - def semantic_field? - false + def java_type + "byte[]" + end end - def rbs_class - "Location?" - end + # This represents a field on a node that is a location. + class LocationField < Field + def semantic_field? + false + end - def rbi_class - "T.nilable(Prism::Location)" - end + def rbs_class + "Location" + end - def java_type - "Location" - end - end + def rbi_class + "Prism::Location" + end - # This represents an integer field. - class UInt8Field < Field - def rbs_class - "Integer" + def java_type + "Location" + end end - def rbi_class - "Integer" - end + # This represents a field on a node that is a location that is optional. + class OptionalLocationField < Field + def semantic_field? + false + end - def java_type - "int" - end - end + def rbs_class + "Location?" + end - # This represents an integer field. - class UInt32Field < Field - def rbs_class - "Integer" - end + def rbi_class + "T.nilable(Prism::Location)" + end - def rbi_class - "Integer" + def java_type + "Location" + end end - def java_type - "int" - end - end + # This represents an integer field. + class UInt8Field < Field + def rbs_class + "Integer" + end - # This represents a set of flags. It is very similar to the UInt32Field, but - # can be directly embedded into the flags field on the struct and provides - # convenient methods for checking if a flag is set. - class FlagsField < Field - def rbs_class - "Integer" - end + def rbi_class + "Integer" + end - def rbi_class - "Integer" + def java_type + "int" + end end - def java_type - "short" - end + # This represents an integer field. + class UInt32Field < Field + def rbs_class + "Integer" + end - def kind - options.fetch(:kind) - end - end + def rbi_class + "Integer" + end - # This represents an arbitrarily-sized integer. When it gets to Ruby it will - # be an Integer. - class IntegerField < Field - def rbs_class - "Integer" + def java_type + "int" + end end - def rbi_class - "Integer" - end + # This represents a set of flags. It is very similar to the UInt32Field, but + # can be directly embedded into the flags field on the struct and provides + # convenient methods for checking if a flag is set. + class FlagsField < Field + def rbs_class + "Integer" + end - def java_type - "Object" - end - end + def rbi_class + "Integer" + end - # This represents a double-precision floating point number. When it gets to - # Ruby it will be a Float. - class DoubleField < Field - def rbs_class - "Float" - end + def java_type + "short" + end - def rbi_class - "Float" + def kind + options.fetch(:kind) + end end - def java_type - "double" + # This represents an arbitrarily-sized integer. When it gets to Ruby it will + # be an Integer. + class IntegerField < Field + def rbs_class + "Integer" + end + + def rbi_class + "Integer" + end + + def java_type + "Object" + end end - end - # This class represents a node in the tree, configured by the config.yml file - # in YAML format. It contains information about the name of the node and the - # various child nodes it contains. - class NodeType - attr_reader :name, :type, :human, :fields, :newline, :comment + # This represents a double-precision floating point number. When it gets to + # Ruby it will be a Float. + class DoubleField < Field + def rbs_class + "Float" + end - def initialize(config) - @name = config.fetch("name") + def rbi_class + "Float" + end - type = @name.gsub(/(?<=.)[A-Z]/, "_\\0") - @type = "PM_#{type.upcase}" - @human = type.downcase + def java_type + "double" + end + end - @fields = - config.fetch("fields", []).map do |field| - type = field_type_for(field.fetch("type")) + # This class represents a node in the tree, configured by the config.yml file + # in YAML format. It contains information about the name of the node and the + # various child nodes it contains. + class NodeType + attr_reader :name, :type, :human, :fields, :newline, :comment - options = field.transform_keys(&:to_sym) - options.delete(:type) + def initialize(config) + @name = config.fetch("name") - # If/when we have documentation on every field, this should be changed - # to use fetch instead of delete. - comment = options.delete(:comment) + type = @name.gsub(/(?<=.)[A-Z]/, "_\\0") + @type = "PM_#{type.upcase}" + @human = type.downcase - type.new(comment: comment, **options) - end + @fields = + config.fetch("fields", []).map do |field| + type = field_type_for(field.fetch("type")) - @newline = config.fetch("newline", true) - @comment = config.fetch("comment") - end + options = field.transform_keys(&:to_sym) + options.delete(:type) - def each_comment_line(&block) - ConfigComment.new(comment).each_line(&block) - end + # If/when we have documentation on every field, this should be changed + # to use fetch instead of delete. + comment = options.delete(:comment) - def each_comment_java_line(&block) - ConfigComment.new(comment).each_java_line(&block) - end + type.new(comment: comment, **options) + end - def semantic_fields - @semantic_fields ||= @fields.select(&:semantic_field?) - end + @newline = config.fetch("newline", true) + @comment = config.fetch("comment") + end - # Should emit serialized length of node so implementations can skip - # the node to enable lazy parsing. - def needs_serialized_length? - name == "DefNode" - end + def each_comment_line(&block) + ConfigComment.new(comment).each_line(&block) + end - private - - def field_type_for(name) - case name - when "node" then NodeField - when "node?" then OptionalNodeField - when "node[]" then NodeListField - when "string" then StringField - when "constant" then ConstantField - when "constant?" then OptionalConstantField - when "constant[]" then ConstantListField - when "location" then LocationField - when "location?" then OptionalLocationField - when "uint8" then UInt8Field - when "uint32" then UInt32Field - when "flags" then FlagsField - when "integer" then IntegerField - when "double" then DoubleField - else raise("Unknown field type: #{name.inspect}") + def each_comment_java_line(&block) + ConfigComment.new(comment).each_java_line(&block) end - end - end - # This represents a token in the lexer. - class Token - attr_reader :name, :value, :comment + def semantic_fields + @semantic_fields ||= @fields.select(&:semantic_field?) + end + + # Should emit serialized length of node so implementations can skip + # the node to enable lazy parsing. + def needs_serialized_length? + name == "DefNode" + end - def initialize(config) - @name = config.fetch("name") - @value = config["value"] - @comment = config.fetch("comment") + private + + def field_type_for(name) + case name + when "node" then NodeField + when "node?" then OptionalNodeField + when "node[]" then NodeListField + when "string" then StringField + when "constant" then ConstantField + when "constant?" then OptionalConstantField + when "constant[]" then ConstantListField + when "location" then LocationField + when "location?" then OptionalLocationField + when "uint8" then UInt8Field + when "uint32" then UInt32Field + when "flags" then FlagsField + when "integer" then IntegerField + when "double" then DoubleField + else raise("Unknown field type: #{name.inspect}") + end + end end - end - # Represents a set of flags that should be internally represented with an enum. - class Flags - # Represents an individual flag within a set of flags. - class Flag - attr_reader :name, :camelcase, :comment + # This represents a token in the lexer. + class Token + attr_reader :name, :value, :comment def initialize(config) @name = config.fetch("name") - @camelcase = @name.split("_").map(&:capitalize).join + @value = config["value"] @comment = config.fetch("comment") end end - attr_reader :name, :human, :values, :comment + # Represents a set of flags that should be internally represented with an enum. + class Flags + # Represents an individual flag within a set of flags. + class Flag + attr_reader :name, :camelcase, :comment - def initialize(config) - @name = config.fetch("name") - @human = @name.gsub(/(?<=.)[A-Z]/, "_\\0").downcase - @values = config.fetch("values").map { |flag| Flag.new(flag) } - @comment = config.fetch("comment") - end - end - - class << self - # This templates out a file using ERB with the given locals. The locals are - # derived from the config.yml file. - def template(name, write_to: nil) - filepath = "templates/#{name}.erb" - template = File.expand_path("../#{filepath}", __dir__) - - erb = read_template(template) - extension = File.extname(filepath.gsub(".erb", "")) - - heading = case extension - when ".rb" - <<~HEADING - # frozen_string_literal: true - - =begin - This file is generated by the templates/template.rb script and should not be - modified manually. See #{filepath} - if you are looking to modify the template - =end - - HEADING - when ".rbs" - <<~HEADING - # This file is generated by the templates/template.rb script and should not be - # modified manually. See #{filepath} - # if you are looking to modify the template - - HEADING - when ".rbi" - <<~HEADING - # typed: strict - - =begin - This file is generated by the templates/template.rb script and should not be - modified manually. See #{filepath} - if you are looking to modify the template - =end - - HEADING - else - <<~HEADING - /******************************************************************************/ - /* This file is generated by the templates/template.rb script and should not */ - /* be modified manually. See */ - /* #{filepath + " " * (74 - filepath.size) } */ - /* if you are looking to modify the */ - /* template */ - /******************************************************************************/ - - HEADING - end - - write_to ||= File.expand_path("../#{name}", __dir__) - contents = heading + erb.result_with_hash(locals) - - if (extension == ".c" || extension == ".h") && !contents.ascii_only? - # Enforce that we only have ASCII characters here. This is necessary - # for non-UTF-8 locales that only allow ASCII characters in C source - # files. - contents.each_line.with_index(1) do |line, line_number| - raise "Non-ASCII character on line #{line_number} of #{write_to}" unless line.ascii_only? + def initialize(config) + @name = config.fetch("name") + @camelcase = @name.split("_").map(&:capitalize).join + @comment = config.fetch("comment") end end - FileUtils.mkdir_p(File.dirname(write_to)) - File.write(write_to, contents) + attr_reader :name, :human, :values, :comment + + def initialize(config) + @name = config.fetch("name") + @human = @name.gsub(/(?<=.)[A-Z]/, "_\\0").downcase + @values = config.fetch("values").map { |flag| Flag.new(flag) } + @comment = config.fetch("comment") + end end - private + class << self + # This templates out a file using ERB with the given locals. The locals are + # derived from the config.yml file. + def render(name, write_to: nil) + filepath = "templates/#{name}.erb" + template = File.expand_path("../#{filepath}", __dir__) + + erb = read_template(template) + extension = File.extname(filepath.gsub(".erb", "")) + + heading = + case extension + when ".rb" + <<~HEADING + # frozen_string_literal: true + + =begin + This file is generated by the templates/template.rb script and should not be + modified manually. See #{filepath} + if you are looking to modify the template + =end + + HEADING + when ".rbs" + <<~HEADING + # This file is generated by the templates/template.rb script and should not be + # modified manually. See #{filepath} + # if you are looking to modify the template + + HEADING + when ".rbi" + <<~HEADING + # typed: strict + + =begin + This file is generated by the templates/template.rb script and should not be + modified manually. See #{filepath} + if you are looking to modify the template + =end + + HEADING + else + <<~HEADING + /******************************************************************************/ + /* This file is generated by the templates/template.rb script and should not */ + /* be modified manually. See */ + /* #{filepath + " " * (74 - filepath.size) } */ + /* if you are looking to modify the */ + /* template */ + /******************************************************************************/ + + HEADING + end + + write_to ||= File.expand_path("../#{name}", __dir__) + contents = heading + erb.result_with_hash(locals) + + if (extension == ".c" || extension == ".h") && !contents.ascii_only? + # Enforce that we only have ASCII characters here. This is necessary + # for non-UTF-8 locales that only allow ASCII characters in C source + # files. + contents.each_line.with_index(1) do |line, line_number| + raise "Non-ASCII character on line #{line_number} of #{write_to}" unless line.ascii_only? + end + end + + FileUtils.mkdir_p(File.dirname(write_to)) + File.write(write_to, contents) + end - def read_template(filepath) - template = File.read(filepath, encoding: Encoding::UTF_8) - erb = erb(template) - erb.filename = filepath - erb - end + private - def erb(template) - ERB.new(template, trim_mode: "-") - end + def read_template(filepath) + template = File.read(filepath, encoding: Encoding::UTF_8) + erb = erb(template) + erb.filename = filepath + erb + end - def locals - @locals ||= - begin - config = YAML.load_file(File.expand_path("../config.yml", __dir__)) + def erb(template) + ERB.new(template, trim_mode: "-") + end - { - nodes: config.fetch("nodes").map { |node| NodeType.new(node) }.sort_by(&:name), - tokens: config.fetch("tokens").map { |token| Token.new(token) }, - flags: config.fetch("flags").map { |flags| Flags.new(flags) } - } - end + def locals + @locals ||= + begin + config = YAML.load_file(File.expand_path("../config.yml", __dir__)) + + { + errors: config.fetch("errors").map { |name| Error.new(name) }, + warnings: config.fetch("warnings").map { |name| Warning.new(name) }, + nodes: config.fetch("nodes").map { |node| NodeType.new(node) }.sort_by(&:name), + tokens: config.fetch("tokens").map { |token| Token.new(token) }, + flags: config.fetch("flags").map { |flags| Flags.new(flags) } + } + end + end end - end - TEMPLATES = [ - "ext/prism/api_node.c", - "include/prism/ast.h", - "javascript/src/deserialize.js", - "javascript/src/nodes.js", - "javascript/src/visitor.js", - "java/org/prism/Loader.java", - "java/org/prism/Nodes.java", - "java/org/prism/AbstractNodeVisitor.java", - "lib/prism/compiler.rb", - "lib/prism/dispatcher.rb", - "lib/prism/dot_visitor.rb", - "lib/prism/dsl.rb", - "lib/prism/mutation_compiler.rb", - "lib/prism/node.rb", - "lib/prism/serialize.rb", - "lib/prism/visitor.rb", - "src/node.c", - "src/prettyprint.c", - "src/serialize.c", - "src/token_type.c", - "rbi/prism/node.rbi", - "rbi/prism/visitor.rbi", - "sig/prism.rbs", - "sig/prism/dsl.rbs", - "sig/prism/mutation_compiler.rbs", - "sig/prism/node.rbs", - "sig/prism/visitor.rbs", - "sig/prism/_private/dot_visitor.rbs" - ] + TEMPLATES = [ + "ext/prism/api_node.c", + "include/prism/ast.h", + "include/prism/diagnostic.h", + "javascript/src/deserialize.js", + "javascript/src/nodes.js", + "javascript/src/visitor.js", + "java/org/prism/Loader.java", + "java/org/prism/Nodes.java", + "java/org/prism/AbstractNodeVisitor.java", + "lib/prism/compiler.rb", + "lib/prism/dispatcher.rb", + "lib/prism/dot_visitor.rb", + "lib/prism/dsl.rb", + "lib/prism/inspect_visitor.rb", + "lib/prism/mutation_compiler.rb", + "lib/prism/node.rb", + "lib/prism/reflection.rb", + "lib/prism/serialize.rb", + "lib/prism/visitor.rb", + "src/diagnostic.c", + "src/node.c", + "src/prettyprint.c", + "src/serialize.c", + "src/token_type.c", + "rbi/prism/node.rbi", + "rbi/prism/visitor.rbi", + "sig/prism.rbs", + "sig/prism/dsl.rbs", + "sig/prism/mutation_compiler.rbs", + "sig/prism/node.rbs", + "sig/prism/visitor.rbs", + "sig/prism/_private/dot_visitor.rbs" + ] + end end if __FILE__ == $0 if ARGV.empty? - Prism::TEMPLATES.each { |filepath| Prism.template(filepath) } + Prism::Template::TEMPLATES.each { |filepath| Prism::Template.render(filepath) } else # ruby/ruby name, write_to = ARGV - Prism.template(name, write_to: write_to) + Prism::Template.render(name, write_to: write_to) end end 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 c03b930ad3f3d7..e523bae90b13da 100644 --- a/prism/util/pm_integer.c +++ b/prism/util/pm_integer.c @@ -1,143 +1,469 @@ #include "prism/util/pm_integer.h" /** - * Create a new node for an integer in the linked list. + * Pull out the length and values from the integer, regardless of the form in + * which the length/values are stored. */ -static pm_integer_word_t * -pm_integer_node_create(pm_integer_t *integer, uint32_t value) { - integer->length++; +#define INTEGER_EXTRACT(integer, length_variable, values_variable) \ + if ((integer)->values == NULL) { \ + length_variable = 1; \ + values_variable = &(integer)->value; \ + } else { \ + length_variable = (integer)->length; \ + values_variable = (integer)->values; \ + } - pm_integer_word_t *node = xmalloc(sizeof(pm_integer_word_t)); - if (node == NULL) return NULL; +/** + * Adds two positive pm_integer_t with the given base. + * Return pm_integer_t with values allocated. Not normalized. + */ +static void +big_add(pm_integer_t *destination, pm_integer_t *left, pm_integer_t *right, uint64_t base) { + size_t left_length; + uint32_t *left_values; + INTEGER_EXTRACT(left, left_length, left_values) + + size_t right_length; + uint32_t *right_values; + INTEGER_EXTRACT(right, right_length, right_values) + + size_t length = left_length < right_length ? right_length : left_length; + uint32_t *values = (uint32_t *) xmalloc(sizeof(uint32_t) * (length + 1)); + if (values == NULL) return; + + uint64_t carry = 0; + for (size_t index = 0; index < length; index++) { + uint64_t sum = carry + (index < left_length ? left_values[index] : 0) + (index < right_length ? right_values[index] : 0); + values[index] = (uint32_t) (sum % base); + carry = sum / base; + } - *node = (pm_integer_word_t) { .next = NULL, .value = value }; - return node; + if (carry > 0) { + values[length] = (uint32_t) carry; + length++; + } + + *destination = (pm_integer_t) { 0, length, values, false }; } /** - * Copy one integer onto another. + * Internal use for karatsuba_multiply. Calculates `a - b - c` with the given + * base. Assume a, b, c, a - b - c all to be poitive. + * Return pm_integer_t with values allocated. Not normalized. */ static void -pm_integer_copy(pm_integer_t *dest, const pm_integer_t *src) { - dest->negative = src->negative; - dest->length = 0; +big_sub2(pm_integer_t *destination, pm_integer_t *a, pm_integer_t *b, pm_integer_t *c, uint64_t base) { + size_t a_length; + uint32_t *a_values; + INTEGER_EXTRACT(a, a_length, a_values) + + size_t b_length; + uint32_t *b_values; + INTEGER_EXTRACT(b, b_length, b_values) + + size_t c_length; + uint32_t *c_values; + INTEGER_EXTRACT(c, c_length, c_values) + + uint32_t *values = (uint32_t*) xmalloc(sizeof(uint32_t) * a_length); + int64_t carry = 0; + + for (size_t index = 0; index < a_length; index++) { + int64_t sub = ( + carry + + a_values[index] - + (index < b_length ? b_values[index] : 0) - + (index < c_length ? c_values[index] : 0) + ); + + if (sub >= 0) { + values[index] = (uint32_t) sub; + carry = 0; + } else { + sub += 2 * (int64_t) base; + values[index] = (uint32_t) ((uint64_t) sub % base); + carry = sub / (int64_t) base - 2; + } + } - dest->head.value = src->head.value; - dest->head.next = NULL; + while (a_length > 1 && values[a_length - 1] == 0) a_length--; + *destination = (pm_integer_t) { 0, a_length, values, false }; +} - pm_integer_word_t *dest_current = &dest->head; - const pm_integer_word_t *src_current = src->head.next; +/** + * Multiply two positive integers with the given base using karatsuba algorithm. + * Return pm_integer_t with values allocated. Not normalized. + */ +static void +karatsuba_multiply(pm_integer_t *destination, pm_integer_t *left, pm_integer_t *right, uint64_t base) { + size_t left_length; + uint32_t *left_values; + INTEGER_EXTRACT(left, left_length, left_values) + + size_t right_length; + uint32_t *right_values; + INTEGER_EXTRACT(right, right_length, right_values) + + if (left_length > right_length) { + size_t temporary_length = left_length; + left_length = right_length; + right_length = temporary_length; + + uint32_t *temporary_values = left_values; + left_values = right_values; + right_values = temporary_values; + } - while (src_current != NULL) { - dest_current->next = pm_integer_node_create(dest, src_current->value); - if (dest_current->next == NULL) return; + if (left_length <= 10) { + size_t length = left_length + right_length; + uint32_t *values = (uint32_t *) xcalloc(length, sizeof(uint32_t)); + if (values == NULL) return; + + for (size_t left_index = 0; left_index < left_length; left_index++) { + uint32_t carry = 0; + for (size_t right_index = 0; right_index < right_length; right_index++) { + uint64_t product = (uint64_t) left_values[left_index] * right_values[right_index] + values[left_index + right_index] + carry; + values[left_index + right_index] = (uint32_t) (product % base); + carry = (uint32_t) (product / base); + } + values[left_index + right_length] = carry; + } - dest_current = dest_current->next; - src_current = src_current->next; + while (length > 1 && values[length - 1] == 0) length--; + *destination = (pm_integer_t) { 0, length, values, false }; + return; } - dest_current->next = NULL; + if (left_length * 2 <= right_length) { + uint32_t *values = (uint32_t *) xcalloc(left_length + right_length, sizeof(uint32_t)); + + for (size_t start_offset = 0; start_offset < right_length; start_offset += left_length) { + size_t end_offset = start_offset + left_length; + if (end_offset > right_length) end_offset = right_length; + + pm_integer_t sliced_left = { + .value = 0, + .length = left_length, + .values = left_values, + .negative = false + }; + + pm_integer_t sliced_right = { + .value = 0, + .length = end_offset - start_offset, + .values = right_values + start_offset, + .negative = false + }; + + pm_integer_t product; + karatsuba_multiply(&product, &sliced_left, &sliced_right, base); + + uint32_t carry = 0; + for (size_t index = 0; index < product.length; index++) { + uint64_t sum = (uint64_t) values[start_offset + index] + product.values[index] + carry; + values[start_offset + index] = (uint32_t) (sum % base); + carry = (uint32_t) (sum / base); + } + + if (carry > 0) values[start_offset + product.length] += carry; + pm_integer_free(&product); + } + + *destination = (pm_integer_t) { 0, left_length + right_length, values, false }; + return; + } + + size_t half = left_length / 2; + pm_integer_t x0 = { 0, half, left_values, false }; + pm_integer_t x1 = { 0, left_length - half, left_values + half, false }; + pm_integer_t y0 = { 0, half, right_values, false }; + pm_integer_t y1 = { 0, right_length - half, right_values + half, false }; + + pm_integer_t z0 = { 0 }; + karatsuba_multiply(&z0, &x0, &y0, base); + + pm_integer_t z2 = { 0 }; + karatsuba_multiply(&z2, &x1, &y1, base); + + // For simplicity to avoid considering negative values, + // use `z1 = (x0 + x1) * (y0 + y1) - z0 - z2` instead of original karatsuba algorithm. + pm_integer_t x01 = { 0 }; + big_add(&x01, &x0, &x1, base); + + pm_integer_t y01 = { 0 }; + big_add(&y01, &y0, &y1, base); + + pm_integer_t xy = { 0 }; + karatsuba_multiply(&xy, &x01, &y01, base); + + pm_integer_t z1; + big_sub2(&z1, &xy, &z0, &z2, base); + + size_t length = left_length + right_length; + uint32_t *values = (uint32_t*) xcalloc(length, sizeof(uint32_t)); + + assert(z0.values != NULL); + memcpy(values, z0.values, sizeof(uint32_t) * z0.length); + + assert(z2.values != NULL); + memcpy(values + 2 * half, z2.values, sizeof(uint32_t) * z2.length); + + uint32_t carry = 0; + for(size_t index = 0; index < z1.length; index++) { + uint64_t sum = (uint64_t) carry + values[index + half] + z1.values[index]; + values[index + half] = (uint32_t) (sum % base); + carry = (uint32_t) (sum / base); + } + + for(size_t index = half + z1.length; carry > 0; index++) { + uint64_t sum = (uint64_t) carry + values[index]; + values[index] = (uint32_t) (sum % base); + carry = (uint32_t) (sum / base); + } + + while (length > 1 && values[length - 1] == 0) length--; + pm_integer_free(&z0); + pm_integer_free(&z1); + pm_integer_free(&z2); + pm_integer_free(&x01); + pm_integer_free(&y01); + pm_integer_free(&xy); + + *destination = (pm_integer_t) { 0, length, values, false }; +} + +/** + * 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 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 1x + -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, 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 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 9x + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Ax + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Bx + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Cx + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Dx + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Ex + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Fx +}; + +/** + * Return the value of a hexadecimal digit in a uint8_t. + */ +static uint8_t +pm_integer_parse_digit(const uint8_t character) { + int8_t value = pm_integer_parse_digit_values[character]; + assert(value != -1 && "invalid digit"); + + return (uint8_t) value; } /** - * Add a 32-bit integer to an integer. + * Create a pm_integer_t from uint64_t with the given base. It is assumed that + * the memory for the pm_integer_t pointer has been zeroed. */ static void -pm_integer_add(pm_integer_t *integer, uint32_t addend) { - uint32_t carry = addend; - pm_integer_word_t *current = &integer->head; - - while (carry > 0) { - uint64_t result = (uint64_t) current->value + carry; - carry = (uint32_t) (result >> 32); - current->value = (uint32_t) result; - - if (carry > 0) { - if (current->next == NULL) { - current->next = pm_integer_node_create(integer, carry); - break; - } +pm_integer_from_uint64(pm_integer_t *integer, uint64_t value, uint64_t base) { + if (value < base) { + integer->value = (uint32_t) value; + return; + } - current = current->next; - } + size_t length = 0; + uint64_t length_value = value; + while (length_value > 0) { + length++; + length_value /= base; + } + + uint32_t *values = (uint32_t *) xmalloc(sizeof(uint32_t) * length); + if (values == NULL) return; + + for (size_t value_index = 0; value_index < length; value_index++) { + values[value_index] = (uint32_t) (value % base); + value /= base; } + + integer->length = length; + integer->values = values; } /** - * Multiple an integer by a 32-bit integer. In practice, the multiplier is the - * base of the integer, so this is 2, 8, 10, or 16. + * Normalize pm_integer_t. + * Heading zero values will be removed. If the integer fits into uint32_t, + * values is set to NULL, length is set to 0, and value field will be used. */ static void -pm_integer_multiply(pm_integer_t *integer, uint32_t multiplier) { - uint32_t carry = 0; +pm_integer_normalize(pm_integer_t *integer) { + if (integer->values == NULL) { + return; + } - for (pm_integer_word_t *current = &integer->head; current != NULL; current = current->next) { - uint64_t result = (uint64_t) current->value * multiplier + carry; - carry = (uint32_t) (result >> 32); - current->value = (uint32_t) result; + while (integer->length > 1 && integer->values[integer->length - 1] == 0) { + integer->length--; + } - if (carry > 0 && current->next == NULL) { - current->next = pm_integer_node_create(integer, carry); - break; - } + if (integer->length > 1) { + return; } + + uint32_t value = integer->values[0]; + bool negative = integer->negative && value != 0; + + pm_integer_free(integer); + *integer = (pm_integer_t) { .value = value, .length = 0, .values = NULL, .negative = negative }; } /** - * Divide an individual word by a 32-bit integer. This will recursively divide - * any subsequent nodes in the linked list. + * Convert base of the integer. + * In practice, it converts 10**9 to 1<<32 or 1<<32 to 10**9. */ -static uint32_t -pm_integer_divide_word(pm_integer_t *integer, pm_integer_word_t *word, uint32_t dividend) { - uint32_t remainder = 0; - if (word->next != NULL) { - remainder = pm_integer_divide_word(integer, word->next, dividend); - - if (integer->length > 0 && word->next->value == 0) { - xfree(word->next); - word->next = NULL; - integer->length--; +static void +pm_integer_convert_base(pm_integer_t *destination, const pm_integer_t *source, uint64_t base_from, uint64_t base_to) { + size_t source_length; + const uint32_t *source_values; + INTEGER_EXTRACT(source, source_length, source_values) + + size_t bigints_length = (source_length + 1) / 2; + assert(bigints_length > 0); + + pm_integer_t *bigints = (pm_integer_t *) xcalloc(bigints_length, sizeof(pm_integer_t)); + if (bigints == NULL) return; + + for (size_t index = 0; index < source_length; index += 2) { + uint64_t value = source_values[index] + base_from * (index + 1 < source_length ? source_values[index + 1] : 0); + pm_integer_from_uint64(&bigints[index / 2], value, base_to); + } + + pm_integer_t base = { 0 }; + pm_integer_from_uint64(&base, base_from, base_to); + + while (bigints_length > 1) { + pm_integer_t next_base; + karatsuba_multiply(&next_base, &base, &base, base_to); + + pm_integer_free(&base); + base = next_base; + + size_t next_length = (bigints_length + 1) / 2; + pm_integer_t *next_bigints = (pm_integer_t *) xcalloc(next_length, sizeof(pm_integer_t)); + + for (size_t bigints_index = 0; bigints_index < bigints_length; bigints_index += 2) { + if (bigints_index + 1 == bigints_length) { + next_bigints[bigints_index / 2] = bigints[bigints_index]; + } else { + pm_integer_t multiplied = { 0 }; + karatsuba_multiply(&multiplied, &base, &bigints[bigints_index + 1], base_to); + + big_add(&next_bigints[bigints_index / 2], &bigints[bigints_index], &multiplied, base_to); + pm_integer_free(&bigints[bigints_index]); + pm_integer_free(&bigints[bigints_index + 1]); + pm_integer_free(&multiplied); + } } + + xfree(bigints); + bigints = next_bigints; + bigints_length = next_length; } - uint64_t value = ((uint64_t) remainder << 32) | word->value; - word->value = (uint32_t) (value / dividend); - return (uint32_t) (value % dividend); + *destination = bigints[0]; + destination->negative = source->negative; + pm_integer_normalize(destination); + + xfree(bigints); + pm_integer_free(&base); } +#undef INTEGER_EXTRACT + /** - * Divide an integer by a 32-bit integer. In practice, this is only 10 so that - * we can format it as a string. It returns the remainder of the division. + * Convert digits to integer with the given power-of-two base. */ -static uint32_t -pm_integer_divide(pm_integer_t *integer, uint32_t dividend) { - return pm_integer_divide_word(integer, &integer->head, dividend); +static void +pm_integer_parse_powof2(pm_integer_t *integer, uint32_t base, const uint8_t *digits, size_t digits_length) { + size_t bit = 1; + while (base > (uint32_t) (1 << bit)) bit++; + + size_t length = (digits_length * bit + 31) / 32; + uint32_t *values = (uint32_t *) xcalloc(length, sizeof(uint32_t)); + + for (size_t digit_index = 0; digit_index < digits_length; digit_index++) { + size_t bit_position = bit * (digits_length - digit_index - 1); + uint32_t value = digits[digit_index]; + + size_t index = bit_position / 32; + size_t shift = bit_position % 32; + + values[index] |= value << shift; + if (32 - shift < bit) values[index + 1] |= value >> (32 - shift); + } + + while (length > 1 && values[length - 1] == 0) length--; + *integer = (pm_integer_t) { .value = 0, .length = length, .values = values, .negative = false }; + pm_integer_normalize(integer); } /** - * Return the value of a digit in a uint32_t. + * Convert decimal digits to pm_integer_t. */ -static uint32_t -pm_integer_parse_digit(const uint8_t character) { - switch (character) { - case '0': return 0; - case '1': return 1; - case '2': return 2; - case '3': return 3; - case '4': return 4; - case '5': return 5; - case '6': return 6; - case '7': return 7; - case '8': return 8; - case '9': return 9; - case 'a': case 'A': return 10; - case 'b': case 'B': return 11; - case 'c': case 'C': return 12; - case 'd': case 'D': return 13; - case 'e': case 'E': return 14; - case 'f': case 'F': return 15; - default: assert(false && "unreachable"); return 0; +static void +pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t digits_length) { + const size_t batch = 9; + size_t length = (digits_length + batch - 1) / batch; + + uint32_t *values = (uint32_t *) xcalloc(length, sizeof(uint32_t)); + uint32_t value = 0; + + for (size_t digits_index = 0; digits_index < digits_length; digits_index++) { + value = value * 10 + digits[digits_index]; + + size_t reverse_index = digits_length - digits_index - 1; + if (reverse_index % batch == 0) { + values[reverse_index / batch] = value; + value = 0; + } } + + // Convert base from 10**9 to 1<<32. + pm_integer_convert_base(integer, &((pm_integer_t) { .value = 0, .length = length, .values = values, .negative = false }), 1000000000, ((uint64_t) 1 << 32)); + xfree(values); +} + +/** + * Parse a large integer from a string that does not fit into uint32_t. + */ +static void +pm_integer_parse_big(pm_integer_t *integer, uint32_t multiplier, const uint8_t *start, const uint8_t *end) { + // Allocate an array to store digits. + uint8_t *digits = xmalloc(sizeof(uint8_t) * (size_t) (end - start)); + size_t digits_length = 0; + + for (; start < end; start++) { + if (*start == '_') continue; + digits[digits_length++] = pm_integer_parse_digit(*start); + } + + // Construct pm_integer_t from the digits. + if (multiplier == 10) { + pm_integer_parse_decimal(integer, digits, digits_length); + } else { + pm_integer_parse_powof2(integer, multiplier, digits, digits_length); + } + + xfree(digits); } /** @@ -189,15 +515,22 @@ pm_integer_parse(pm_integer_t *integer, pm_integer_base_t base, const uint8_t *s // invalid integer. If this is the case, we'll just return 0. if (start >= end) return; - // Add the first digit to the integer. - pm_integer_add(integer, pm_integer_parse_digit(*start++)); + const uint8_t *cursor = start; + uint64_t value = (uint64_t) pm_integer_parse_digit(*cursor++); - // Add the subsequent digits to the integer. - for (; start < end; start++) { - if (*start == '_') continue; - pm_integer_multiply(integer, multiplier); - pm_integer_add(integer, pm_integer_parse_digit(*start)); + for (; cursor < end; cursor++) { + if (*cursor == '_') continue; + value = value * multiplier + (uint64_t) pm_integer_parse_digit(*cursor); + + if (value > UINT32_MAX) { + // If the integer is too large to fit into a single uint32_t, then + // we'll parse it as a big integer. + pm_integer_parse_big(integer, multiplier, start, end); + return; + } } + + integer->value = (uint32_t) value; } /** @@ -205,7 +538,7 @@ pm_integer_parse(pm_integer_t *integer, pm_integer_base_t base, const uint8_t *s */ size_t pm_integer_memsize(const pm_integer_t *integer) { - return sizeof(pm_integer_t) + integer->length * sizeof(pm_integer_word_t); + return sizeof(pm_integer_t) + integer->length * sizeof(uint32_t); } /** @@ -218,16 +551,22 @@ pm_integer_compare(const pm_integer_t *left, const pm_integer_t *right) { if (left->negative != right->negative) return left->negative ? -1 : 1; int negative = left->negative ? -1 : 1; - if (left->length < right->length) return -1 * negative; - if (left->length > right->length) return 1 * negative; + if (left->values == NULL && right->values == NULL) { + if (left->value < right->value) return -1 * negative; + if (left->value > right->value) return 1 * negative; + return 0; + } + + if (left->values == NULL || left->length < right->length) return -1 * negative; + if (right->values == NULL || left->length > right->length) return 1 * negative; - for ( - const pm_integer_word_t *left_word = &left->head, *right_word = &right->head; - left_word != NULL && right_word != NULL; - left_word = left_word->next, right_word = right_word->next - ) { - if (left_word->value < right_word->value) return -1 * negative; - if (left_word->value > right_word->value) return 1 * negative; + for (size_t index = 0; index < left->length; index++) { + size_t value_index = left->length - index - 1; + uint32_t left_value = left->values[value_index]; + uint32_t right_value = right->values[value_index]; + + if (left_value < right_value) return -1 * negative; + if (left_value > right_value) return 1 * negative; } return 0; @@ -242,65 +581,62 @@ pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) { pm_buffer_append_byte(buffer, '-'); } - switch (integer->length) { - case 0: { - const uint32_t value = integer->head.value; - pm_buffer_append_format(buffer, "%" PRIu32, value); - return; - } - case 1: { - const uint64_t value = ((uint64_t) integer->head.value) | (((uint64_t) integer->head.next->value) << 32); - pm_buffer_append_format(buffer, "%" PRIu64, value); - return; - } - default: { - // First, allocate a buffer that we'll copy the decimal digits into. - size_t length = (integer->length + 1) * 10; - char *digits = xcalloc(length, sizeof(char)); - if (digits == NULL) return; - - // Next, create a new integer that we'll use to store the result of - // the division and modulo operations. - pm_integer_t copy; - pm_integer_copy(©, integer); - - // Then, iterate through the integer, dividing by 10 and storing the - // result in the buffer. - char *ending = digits + length - 1; - char *current = ending; - - while (copy.length > 0 || copy.head.value > 0) { - uint32_t remainder = pm_integer_divide(©, 10); - *current-- = (char) ('0' + remainder); - } + // If the integer fits into a single uint32_t, then we can just append the + // value directly to the buffer. + if (integer->values == NULL) { + pm_buffer_append_format(buffer, "%" PRIu32, integer->value); + return; + } - // Finally, append the string to the buffer and free the digits. - pm_buffer_append_string(buffer, current + 1, (size_t) (ending - current)); - xfree(digits); - return; - } + // If the integer is two uint32_t values, then we can | them together and + // append the result to the buffer. + if (integer->length == 2) { + const uint64_t value = ((uint64_t) integer->values[0]) | ((uint64_t) integer->values[1] << 32); + pm_buffer_append_format(buffer, "%" PRIu64, value); + return; } -} -/** - * Recursively destroy the linked list of an integer. - */ -static void -pm_integer_word_destroy(pm_integer_word_t *integer) { - if (integer->next != NULL) { - pm_integer_word_destroy(integer->next); + // Otherwise, first we'll convert the base from 1<<32 to 10**9. + pm_integer_t converted = { 0 }; + pm_integer_convert_base(&converted, integer, (uint64_t) 1 << 32, 1000000000); + + if (converted.values == NULL) { + pm_buffer_append_format(buffer, "%" PRIu32, converted.value); + pm_integer_free(&converted); + return; + } + + // Allocate a buffer that we'll copy the decimal digits into. + size_t digits_length = converted.length * 9; + char *digits = xcalloc(digits_length, sizeof(char)); + if (digits == NULL) return; + + // Pack bigdecimal to digits. + for (size_t value_index = 0; value_index < converted.length; value_index++) { + uint32_t value = converted.values[value_index]; + + for (size_t digit_index = 0; digit_index < 9; digit_index++) { + digits[digits_length - 9 * value_index - digit_index - 1] = (char) ('0' + value % 10); + value /= 10; + } } - xfree(integer); + size_t start_offset = 0; + while (start_offset < digits_length - 1 && digits[start_offset] == '0') start_offset++; + + // Finally, append the string to the buffer and free the digits. + pm_buffer_append_string(buffer, digits + start_offset, digits_length - start_offset); + xfree(digits); + pm_integer_free(&converted); } /** * Free the internal memory of an integer. This memory will only be allocated if - * the integer exceeds the size of a single node in the linked list. + * the integer exceeds the size of a single uint32_t. */ PRISM_EXPORTED_FUNCTION void pm_integer_free(pm_integer_t *integer) { - if (integer->head.next) { - pm_integer_word_destroy(integer->head.next); + if (integer->values) { + xfree(integer->values); } } diff --git a/prism/util/pm_integer.h b/prism/util/pm_integer.h index 63f560275d47e7..7f172988b3e182 100644 --- a/prism/util/pm_integer.h +++ b/prism/util/pm_integer.h @@ -15,30 +15,25 @@ #include /** - * A node in the linked list of a pm_integer_t. + * A structure represents an arbitrary-sized integer. */ -typedef struct pm_integer_word { - /** A pointer to the next node in the list. */ - struct pm_integer_word *next; - - /** The value of the node. */ +typedef struct { + /** + * Embedded value for small integer. This value is set to 0 if the value + * does not fit into uint32_t. + */ uint32_t value; -} pm_integer_word_t; -/** - * This structure represents an arbitrary-sized integer. It is implemented as a - * linked list of 32-bit integers, with the least significant digit at the head - * of the list. - */ -typedef struct { - /** The number of nodes in the linked list that have been allocated. */ + /** + * The number of allocated values. length is set to 0 if the integer fits + * into uint32_t. + */ size_t length; /** - * The head of the linked list, embedded directly so that allocations do not - * need to be performed for small integers. + * List of 32-bit integers. Set to NULL if the integer fits into uint32_t. */ - pm_integer_word_t head; + uint32_t *values; /** * Whether or not the integer is negative. It is stored this way so that a 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/util/pm_state_stack.c b/prism/util/pm_state_stack.c deleted file mode 100644 index 2a424b4c03c296..00000000000000 --- a/prism/util/pm_state_stack.c +++ /dev/null @@ -1,25 +0,0 @@ -#include "prism/util/pm_state_stack.h" - -/** - * Pushes a value onto the stack. - */ -void -pm_state_stack_push(pm_state_stack_t *stack, bool value) { - *stack = (*stack << 1) | (value & 1); -} - -/** - * Pops a value off the stack. - */ -void -pm_state_stack_pop(pm_state_stack_t *stack) { - *stack >>= 1; -} - -/** - * Returns the value at the top of the stack. - */ -bool -pm_state_stack_p(pm_state_stack_t *stack) { - return *stack & 1; -} diff --git a/prism/util/pm_state_stack.h b/prism/util/pm_state_stack.h deleted file mode 100644 index 1ce57a2209f0c2..00000000000000 --- a/prism/util/pm_state_stack.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file pm_state_stack.h - * - * A stack of boolean values. - */ -#ifndef PRISM_STATE_STACK_H -#define PRISM_STATE_STACK_H - -#include "prism/defines.h" - -#include -#include - -/** - * A struct that represents a stack of boolean values. - */ -typedef uint32_t pm_state_stack_t; - -/** - * Pushes a value onto the stack. - * - * @param stack The stack to push the value onto. - * @param value The value to push onto the stack. - */ -void pm_state_stack_push(pm_state_stack_t *stack, bool value); - -/** - * Pops a value off the stack. - * - * @param stack The stack to pop the value off of. - */ -void pm_state_stack_pop(pm_state_stack_t *stack); - -/** - * Returns the value at the top of the stack. - * - * @param stack The stack to get the value from. - * @return The value at the top of the stack. - */ -bool pm_state_stack_p(pm_state_stack_t *stack); - -#endif diff --git a/prism/util/pm_string.c b/prism/util/pm_string.c index fea3f1d2cf323a..8342edc34ef973 100644 --- a/prism/util/pm_string.c +++ b/prism/util/pm_string.c @@ -58,7 +58,7 @@ pm_string_constant_init(pm_string_t *string, const char *source, size_t length) * `MapViewOfFile`, on POSIX systems that have access to `mmap` we'll use * `mmap`, and on other POSIX systems we'll use `read`. */ -bool +PRISM_EXPORTED_FUNCTION bool pm_string_mapped_init(pm_string_t *string, const char *filepath) { #ifdef _WIN32 // Open the file for reading. @@ -143,6 +143,108 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) { #endif } +/** + * Read the file indicated by the filepath parameter into source and load its + * contents and size into the given `pm_string_t`. The given `pm_string_t` + * should be freed using `pm_string_free` when it is no longer used. + */ +PRISM_EXPORTED_FUNCTION bool +pm_string_file_init(pm_string_t *string, const char *filepath) { +#ifdef _WIN32 + // Open the file for reading. + HANDLE file = CreateFile(filepath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (file == INVALID_HANDLE_VALUE) { + return false; + } + + // Get the file size. + DWORD file_size = GetFileSize(file, NULL); + if (file_size == INVALID_FILE_SIZE) { + CloseHandle(file); + return false; + } + + // If the file is empty, then we don't need to do anything else, we'll set + // the source to a constant empty string and return. + if (file_size == 0) { + CloseHandle(file); + const uint8_t source[] = ""; + *string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 }; + return true; + } + + // Create a buffer to read the file into. + uint8_t *source = xmalloc(file_size); + if (source == NULL) { + CloseHandle(file); + return false; + } + + // Read the contents of the file + DWORD bytes_read; + if (!ReadFile(file, source, file_size, &bytes_read, NULL)) { + CloseHandle(file); + return false; + } + + // Check the number of bytes read + if (bytes_read != file_size) { + xfree(source); + CloseHandle(file); + return false; + } + + CloseHandle(file); + *string = (pm_string_t) { .type = PM_STRING_OWNED, .source = source, .length = (size_t) file_size }; + return true; +#elif defined(_POSIX_MAPPED_FILES) + FILE *file = fopen(filepath, "rb"); + if (file == NULL) { + return false; + } + + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + + if (file_size == -1) { + fclose(file); + return false; + } + + if (file_size == 0) { + fclose(file); + const uint8_t source[] = ""; + *string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 }; + return true; + } + + size_t length = (size_t) file_size; + uint8_t *source = xmalloc(length); + if (source == NULL) { + fclose(file); + return false; + } + + fseek(file, 0, SEEK_SET); + size_t bytes_read = fread(source, length, 1, file); + fclose(file); + + if (bytes_read != 1) { + xfree(source); + return false; + } + + *string = (pm_string_t) { .type = PM_STRING_OWNED, .source = source, .length = length }; + return true; +#else + (void) string; + (void) filepath; + perror("pm_string_file_init is not implemented for this platform"); + return false; +#endif +} + /** * Returns the memory size associated with the string. */ diff --git a/prism/util/pm_string.h b/prism/util/pm_string.h index d6442fc608d9d8..a68e2a7c912233 100644 --- a/prism/util/pm_string.h +++ b/prism/util/pm_string.h @@ -109,6 +109,17 @@ void pm_string_constant_init(pm_string_t *string, const char *source, size_t len */ PRISM_EXPORTED_FUNCTION bool pm_string_mapped_init(pm_string_t *string, const char *filepath); +/** + * Read the file indicated by the filepath parameter into source and load its + * contents and size into the given `pm_string_t`. The given `pm_string_t` + * should be freed using `pm_string_free` when it is no longer used. + * + * @param string The string to initialize. + * @param filepath The filepath to read. + * @return Whether or not the file was successfully read. + */ +PRISM_EXPORTED_FUNCTION bool pm_string_file_init(pm_string_t *string, const char *filepath); + /** * Returns the memory size associated with the string. * diff --git a/prism/version.h b/prism/version.h index 237796815f95a7..489a8107cf4c28 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 26 /** * 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.26.0" #endif diff --git a/prism_compile.c b/prism_compile.c index fff43d6f13e633..dd5f920fb49663 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; } /** @@ -125,16 +151,32 @@ static VALUE parse_integer(const pm_integer_node_t *node) { const pm_integer_t *integer = &node->value; + VALUE result; - VALUE result = UINT2NUM(integer->head.value); - size_t shift = 0; + if (integer->values == NULL) { + result = UINT2NUM(integer->value); + } + else { + VALUE string = rb_str_new(NULL, integer->length * 8); + unsigned char *bytes = (unsigned char *) RSTRING_PTR(string); - for (pm_integer_word_t *node = integer->head.next; node != NULL; node = node->next) { - VALUE receiver = rb_funcall(UINT2NUM(node->value), rb_intern("<<"), 1, ULONG2NUM(++shift * 32)); - result = rb_funcall(receiver, rb_intern("|"), 1, result); + size_t offset = integer->length * 8; + for (size_t value_index = 0; value_index < integer->length; value_index++) { + uint32_t value = integer->values[value_index]; + + for (int index = 0; index < 8; index++) { + int byte = (value >> (4 * index)) & 0xf; + bytes[--offset] = byte < 10 ? byte + '0' : byte - 10 + 'a'; + } + } + + result = rb_funcall(string, rb_intern("to_i"), 1, UINT2NUM(16)); + } + + if (integer->negative) { + result = rb_funcall(result, rb_intern("-@"), 0); } - if (integer->negative) result = rb_funcall(result, rb_intern("-@"), 0); return result; } @@ -195,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)) { @@ -236,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) { @@ -266,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); + } } /** @@ -371,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); @@ -396,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); } @@ -415,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: @@ -444,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); @@ -495,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); @@ -544,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: @@ -619,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: { @@ -634,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); } /** @@ -671,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) { @@ -687,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) { @@ -742,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); } @@ -753,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; @@ -763,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. @@ -863,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); @@ -905,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); @@ -916,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; } } @@ -933,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; @@ -946,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); } /** @@ -990,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; @@ -1018,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; @@ -1040,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; @@ -1070,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; @@ -1093,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; @@ -1175,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 @@ -1185,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 { @@ -1195,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; @@ -1214,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; } @@ -1250,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: @@ -1282,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 @@ -1311,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); } @@ -1322,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); } /** @@ -1340,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); } } @@ -1375,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); } /** @@ -1460,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); } } @@ -1495,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 @@ -1606,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; } @@ -1638,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); - - 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_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, 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, 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; } @@ -1673,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; } @@ -1709,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; } @@ -1723,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; } @@ -1775,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; } @@ -1799,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; @@ -1811,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); } /** @@ -1850,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: { @@ -1877,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++; } @@ -1894,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: { @@ -1983,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)); @@ -1994,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)); } @@ -2040,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)); } @@ -2051,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; } @@ -2121,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) { @@ -2144,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) { @@ -2181,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)) { @@ -2217,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; } @@ -2250,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: { @@ -2278,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; } @@ -2295,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 @@ -2312,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: @@ -2383,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: { @@ -2401,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; } @@ -2427,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; @@ -2439,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: @@ -2498,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; @@ -2556,41 +2757,41 @@ 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: @@ -2607,26 +2808,117 @@ pm_scope_node_destroy(pm_scope_node_t *scope_node) } } -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); +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; -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) + 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)); + } + } + + if ((flags & VM_CALL_KW_SPLAT) && (flags & VM_CALL_ARGS_BLOCKARG) && !(flags & VM_CALL_KW_SPLAT_MUT)) { + PUSH_INSN(ret, location, splatkw); + } + + PUSH_SEND_R(ret, location, method_id, INT2FIX(orig_argc), block_iseq, INT2FIX(flags), kw_arg); + + 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; @@ -2644,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 { @@ -2665,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]); } } } @@ -2726,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: @@ -2900,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; + ELEM_INSERT_NEXT(last, &new_insn_body(iseq, node_location->line, node_location->column, BIN(putnil), 0)->link); + PUSH_INSN(ret, *node_location, swap); - 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); - } - - 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 @@ -3077,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) { @@ -3091,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 { @@ -3104,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; @@ -3165,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); } @@ -3187,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); } @@ -3203,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); } /** @@ -3221,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); } @@ -3243,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); @@ -3254,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++) { @@ -3262,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); } @@ -3331,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; } @@ -3417,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: { @@ -3427,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: { @@ -3439,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: { @@ -3451,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: { @@ -3464,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: { @@ -3476,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: { @@ -3496,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; @@ -3530,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; @@ -3563,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)); } } @@ -3586,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); } } @@ -3631,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; @@ -3658,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 @@ -3682,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); @@ -3695,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++) { @@ -3722,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; } @@ -3753,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; @@ -3771,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 @@ -3780,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: @@ -3811,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; @@ -3876,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); } } @@ -3956,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 ); } @@ -3975,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 ); } @@ -3987,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); } } @@ -4047,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: { @@ -4064,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; } @@ -4099,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: @@ -4144,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) { @@ -4157,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 @@ -4218,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 @@ -4237,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 { @@ -4292,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) { @@ -4311,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++; @@ -4360,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) { @@ -4456,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 @@ -4578,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; } @@ -4626,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 @@ -4642,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 @@ -4659,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); } } } @@ -4677,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 @@ -4713,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 @@ -4725,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 @@ -4776,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 @@ -4825,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. @@ -4845,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 @@ -4864,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 @@ -4887,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); } @@ -4906,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)); + PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(flags)); - PM_DUP_UNLESS_POPPED; - - 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: { @@ -5101,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); @@ -5115,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; } @@ -5180,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; } @@ -5235,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: { @@ -5264,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: { @@ -5288,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: { @@ -5297,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; } @@ -5319,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; } @@ -5350,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; } @@ -5367,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(location.line); + LABEL *then_label = NEW_LABEL(location.line); + LABEL *else_label = NEW_LABEL(location.line); - LABEL *final_label = NEW_LABEL(lineno); - LABEL *then_label = NEW_LABEL(lineno); - LABEL *else_label = NEW_LABEL(lineno); + pm_compile_flip_flop(cast, else_label, then_label, iseq, location.line, ret, popped, scope_node); - pm_compile_flip_flop(flip_flop_node, else_label, then_label, iseq, lineno, 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; @@ -5497,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 @@ -5526,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: { @@ -5536,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); @@ -5565,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; } @@ -5574,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; } @@ -5582,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; @@ -5597,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; @@ -5615,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)); - - PM_DUP_UNLESS_POPPED; + PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(flags)); - 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 @@ -5769,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); } } @@ -5789,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; } @@ -5803,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; } @@ -5813,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; @@ -6026,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; + if (popped) PUSH_INSN(ret, location, pop); 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); + // 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); + } + case PM_LOCAL_VARIABLE_OR_WRITE_NODE: { + // 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, // @@ -6193,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. @@ -6211,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 @@ -6223,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: { @@ -6322,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 @@ -6368,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; @@ -6449,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: { @@ -6505,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; } @@ -6519,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); } @@ -6530,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; } @@ -6599,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); } @@ -6687,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; } @@ -6713,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)) { @@ -6722,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); @@ -6750,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 @@ -6766,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); } @@ -6782,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; @@ -6819,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: { @@ -6890,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; @@ -6910,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; @@ -6928,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))); @@ -7010,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++; } } @@ -7027,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)) { @@ -7059,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++; @@ -7116,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); @@ -7146,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); @@ -7169,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) @@ -7258,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); @@ -7280,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); @@ -7415,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)) { @@ -7477,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; } @@ -7489,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 }; @@ -7523,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); } } @@ -7560,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 @@ -7570,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); } @@ -7578,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) { @@ -7593,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; @@ -7636,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); } } @@ -7650,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); } } @@ -7660,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; @@ -7673,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; @@ -7747,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 // ^^^^^^^^^^^^^^^^^^ @@ -7813,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; @@ -7822,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; } @@ -7831,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; @@ -7870,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; } @@ -7888,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); } @@ -7909,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 // ^^^^^^^^^ @@ -7991,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); @@ -8022,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)); @@ -8036,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; } } @@ -8055,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); @@ -8085,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; @@ -8107,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; @@ -8157,30 +8660,23 @@ 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); - // 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); + // 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; - // TODO: We need to set the backtrace. - // rb_funcallv(error, rb_intern("set_backtrace"), 1, &path); - return error; - } + pm_scope_node_init(node, scope_node, NULL); + scope_node->filepath_encoding = filepath_encoding; // Emit all of the various warnings from the parse. 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) { @@ -8191,11 +8687,17 @@ pm_parse_input(pm_parse_result_t *result, VALUE filepath) } } + // If there are errors, raise an appropriate error and free the 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); + return error; + } + // 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); @@ -8219,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. @@ -8259,7 +8785,7 @@ pm_parse_file_script_lines(const pm_scope_node_t *scope_node, const pm_parser_t * be read. */ VALUE -pm_load_file(pm_parse_result_t *result, VALUE filepath) +pm_load_file(pm_parse_result_t *result, VALUE filepath, bool load_error) { if (!pm_string_mapped_init(&result->input, RSTRING_PTR(filepath))) { #ifdef _WIN32 @@ -8268,11 +8794,24 @@ pm_load_file(pm_parse_result_t *result, VALUE filepath) int e = errno; #endif - VALUE err = rb_syserr_new(e, RSTRING_PTR(filepath)); - RB_GC_GUARD(filepath); - return err; + VALUE error; + + if (load_error) { + VALUE message = rb_str_buf_new_cstr(strerror(e)); + rb_str_cat2(message, " -- "); + rb_str_append(message, filepath); + + error = rb_exc_new3(rb_eLoadError, message); + rb_ivar_set(error, rb_intern_const("@path"), filepath); + } else { + error = rb_syserr_new(e, RSTRING_PTR(filepath)); + RB_GC_GUARD(filepath); + } + + return error; } + pm_options_frozen_string_literal_init(&result->options); return Qnil; } @@ -8285,7 +8824,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 @@ -8310,7 +8855,7 @@ pm_parse_file(pm_parse_result_t *result, VALUE filepath) VALUE pm_load_parse_file(pm_parse_result_t *result, VALUE filepath) { - VALUE error = pm_load_file(result, filepath); + VALUE error = pm_load_file(result, filepath, false); if (NIL_P(error)) { error = pm_parse_file(result, filepath); } @@ -8320,19 +8865,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..0f82782ec02407 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,17 +56,27 @@ 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; -VALUE pm_load_file(pm_parse_result_t *result, VALUE filepath); +VALUE pm_load_file(pm_parse_result_t *result, VALUE filepath, bool load_error); 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 bb09f92ae98a6d..1a67a636632a90 100644 --- a/proc.c +++ b/proc.c @@ -1462,7 +1462,7 @@ rb_sym_to_proc(VALUE sym) if (!sym_proc_cache) { sym_proc_cache = rb_ary_hidden_new(SYM_PROC_CACHE_SIZE * 2); - rb_gc_register_mark_object(sym_proc_cache); + rb_vm_register_global_object(sym_proc_cache); rb_ary_store(sym_proc_cache, SYM_PROC_CACHE_SIZE*2 - 1, Qnil); } @@ -1945,7 +1945,7 @@ rb_method_name_error(VALUE klass, VALUE str) VALUE c = klass; VALUE s = Qundef; - if (FL_TEST(c, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(c)) { VALUE obj = RCLASS_ATTACHED_OBJECT(klass); switch (BUILTIN_TYPE(obj)) { @@ -2194,7 +2194,7 @@ rb_mod_define_method_with_visibility(int argc, VALUE *argv, VALUE mod, const str struct METHOD *method = (struct METHOD *)RTYPEDDATA_GET_DATA(body); if (method->me->owner != mod && !RB_TYPE_P(method->me->owner, T_MODULE) && !RTEST(rb_class_inherited_p(mod, method->me->owner))) { - if (FL_TEST(method->me->owner, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(method->me->owner)) { rb_raise(rb_eTypeError, "can't bind singleton method to a different class"); } @@ -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")); } /* @@ -2561,7 +2551,7 @@ convert_umethod_to_method_components(const struct METHOD *data, VALUE recv, VALU if (!NIL_P(refined_class)) methclass = refined_class; } if (!RB_TYPE_P(methclass, T_MODULE) && !RTEST(rb_obj_is_kind_of(recv, methclass))) { - if (FL_TEST(methclass, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(methclass)) { rb_raise(rb_eTypeError, "singleton method called for a different object"); } @@ -3132,7 +3122,7 @@ method_inspect(VALUE method) // UnboundMethod rb_str_buf_append(str, rb_inspect(defined_class)); } - else if (FL_TEST(mklass, FL_SINGLETON)) { + else if (RCLASS_SINGLETON_P(mklass)) { VALUE v = RCLASS_ATTACHED_OBJECT(mklass); if (UNDEF_P(data->recv)) { @@ -3152,7 +3142,7 @@ method_inspect(VALUE method) } else { mklass = data->klass; - if (FL_TEST(mklass, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(mklass)) { VALUE v = RCLASS_ATTACHED_OBJECT(mklass); if (!(RB_TYPE_P(v, T_CLASS) || RB_TYPE_P(v, T_MODULE))) { do { @@ -3477,7 +3467,7 @@ proc_binding(VALUE self) env = VM_ENV_ENVVAL_PTR(block->as.captured.ep); env = env_clone(env, method_cref(method)); /* set empty iseq */ - empty = rb_iseq_new(NULL, name, name, Qnil, 0, ISEQ_TYPE_TOP); + empty = rb_iseq_new(Qnil, name, name, Qnil, 0, ISEQ_TYPE_TOP); RB_OBJ_WRITE(env, &env->iseq, empty); break; } diff --git a/process.c b/process.c index e5415dd170a362..b1f9931f06d477 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. * @@ -1682,7 +1682,6 @@ before_fork_ruby(void) static void after_fork_ruby(rb_pid_t pid) { - rb_threadptr_pending_interrupt_clear(GET_THREAD()); if (pid == 0) { // child clear_pid_cache(); @@ -3141,9 +3140,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); @@ -5255,7 +5258,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..f6b2b22c9ae953 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); @@ -2012,12 +2012,11 @@ ractor_alloc(VALUE klass) rb_ractor_t * rb_ractor_main_alloc(void) { - rb_ractor_t *r = ruby_mimmalloc(sizeof(rb_ractor_t)); + rb_ractor_t *r = ruby_mimcalloc(1, sizeof(rb_ractor_t)); if (r == NULL) { fprintf(stderr, "[FATAL] failed to allocate memory for main ractor\n"); exit(EXIT_FAILURE); } - MEMZERO(r, rb_ractor_t, 1); r->pub.id = ++ractor_last_id; r->loc = Qnil; r->name = Qnil; @@ -2985,7 +2984,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 +3016,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/random.c b/random.c index ab33600792fdbe..ea76ea656f4b71 100644 --- a/random.c +++ b/random.c @@ -596,7 +596,7 @@ fill_random_bytes_crypt(void *seed, size_t size) if (prov != INVALID_HCRYPTPROV) { #undef RUBY_UNTYPED_DATA_WARNING #define RUBY_UNTYPED_DATA_WARNING 0 - rb_gc_register_mark_object(Data_Wrap_Struct(0, 0, release_crypt, &perm_prov)); + rb_vm_register_global_object(Data_Wrap_Struct(0, 0, release_crypt, &perm_prov)); } } else { /* another thread acquired */ 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/rational.c b/rational.c index 3b82016f323087..014cbb6c6adf87 100644 --- a/rational.c +++ b/rational.c @@ -418,7 +418,7 @@ nurat_s_new_internal(VALUE klass, VALUE num, VALUE den) RATIONAL_SET_NUM((VALUE)obj, num); RATIONAL_SET_DEN((VALUE)obj, den); - OBJ_FREEZE_RAW((VALUE)obj); + OBJ_FREEZE((VALUE)obj); return (VALUE)obj; } @@ -1847,7 +1847,7 @@ nurat_loader(VALUE self, VALUE a) nurat_canonicalize(&num, &den); RATIONAL_SET_NUM((VALUE)dat, num); RATIONAL_SET_DEN((VALUE)dat, den); - OBJ_FREEZE_RAW(self); + OBJ_FREEZE(self); return self; } diff --git a/re.c b/re.c index 9c474492711519..c8940ff887df64 100644 --- a/re.c +++ b/re.c @@ -1530,8 +1530,8 @@ reg_enc_error(VALUE re, VALUE str) { rb_raise(rb_eEncCompatError, "incompatible encoding regexp match (%s regexp with %s string)", - rb_enc_name(rb_enc_get(re)), - rb_enc_name(rb_enc_get(str))); + rb_enc_inspect_name(rb_enc_get(re)), + rb_enc_inspect_name(rb_enc_get(str))); } static inline int diff --git a/regexec.c b/regexec.c index 95332915840e96..6d82429e03dc99 100644 --- a/regexec.c +++ b/regexec.c @@ -3449,8 +3449,8 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, CASE(OP_MEMORY_END_PUSH_REC) MOP_IN(OP_MEMORY_END_PUSH_REC); GET_MEMNUM_INC(mem, p); STACK_GET_MEM_START(mem, stkp); /* should be before push mem-end. */ - STACK_PUSH_MEM_END(mem, s); mem_start_stk[mem] = GET_STACK_INDEX(stkp); + STACK_PUSH_MEM_END(mem, s); MOP_OUT; JUMP; @@ -4218,7 +4218,8 @@ match_at(regex_t* reg, const UChar* str, const UChar* end, timeout: xfree(xmalloc_base); - xfree(stk_base); + if (stk_base != stk_alloc || IS_NOT_NULL(msa->stack_p)) + xfree(stk_base); HANDLE_REG_TIMEOUT_IN_MATCH_AT; } @@ -4920,12 +4921,17 @@ forward_search_range(regex_t* reg, const UChar* str, const UChar* end, UChar* s, UChar* range, UChar** low, UChar** high, UChar** low_prev) { UChar *p, *pprev = (UChar* )NULL; + size_t input_len = end - str; #ifdef ONIG_DEBUG_SEARCH fprintf(stderr, "forward_search_range: str: %"PRIuPTR" (%p), end: %"PRIuPTR" (%p), s: %"PRIuPTR" (%p), range: %"PRIuPTR" (%p)\n", (uintptr_t )str, str, (uintptr_t )end, end, (uintptr_t )s, s, (uintptr_t )range, range); #endif + if (reg->dmin > input_len) { + return 0; + } + p = s; if (reg->dmin > 0) { if (ONIGENC_IS_SINGLEBYTE(reg->enc)) { @@ -5062,6 +5068,11 @@ backward_search_range(regex_t* reg, const UChar* str, const UChar* end, UChar** low, UChar** high) { UChar *p; + size_t input_len = end - str; + + if (reg->dmin > input_len) { + return 0; + } range += reg->dmin; p = s; 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/rjit_c.rb b/rjit_c.rb index 5b08ac71c58cba..b9a5bb0b5532bc 100644 --- a/rjit_c.rb +++ b/rjit_c.rb @@ -326,6 +326,10 @@ def UNDEFINED_METHOD_ENTRY_P(cme) def RCLASS_ORIGIN(klass) Primitive.cexpr! 'RCLASS_ORIGIN(klass)' end + + def RCLASS_SINGLETON_P(klass) + Primitive.cexpr! 'RCLASS_SINGLETON_P(klass)' + end end # @@ -392,7 +396,6 @@ def rb_iseqw_to_iseq(iseqw) C::RUBY_FLONUM_FLAG = Primitive.cexpr! %q{ SIZET2NUM(RUBY_FLONUM_FLAG) } C::RUBY_FLONUM_MASK = Primitive.cexpr! %q{ SIZET2NUM(RUBY_FLONUM_MASK) } C::RUBY_FL_FREEZE = Primitive.cexpr! %q{ SIZET2NUM(RUBY_FL_FREEZE) } - C::RUBY_FL_SINGLETON = Primitive.cexpr! %q{ SIZET2NUM(RUBY_FL_SINGLETON) } C::RUBY_IMMEDIATE_MASK = Primitive.cexpr! %q{ SIZET2NUM(RUBY_IMMEDIATE_MASK) } C::RUBY_SPECIAL_SHIFT = Primitive.cexpr! %q{ SIZET2NUM(RUBY_SPECIAL_SHIFT) } C::RUBY_SYMBOL_FLAG = Primitive.cexpr! %q{ SIZET2NUM(RUBY_SYMBOL_FLAG) } @@ -1090,6 +1093,7 @@ def C.rb_iseq_constant_body ruby2_keywords: [CType::BitField.new(1, 1), 9], anon_rest: [CType::BitField.new(1, 2), 10], anon_kwrest: [CType::BitField.new(1, 3), 11], + use_block: [CType::BitField.new(1, 4), 12], ), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, flags)")], size: [CType::Immediate.parse("unsigned int"), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, size)")], lead_num: [CType::Immediate.parse("int"), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, lead_num)")], diff --git a/ruby.c b/ruby.c index d19894e4e4e318..bfff15ba30ea50 100644 --- a/ruby.c +++ b/ruby.c @@ -151,22 +151,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 @@ -222,12 +219,13 @@ cmdline_options_init(ruby_cmdline_options_t *opt) #elif defined(YJIT_FORCE_ENABLE) opt->features.set |= FEATURE_BIT(yjit); #endif + opt->dump |= DUMP_BIT(opt_optimize); opt->backtrace_length_limit = LONG_MIN; return opt; } -static rb_ast_t *load_file(VALUE parser, VALUE fname, VALUE f, int script, +static VALUE load_file(VALUE parser, VALUE fname, VALUE f, int script, ruby_cmdline_options_t *opt); static VALUE open_load_file(VALUE fname_v, int *xflag); static void forbid_setid(const char *, const ruby_cmdline_options_t *); @@ -254,6 +252,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); @@ -319,80 +318,83 @@ 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 - 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) < 25); 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 }; 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[]; @@ -404,7 +406,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]); @@ -437,39 +439,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); @@ -486,23 +480,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 @@ -510,6 +495,7 @@ identical_path(VALUE path) { return path; } + static VALUE locale_path(VALUE path) { @@ -720,11 +706,11 @@ ruby_init_loadpath(void) p -= bindir_len; archlibdir = rb_str_subseq(sopath, 0, p - libpath); rb_str_cat_cstr(archlibdir, libdir); - OBJ_FREEZE_RAW(archlibdir); + OBJ_FREEZE(archlibdir); } else if (p - libpath >= libdir_len && !strncmp(p - libdir_len, libdir, libdir_len)) { archlibdir = rb_str_subseq(sopath, 0, (p2 ? p2 : p) - libpath); - OBJ_FREEZE_RAW(archlibdir); + OBJ_FREEZE(archlibdir); p -= libdir_len; } #ifdef ENABLE_MULTIARCH @@ -755,7 +741,7 @@ ruby_init_loadpath(void) #endif rb_gc_register_address(&ruby_prefix_path); ruby_prefix_path = PREFIX_PATH(); - OBJ_FREEZE_RAW(ruby_prefix_path); + OBJ_FREEZE(ruby_prefix_path); if (!archlibdir) archlibdir = ruby_prefix_path; rb_gc_register_address(&ruby_archlibdir_path); ruby_archlibdir_path = archlibdir; @@ -975,7 +961,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; } @@ -1095,21 +1081,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; } @@ -1118,12 +1128,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, ;); @@ -2041,17 +2056,19 @@ show_help(const char *progname, int help) usage(progname, help, tty, columns); } -static rb_ast_t * +static VALUE process_script(ruby_cmdline_options_t *opt) { rb_ast_t *ast; + VALUE vast; 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); } @@ -2063,7 +2080,7 @@ process_script(ruby_cmdline_options_t *opt) ruby_set_script_name(progname); rb_parser_set_options(parser, opt->do_print, opt->do_loop, opt->do_line, opt->do_split); - ast = rb_parser_compile_string(parser, opt->script, opt->e_script, 1); + vast = rb_parser_compile_string(parser, opt->script, opt->e_script, 1); } else { VALUE f; @@ -2071,13 +2088,14 @@ process_script(ruby_cmdline_options_t *opt) f = open_load_file(opt->script_name, &xflag); opt->xflag = xflag != 0; rb_parser_set_context(parser, 0, f == rb_stdin); - ast = load_file(parser, opt->script_name, f, 1, opt); + vast = load_file(parser, opt->script_name, f, 1, opt); } + ast = rb_ruby_ast_data_get(vast); if (!ast->body.root) { rb_ast_dispose(ast); - return NULL; + return Qnil; } - return ast; + return vast; } /** @@ -2113,6 +2131,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; @@ -2122,7 +2144,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; @@ -2133,7 +2159,7 @@ prism_script(ruby_cmdline_options_t *opt, pm_parse_result_t *result) } else { pm_options_command_line_set(options, command_line); - error = pm_load_file(result, opt->script_name); + error = pm_load_file(result, opt->script_name, true); // If reading the file did not error, at that point we load the command // line options. We do it in this order so that if the main script fails @@ -2202,7 +2228,7 @@ process_options_global_setup(const ruby_cmdline_options_t *opt, const rb_iseq_t if ((rb_e_script = opt->e_script) != 0) { rb_str_freeze(rb_e_script); - rb_gc_register_mark_object(opt->e_script); + rb_vm_register_global_object(opt->e_script); } rb_execution_context_t *ec = GET_EC(); @@ -2213,6 +2239,7 @@ process_options_global_setup(const ruby_cmdline_options_t *opt, const rb_iseq_t static VALUE process_options(int argc, char **argv, ruby_cmdline_options_t *opt) { + VALUE vast = Qnil; struct { rb_ast_t *ast; pm_parse_result_t prism; @@ -2327,8 +2354,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(); @@ -2449,7 +2474,8 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } if (!(*rb_ruby_prism_ptr())) { - if (!(result.ast = process_script(opt))) return Qfalse; + vast = process_script(opt); + if (!(result.ast = rb_ruby_ast_data_get(vast))) return Qfalse; } else { prism_script(opt, &result.prism); @@ -2486,10 +2512,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 { @@ -2497,7 +2523,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; @@ -2522,7 +2548,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; @@ -2531,12 +2557,12 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } else { rb_ast_t *ast = result.ast; - iseq = rb_iseq_new_main(&ast->body, opt->script_name, path, parent, optimize); + iseq = rb_iseq_new_main(vast, opt->script_name, path, parent, optimize); rb_ast_dispose(ast); } } - 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); @@ -2570,7 +2596,7 @@ struct load_file_arg { VALUE f; }; -VALUE rb_script_lines_for(VALUE path, bool add); +void rb_set_script_lines_for(VALUE vparser, VALUE path); static VALUE load_file_internal(VALUE argp_v) @@ -2582,7 +2608,7 @@ load_file_internal(VALUE argp_v) ruby_cmdline_options_t *opt = argp->opt; VALUE f = argp->f; int line_start = 1; - rb_ast_t *ast = 0; + VALUE vast = Qnil; rb_encoding *enc; ID set_encoding; @@ -2675,18 +2701,15 @@ 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); - if (!NIL_P(lines)) { - rb_parser_set_script_lines(parser, lines); - } + rb_set_script_lines_for(parser, orig_fname); if (NIL_P(f)) { f = rb_str_new(0, 0); rb_enc_associate(f, enc); - return (VALUE)rb_parser_compile_string_path(parser, orig_fname, f, line_start); + return rb_parser_compile_string_path(parser, orig_fname, f, line_start); } rb_funcall(f, set_encoding, 2, rb_enc_from_encoding(enc), rb_str_new_cstr("-")); - ast = rb_parser_compile_file_path(parser, orig_fname, f, line_start); + vast = rb_parser_compile_file_path(parser, orig_fname, f, line_start); rb_funcall(f, set_encoding, 1, rb_parser_encoding(parser)); if (script && rb_parser_end_seen_p(parser)) { /* @@ -2704,7 +2727,7 @@ load_file_internal(VALUE argp_v) rb_define_global_const("DATA", f); argp->f = Qnil; } - return (VALUE)ast; + return vast; } /* disabling O_NONBLOCK, and returns 0 on success, otherwise errno */ @@ -2813,7 +2836,7 @@ restore_load_file(VALUE arg) return Qnil; } -static rb_ast_t * +static VALUE load_file(VALUE parser, VALUE fname, VALUE f, int script, ruby_cmdline_options_t *opt) { struct load_file_arg arg; @@ -2822,7 +2845,7 @@ load_file(VALUE parser, VALUE fname, VALUE f, int script, ruby_cmdline_options_t arg.script = script; arg.opt = opt; arg.f = f; - return (rb_ast_t *)rb_ensure(load_file_internal, (VALUE)&arg, + return rb_ensure(load_file_internal, (VALUE)&arg, restore_load_file, (VALUE)&arg); } @@ -2836,10 +2859,12 @@ rb_load_file(const char *fname) void * rb_load_file_str(VALUE fname_v) { - return rb_parser_load_file(rb_parser_new(), fname_v); + VALUE vast; + vast = rb_parser_load_file(rb_parser_new(), fname_v); + return (void *)rb_ruby_ast_data_get(vast); } -void * +VALUE rb_parser_load_file(VALUE parser, VALUE fname_v) { ruby_cmdline_options_t opt; @@ -3070,7 +3095,7 @@ ruby_process_options(int argc, char **argv) } set_progname(external_str_new_cstr(script_name)); /* for the time being */ rb_argv0 = rb_str_new4(rb_progname); - rb_gc_register_mark_object(rb_argv0); + rb_vm_register_global_object(rb_argv0); #ifndef HAVE_SETPROCTITLE ruby_init_setproctitle(argc, argv); diff --git a/ruby_parser.c b/ruby_parser.c index 8e2371fd1d92dd..7ba5c26fb05315 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -1,5 +1,6 @@ /* This is a wrapper for parse.y */ +#include "internal/parse.h" #include "internal/re.h" #include "internal/ruby_parser.h" @@ -18,7 +19,6 @@ #include "internal/gc.h" #include "internal/hash.h" #include "internal/io.h" -#include "internal/parse.h" #include "internal/rational.h" #include "internal/re.h" #include "internal/string.h" @@ -32,41 +32,6 @@ #include "vm_core.h" #include "symbol.h" -struct ruby_parser { - rb_parser_t *parser_params; -}; - -static void -parser_mark(void *ptr) -{ - struct ruby_parser *parser = (struct ruby_parser*)ptr; - rb_ruby_parser_mark(parser->parser_params); -} - -static void -parser_free(void *ptr) -{ - struct ruby_parser *parser = (struct ruby_parser*)ptr; - rb_ruby_parser_free(parser->parser_params); -} - -static size_t -parser_memsize(const void *ptr) -{ - struct ruby_parser *parser = (struct ruby_parser*)ptr; - return rb_ruby_parser_memsize(parser->parser_params); -} - -static const rb_data_type_t ruby_parser_data_type = { - "parser", - { - parser_mark, - parser_free, - parser_memsize, - }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY -}; - static int is_ascii_string2(VALUE str) { @@ -93,32 +58,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) { @@ -221,12 +160,6 @@ enc_codelen(int c, void *enc) return rb_enc_codelen(c, (rb_encoding *)enc); } -static VALUE -enc_str_buf_cat(VALUE str, const char *ptr, long len, void *enc) -{ - return rb_enc_str_buf_cat(str, ptr, len, (rb_encoding *)enc); -} - static int enc_mbcput(unsigned int c, void *buf, void *enc) { @@ -251,36 +184,6 @@ intern3(const char *name, long len, void *enc) return rb_intern3(name, len, (rb_encoding *)enc); } -static void * -enc_compatible(VALUE str1, VALUE str2) -{ - return (void *)rb_enc_compatible(str1, str2); -} - -static VALUE -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) -{ - return ENCODING_IS_ASCII8BIT(obj); -} - static void * usascii_encoding(void) { @@ -329,18 +232,6 @@ reg_named_capture_assign(struct parser_params* p, VALUE regexp, const rb_code_lo return RNODE_BLOCK(arg.succ_block)->nd_next; } -static VALUE -rbool(VALUE v) -{ - return RBOOL(v); -} - -static int -undef_p(VALUE v) -{ - return RB_UNDEF_P(v); -} - static int rtest(VALUE obj) { @@ -359,24 +250,6 @@ syntax_error_new(void) return rb_class_new_instance(0, 0, rb_eSyntaxError); } -static VALUE -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) -{ - return rb_default_rs; -} - static void * memmove2(void *dest, const void *src, size_t t, size_t n) { @@ -401,18 +274,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,28 +286,18 @@ 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) +ast_new(node_buffer_t *nb) { - return IMEMO_NEW(rb_ast_t, imemo_ast, nb); + rb_ast_t *ast = ruby_xcalloc(1, sizeof(rb_ast_t)); + ast->node_buffer = nb; + return ast; } static VALUE @@ -461,10 +312,28 @@ 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); } -VALUE rb_io_gets_internal(VALUE io); +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); +} + 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,30 +348,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, .make_temporary_id = rb_make_temporary_id, .is_local_id = is_local_id2, @@ -523,10 +379,6 @@ static const rb_parser_config_t rb_global_parser_config = { .str_catf = rb_str_catf, .str_cat_cstr = rb_str_cat_cstr, - .str_subseq = rb_str_subseq, - .str_new_frozen = rb_str_new_frozen, - .str_buf_new = rb_str_buf_new, - .str_buf_cat = rb_str_buf_cat, .str_modify = rb_str_modify, .str_set_len = rb_str_set_len, .str_cat = rb_str_cat, @@ -536,34 +388,21 @@ static const rb_parser_config_t rb_global_parser_config = { .str_to_interned_str = rb_str_to_interned_str, .is_ascii_string = is_ascii_string2, .enc_str_new = enc_str_new, - .enc_str_buf_cat = enc_str_buf_cat, - .str_buf_append = rb_str_buf_append, .str_vcatf = rb_str_vcatf, .string_value_cstr = rb_string_value_cstr, .rb_sprintf = rb_sprintf, .rstring_ptr = RSTRING_PTR, .rstring_end = RSTRING_END, .rstring_len = RSTRING_LEN, - .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, .write_error_str = rb_write_error_str, - .default_rs = default_rs, .io_write = rb_io_write, .io_flush = rb_io_flush, .io_puts = rb_io_puts, - .io_gets_internal = rb_io_gets_internal, .debug_output_stdout = rb_ractor_stdout, .debug_output_stderr = rb_ractor_stderr, @@ -582,29 +421,20 @@ 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, .enc_isspace = enc_isspace, .enc_coderange_7bit = ENC_CODERANGE_7BIT, .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, @@ -616,12 +446,8 @@ 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, @@ -641,29 +467,91 @@ static const rb_parser_config_t rb_global_parser_config = { .scan_digits = ruby_scan_digits, .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, }; +#endif -rb_parser_t * -rb_parser_params_allocate(void) +enum lex_type { + lex_type_str, + lex_type_io, + lex_type_array, + lex_type_generic, +}; + +struct ruby_parser { + rb_parser_t *parser_params; + enum lex_type type; + union { + struct lex_pointer_string lex_str; + struct { + VALUE file; + } lex_io; + struct { + VALUE ary; + } lex_array; + } data; +}; + +static void +parser_mark(void *ptr) { - return rb_ruby_parser_allocate(&rb_global_parser_config); + struct ruby_parser *parser = (struct ruby_parser*)ptr; + rb_ruby_parser_mark(parser->parser_params); + + switch (parser->type) { + case lex_type_str: + rb_gc_mark(parser->data.lex_str.str); + break; + case lex_type_io: + rb_gc_mark(parser->data.lex_io.file); + break; + case lex_type_array: + rb_gc_mark(parser->data.lex_array.ary); + break; + case lex_type_generic: + /* noop. Caller of rb_parser_compile_generic should mark the objects. */ + break; + } +} + +static void +parser_free(void *ptr) +{ + struct ruby_parser *parser = (struct ruby_parser*)ptr; + rb_ruby_parser_free(parser->parser_params); +} + +static size_t +parser_memsize(const void *ptr) +{ + struct ruby_parser *parser = (struct ruby_parser*)ptr; + return rb_ruby_parser_memsize(parser->parser_params); +} + +static const rb_data_type_t ruby_parser_data_type = { + "parser", + { + parser_mark, + parser_free, + parser_memsize, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY +}; + +#ifdef UNIVERSAL_PARSER +const rb_parser_config_t * +rb_ruby_parser_config(void) +{ + return &rb_global_parser_config; } rb_parser_t * @@ -671,6 +559,13 @@ rb_parser_params_new(void) { return rb_ruby_parser_new(&rb_global_parser_config); } +#else +rb_parser_t * +rb_parser_params_new(void) +{ + return rb_ruby_parser_new(); +} +#endif /* UNIVERSAL_PARSER */ VALUE rb_parser_new(void) @@ -711,12 +606,12 @@ rb_parser_set_context(VALUE vparser, const struct rb_iseq_struct *base, int main } void -rb_parser_set_script_lines(VALUE vparser, VALUE lines) +rb_parser_set_script_lines(VALUE vparser) { struct ruby_parser *parser; TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); - rb_ruby_parser_set_script_lines(parser->parser_params, lines); + rb_ruby_parser_set_script_lines(parser->parser_params); } void @@ -728,65 +623,216 @@ rb_parser_error_tolerant(VALUE vparser) rb_ruby_parser_error_tolerant(parser->parser_params); } -rb_ast_t* -rb_parser_compile_file_path(VALUE vparser, VALUE fname, VALUE file, int start) +void +rb_parser_keep_tokens(VALUE vparser) { struct ruby_parser *parser; + + TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); + rb_ruby_parser_keep_tokens(parser->parser_params); +} + +VALUE +rb_parser_lex_get_str(struct lex_pointer_string *ptr_str) +{ + char *beg, *end, *start; + long len; + VALUE s = ptr_str->str; + + beg = RSTRING_PTR(s); + len = RSTRING_LEN(s); + start = beg; + if (ptr_str->ptr) { + if (len == ptr_str->ptr) return Qnil; + beg += ptr_str->ptr; + len -= ptr_str->ptr; + } + end = memchr(beg, '\n', len); + if (end) len = ++end - beg; + ptr_str->ptr += len; + return rb_str_subseq(s, beg - start, len); +} + +static VALUE +lex_get_str(struct parser_params *p, rb_parser_input_data input, int line_count) +{ + return rb_parser_lex_get_str((struct lex_pointer_string *)input); +} + +static rb_ast_t* +parser_compile_string0(struct ruby_parser *parser, VALUE fname, VALUE s, int line) +{ + VALUE str = rb_str_new_frozen(s); + + parser->type = lex_type_str; + parser->data.lex_str.str = str; + parser->data.lex_str.ptr = 0; + + return rb_parser_compile(parser->parser_params, lex_get_str, fname, (rb_parser_input_data)&parser->data, line); +} + +static rb_encoding * +must_be_ascii_compatible(VALUE s) +{ + rb_encoding *enc = rb_enc_get(s); + if (!rb_enc_asciicompat(enc)) { + rb_raise(rb_eArgError, "invalid source encoding"); + } + return enc; +} + +static rb_ast_t* +parser_compile_string_path(struct ruby_parser *parser, VALUE f, VALUE s, int line) +{ + must_be_ascii_compatible(s); + return parser_compile_string0(parser, f, s, line); +} + +static rb_ast_t* +parser_compile_string(struct ruby_parser *parser, const char *f, VALUE s, int line) +{ + return parser_compile_string_path(parser, rb_filesystem_str_new_cstr(f), s, line); +} + +VALUE rb_io_gets_internal(VALUE io); + +static VALUE +lex_io_gets(struct parser_params *p, rb_parser_input_data input, int line_count) +{ + VALUE io = (VALUE)input; + + return rb_io_gets_internal(io); +} + +static VALUE +lex_gets_array(struct parser_params *p, rb_parser_input_data data, int index) +{ + VALUE array = (VALUE)data; + VALUE str = rb_ary_entry(array, index); + if (!NIL_P(str)) { + StringValue(str); + if (!rb_enc_asciicompat(rb_enc_get(str))) { + rb_raise(rb_eArgError, "invalid source encoding"); + } + } + return str; +} + +static rb_ast_t* +parser_compile_file_path(struct ruby_parser *parser, VALUE fname, VALUE file, int start) +{ + parser->type = lex_type_io; + parser->data.lex_io.file = file; + + return rb_parser_compile(parser->parser_params, lex_io_gets, fname, (rb_parser_input_data)file, start); +} + +static rb_ast_t* +parser_compile_array(struct ruby_parser *parser, VALUE fname, VALUE array, int start) +{ + parser->type = lex_type_array; + parser->data.lex_array.ary = array; + + return rb_parser_compile(parser->parser_params, lex_gets_array, fname, (rb_parser_input_data)array, start); +} + +static rb_ast_t* +parser_compile_generic(struct ruby_parser *parser, rb_parser_lex_gets_func *lex_gets, VALUE fname, VALUE input, int start) +{ + parser->type = lex_type_generic; + + return rb_parser_compile(parser->parser_params, lex_gets, fname, (rb_parser_input_data)input, start); +} + +static void +ast_free(void *ptr) +{ + rb_ast_t *ast = (rb_ast_t *)ptr; + if (ast) { + rb_ast_free(ast); + } +} + +static const rb_data_type_t ast_data_type = { + "AST", + { + NULL, + ast_free, + NULL, // No dsize() because this object does not appear in ObjectSpace. + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY +}; + +static VALUE +ast_alloc(void) +{ rb_ast_t *ast; + return TypedData_Make_Struct(0, rb_ast_t, &ast_data_type, ast); +} + +VALUE +rb_parser_compile_file_path(VALUE vparser, VALUE fname, VALUE file, int start) +{ + struct ruby_parser *parser; + VALUE vast = ast_alloc(); TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); - ast = rb_ruby_parser_compile_file_path(parser->parser_params, fname, file, start); + DATA_PTR(vast) = parser_compile_file_path(parser, fname, file, start); RB_GC_GUARD(vparser); - return ast; + return vast; } -void -rb_parser_keep_tokens(VALUE vparser) +VALUE +rb_parser_compile_array(VALUE vparser, VALUE fname, VALUE array, int start) { struct ruby_parser *parser; + VALUE vast = ast_alloc(); TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); - rb_ruby_parser_keep_tokens(parser->parser_params); + DATA_PTR(vast) = parser_compile_array(parser, fname, array, start); + RB_GC_GUARD(vparser); + + return vast; } -rb_ast_t* -rb_parser_compile_generic(VALUE vparser, VALUE (*lex_gets)(VALUE, int), VALUE fname, VALUE input, int start) +VALUE +rb_parser_compile_generic(VALUE vparser, rb_parser_lex_gets_func *lex_gets, VALUE fname, VALUE input, int start) { struct ruby_parser *parser; - rb_ast_t *ast; + VALUE vast = ast_alloc(); TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); - ast = rb_ruby_parser_compile_generic(parser->parser_params, lex_gets, fname, input, start); + DATA_PTR(vast) = parser_compile_generic(parser, lex_gets, fname, input, start); RB_GC_GUARD(vparser); - return ast; + return vast; } -rb_ast_t* +VALUE rb_parser_compile_string(VALUE vparser, const char *f, VALUE s, int line) { struct ruby_parser *parser; - rb_ast_t *ast; + VALUE vast = ast_alloc(); TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); - ast = rb_ruby_parser_compile_string(parser->parser_params, f, s, line); + DATA_PTR(vast) = parser_compile_string(parser, f, s, line); RB_GC_GUARD(vparser); - return ast; + return vast; } -rb_ast_t* +VALUE rb_parser_compile_string_path(VALUE vparser, VALUE f, VALUE s, int line) { struct ruby_parser *parser; - rb_ast_t *ast; + VALUE vast = ast_alloc(); TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); - ast = rb_ruby_parser_compile_string_path(parser->parser_params, f, s, line); + DATA_PTR(vast) = parser_compile_string_path(parser, f, s, line); RB_GC_GUARD(vparser); - return ast; + return vast; } VALUE @@ -795,7 +841,7 @@ rb_parser_encoding(VALUE vparser) struct ruby_parser *parser; TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); - return rb_ruby_parser_encoding(parser->parser_params); + return rb_enc_from_encoding(rb_ruby_parser_encoding(parser->parser_params)); } VALUE @@ -816,10 +862,48 @@ rb_parser_set_yydebug(VALUE vparser, VALUE flag) rb_ruby_parser_set_yydebug(parser->parser_params, RTEST(flag)); return flag; } -#endif + +void +rb_set_script_lines_for(VALUE vparser, VALUE path) +{ + struct ruby_parser *parser; + VALUE hash; + ID script_lines; + CONST_ID(script_lines, "SCRIPT_LINES__"); + if (!rb_const_defined_at(rb_cObject, script_lines)) return; + hash = rb_const_get_at(rb_cObject, script_lines); + if (RB_TYPE_P(hash, T_HASH)) { + rb_hash_aset(hash, path, Qtrue); + TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); + rb_ruby_parser_set_script_lines(parser->parser_params); + } +} + +VALUE +rb_parser_build_script_lines_from(rb_parser_ary_t *lines) +{ + int i; + if (lines->data_type != PARSER_ARY_DATA_SCRIPT_LINE) { + rb_bug("unexpected rb_parser_ary_data_type (%d) for script lines", lines->data_type); + } + VALUE script_lines = rb_ary_new_capa(lines->len); + for (i = 0; i < lines->len; i++) { + rb_parser_string_t *str = (rb_parser_string_t *)lines->data[i]; + rb_ary_push(script_lines, rb_enc_str_new(str->ptr, str->len, str->enc)); + } + return script_lines; +} 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); } @@ -1016,53 +1100,40 @@ rb_node_encoding_val(const NODE *node) return rb_enc_from_encoding(RNODE_ENCODING(node)->enc); } +void +rb_parser_aset_script_lines_for(VALUE path, rb_parser_ary_t *lines) +{ + VALUE hash, script_lines; + ID script_lines_id; + if (NIL_P(path) || !lines || FIXNUM_P((VALUE)lines)) return; + CONST_ID(script_lines_id, "SCRIPT_LINES__"); + if (!rb_const_defined_at(rb_cObject, script_lines_id)) return; + hash = rb_const_get_at(rb_cObject, script_lines_id); + if (!RB_TYPE_P(hash, T_HASH)) return; + if (rb_hash_lookup(hash, path) == Qnil) return; + script_lines = rb_parser_build_script_lines_from(lines); + rb_hash_aset(hash, path, script_lines); +} + 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); - } +rb_ruby_ast_new(const NODE *const root, rb_parser_ary_t *script_lines) +{ + VALUE vast = ast_alloc(); + rb_ast_t *ast = DATA_PTR(vast); + ast->body = (rb_ast_body_t){ + .root = root, + .frozen_string_literal = -1, + .coverage_enabled = -1, + .script_lines = script_lines + }; + return vast; +} - 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_ast_t * +rb_ruby_ast_data_get(VALUE vast) +{ + rb_ast_t *ast; + if (NIL_P(vast)) return NULL; + TypedData_Get_Struct(vast, rb_ast_t, &ast_data_type, ast); + return ast; } diff --git a/rubyparser.h b/rubyparser.h index 34ee117f650974..55a49a2b3285ae 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,15 @@ 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, +}; + +typedef void* rb_parser_input_data; + /* * AST Node */ @@ -117,7 +138,6 @@ enum node_type { NODE_MATCH, NODE_MATCH2, NODE_MATCH3, - NODE_LIT, NODE_INTEGER, NODE_FLOAT, NODE_RATIONAL, @@ -188,6 +208,32 @@ 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 void* rb_parser_ary_data; + +enum rb_parser_ary_data_type { + PARSER_ARY_DATA_AST_TOKEN, + PARSER_ARY_DATA_SCRIPT_LINE +}; + +typedef struct rb_parser_ary { + enum rb_parser_ary_data_type data_type; + rb_parser_ary_data *data; + long len; // current size + long capa; // capacity +} rb_parser_ary_t; /* Header part of AST Node */ typedef struct RNode { @@ -414,6 +460,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 +509,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 +684,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 +695,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 +717,7 @@ enum rb_numeric_type { typedef struct RNode_IMAGINARY { NODE node; - char* val; + char *val; int minus; int base; int seen_point; @@ -773,7 +815,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 +1134,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 +1188,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 */ @@ -1167,18 +1208,17 @@ typedef struct RNode_ERROR { (n)->flags=(((n)->flags&~NODE_TYPEMASK)|((((unsigned long)(t))<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; @@ -1208,9 +1213,7 @@ rb_shape_find_by_id(VALUE mod, VALUE id) void Init_default_shapes(void) { - rb_shape_tree_t *st = ruby_mimmalloc(sizeof(rb_shape_tree_t)); - memset(st, 0, sizeof(rb_shape_tree_t)); - rb_shape_tree_ptr = st; + rb_shape_tree_ptr = xcalloc(1, sizeof(rb_shape_tree_t)); #ifdef HAVE_MMAP rb_shape_tree_ptr->shape_list = (rb_shape_t *)mmap(NULL, rb_size_mul_or_raise(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t), rb_eRuntimeError), @@ -1243,11 +1246,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 +1254,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 +1309,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/add_spec.rb b/spec/bundler/commands/add_spec.rb index e2f5bbf42fad0e..36e286793b6d73 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -175,6 +175,61 @@ end end + describe "with --git and --glob" do + it "adds dependency with specified git source" do + bundle "add foo --git=#{lib_path("foo-2.0")} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --git and --branch and --glob" do + before do + update_git "foo", "2.0", branch: "test" + end + + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --branch=test --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :branch => "test", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --git and --ref and --glob" do + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --ref=#{revision_for(lib_path("foo-2.0"))} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2\.0", :git => "#{lib_path("foo-2.0")}", :ref => "#{revision_for(lib_path("foo-2.0"))}", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --github and --glob" do + it "adds dependency with specified github source", :realworld do + bundle "add rake --github=ruby/rake --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :glob => "\.\/\*\.gemspec"}) + end + end + + describe "with --github and --branch --and glob" do + it "adds dependency with specified github source and branch", :realworld do + bundle "add rake --github=ruby/rake --branch=master --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :branch => "master", :glob => "\.\/\*\.gemspec"}) + end + end + + describe "with --github and --ref and --glob" do + it "adds dependency with specified github source and ref", :realworld do + bundle "add rake --github=ruby/rake --ref=5c60da8 --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :ref => "5c60da8", :glob => "\.\/\*\.gemspec"}) + end + end + describe "with --skip-install" do it "adds gem to Gemfile but is not installed" do bundle "add foo --skip-install --version=2.0" 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/help_spec.rb b/spec/bundler/commands/help_spec.rb index 535df8e35a2ae8..0c7031e813cbeb 100644 --- a/spec/bundler/commands/help_spec.rb +++ b/spec/bundler/commands/help_spec.rb @@ -15,6 +15,13 @@ expect(out).to eq(%(["#{man_dir}/bundle-install.1"])) end + it "prexifes bundle commands with bundle- and resolves aliases when finding the man files" do + with_fake_man do + bundle "help package" + end + expect(out).to eq(%(["#{man_dir}/bundle-cache.1"])) + end + it "simply outputs the human readable file when there is no man on the path" do with_path_as("") do bundle "help install" 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/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb index 80029783682a62..d89fdea6f105fd 100644 --- a/spec/bundler/install/deploy_spec.rb +++ b/spec/bundler/install/deploy_spec.rb @@ -183,50 +183,10 @@ bundle "config set --local deployment true" end - it "prevents the replace by default" do - bundle :install, raise_on_error: false - - expect(err).to match(/The list of sources changed/) - end - - context "when allow_deployment_source_credential_changes is true" do - before { bundle "config set allow_deployment_source_credential_changes true" } - - it "allows the replace" do - bundle :install - - expect(out).to match(/Bundle complete!/) - end - end - - context "when allow_deployment_source_credential_changes is false" do - before { bundle "config set allow_deployment_source_credential_changes false" } - - it "prevents the replace" do - bundle :install, raise_on_error: false - - expect(err).to match(/The list of sources changed/) - end - end - - context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is true" do - before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "true" } - - it "allows the replace" do - bundle :install - - expect(out).to match(/Bundle complete!/) - end - end - - context "when BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES env var is false" do - before { ENV["BUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES"] = "false" } - - it "prevents the replace" do - bundle :install, raise_on_error: false + it "allows the replace" do + bundle :install - expect(err).to match(/The list of sources changed/) - end + expect(out).to match(/Bundle complete!/) end end 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..5f1b034bfc0de1 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} @@ -1262,43 +1262,47 @@ end end - it "adds current musl platform" do - build_repo4 do - build_gem "rcee_precompiled", "0.5.0" do |s| - s.platform = "x86_64-linux" - end + ["x86_64-linux", "x86_64-linux-musl"].each do |host_platform| + describe "on host platform #{host_platform}" do + it "adds current musl platform" do + build_repo4 do + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux" + end + + build_gem "rcee_precompiled", "0.5.0" do |s| + s.platform = "x86_64-linux-musl" + end + end - build_gem "rcee_precompiled", "0.5.0" do |s| - s.platform = "x86_64-linux-musl" - end - end + gemfile <<~G + source "#{file_uri_for(gem_repo4)}" - gemfile <<~G - source "#{file_uri_for(gem_repo4)}" + gem "rcee_precompiled", "0.5.0" + G - gem "rcee_precompiled", "0.5.0" - G + simulate_platform host_platform do + bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } - simulate_platform "x86_64-linux-musl" do - bundle "lock", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s } + expect(lockfile).to eq(<<~L) + GEM + remote: #{file_uri_for(gem_repo4)}/ + specs: + rcee_precompiled (0.5.0-x86_64-linux) + rcee_precompiled (0.5.0-x86_64-linux-musl) - expect(lockfile).to eq(<<~L) - GEM - remote: #{file_uri_for(gem_repo4)}/ - specs: - rcee_precompiled (0.5.0-x86_64-linux) - rcee_precompiled (0.5.0-x86_64-linux-musl) + PLATFORMS + x86_64-linux + x86_64-linux-musl - PLATFORMS - x86_64-linux - x86_64-linux-musl + DEPENDENCIES + rcee_precompiled (= 0.5.0) - DEPENDENCIES - rcee_precompiled (= 0.5.0) - - BUNDLED WITH - #{Bundler::VERSION} - L + BUNDLED WITH + #{Bundler::VERSION} + L + end + end end end 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/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb index 7f664abc4d8e7f..4fd081e7d0ad28 100644 --- a/spec/bundler/lock/lockfile_spec.rb +++ b/spec/bundler/lock/lockfile_spec.rb @@ -324,7 +324,7 @@ G end - it "generates a lockfile without credentials for a configured source" do + it "generates a lockfile without credentials" do bundle "config set http://localgemserver.test/ user:pass" install_gemfile(<<-G, artifice: "endpoint_strict_basic_authentication", quiet: true) @@ -354,7 +354,7 @@ specs: GEM - remote: http://user:pass@othergemserver.test/ + remote: http://othergemserver.test/ specs: rack (1.0.0) rack-obama (1.0) 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/hook_spec.rb b/spec/bundler/plugins/hook_spec.rb index 72feb14d84ee08..f6ee0ba210c898 100644 --- a/spec/bundler/plugins/hook_spec.rb +++ b/spec/bundler/plugins/hook_spec.rb @@ -106,4 +106,103 @@ expect(out).to include "installed gem rack : installed" end end + + context "before-require-all hook" do + before do + build_repo2 do + build_plugin "before-require-all-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_REQUIRE_ALL do |deps| + puts "gems to be required \#{deps.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install before-require-all-plugin --source #{file_uri_for(gem_repo2)}" + end + + it "runs before all rubygems are required" do + install_gemfile_and_bundler_require + expect(out).to include "gems to be required rake, rack" + end + end + + context "before-require hook" do + before do + build_repo2 do + build_plugin "before-require-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_REQUIRE do |dep| + puts "requiring gem \#{dep.name}" + end + RUBY + end + end + + bundle "plugin install before-require-plugin --source #{file_uri_for(gem_repo2)}" + end + + it "runs before each rubygem is required" do + install_gemfile_and_bundler_require + expect(out).to include "requiring gem rake" + expect(out).to include "requiring gem rack" + end + end + + context "after-require-all hook" do + before do + build_repo2 do + build_plugin "after-require-all-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_REQUIRE_ALL do |deps| + puts "required gems \#{deps.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install after-require-all-plugin --source #{file_uri_for(gem_repo2)}" + end + + it "runs after all rubygems are required" do + install_gemfile_and_bundler_require + expect(out).to include "required gems rake, rack" + end + end + + context "after-require hook" do + before do + build_repo2 do + build_plugin "after-require-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_REQUIRE do |dep| + puts "required gem \#{dep.name}" + end + RUBY + end + end + + bundle "plugin install after-require-plugin --source #{file_uri_for(gem_repo2)}" + end + + it "runs after each rubygem is required" do + install_gemfile_and_bundler_require + expect(out).to include "required gem rake" + expect(out).to include "required gem rack" + end + end + + def install_gemfile_and_bundler_require + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rake" + gem "rack" + G + + ruby <<-RUBY + require "bundler" + Bundler.require + RUBY + end end 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/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb index ffac30d6d83084..50a5258dc748ec 100644 --- a/spec/bundler/runtime/inline_spec.rb +++ b/spec/bundler/runtime/inline_spec.rb @@ -638,4 +638,22 @@ def confirm(msg, newline = nil) expect(out).to include("Installing timeout 999") end + + it "does not upcase ENV" do + script <<-RUBY + require 'bundler/inline' + + ENV['Test_Variable'] = 'value string' + puts("before: \#{ENV.each_key.select { |key| key.match?(/test_variable/i) }}") + + gemfile do + source "#{file_uri_for(gem_repo1)}" + end + + puts("after: \#{ENV.each_key.select { |key| key.match?(/test_variable/i) }}") + RUBY + + expect(out).to include("before: [\"Test_Variable\"]") + expect(out).to include("after: [\"Test_Variable\"]") + end end diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index ccfe5d55b68337..0344d24223fd07 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -1599,4 +1599,19 @@ def require(path) sys_exec "#{Gem.ruby} #{script}", raise_on_error: false expect(out).to include("requiring foo used the monkeypatch") end + + it "performs an automatic bundle install" do + gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + gem "rack", :group => :test + G + + bundle "config set auto_install 1" + + ruby <<-RUBY + require 'bundler/setup' + RUBY + expect(err).to be_empty + expect(out).to include("Installing rack 1.0.0") + end end diff --git a/spec/bundler/runtime/with_unbundled_env_spec.rb b/spec/bundler/runtime/with_unbundled_env_spec.rb index 84b198cfb604f0..135c71b0af77f4 100644 --- a/spec/bundler/runtime/with_unbundled_env_spec.rb +++ b/spec/bundler/runtime/with_unbundled_env_spec.rb @@ -139,7 +139,7 @@ def run_bundler_script(env, script) describe "Bundler.with_original_env" do it "should set ENV to original_env in the block" do expected = Bundler.original_env - actual = Bundler.with_original_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) } + actual = Bundler.with_original_env { ENV.to_hash } expect(actual).to eq(expected) end @@ -157,7 +157,7 @@ def run_bundler_script(env, script) expected = Bundler.unbundled_env actual = Bundler.ui.silence do - Bundler.with_clean_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) } + Bundler.with_clean_env { ENV.to_hash } end expect(actual).to eq(expected) @@ -175,7 +175,7 @@ def run_bundler_script(env, script) describe "Bundler.with_unbundled_env" do it "should set ENV to unbundled_env in the block" do expected = Bundler.unbundled_env - actual = Bundler.with_unbundled_env { Bundler::EnvironmentPreserver.env_to_hash(ENV) } + actual = Bundler.with_unbundled_env { ENV.to_hash } expect(actual).to eq(expected) end 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..ff84fb053da5ba --- /dev/null +++ b/spec/prism.mspec @@ -0,0 +1,40 @@ +# 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, "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, "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/inspect_spec.rb b/spec/ruby/core/encoding/inspect_spec.rb index 9a930b2a77608e..df96141db90498 100644 --- a/spec/ruby/core/encoding/inspect_spec.rb +++ b/spec/ruby/core/encoding/inspect_spec.rb @@ -5,9 +5,23 @@ Encoding::UTF_8.inspect.should be_an_instance_of(String) end - it "returns # for a non-dummy encoding named 'name'" do - Encoding.list.to_a.reject {|e| e.dummy? }.each do |enc| - enc.inspect.should =~ /#/ + ruby_version_is ""..."3.4" do + it "returns # for a non-dummy encoding named 'name'" do + Encoding.list.to_a.reject {|e| e.dummy? }.each do |enc| + enc.inspect.should =~ /#/ + end + end + end + + ruby_version_is "3.4" do + it "returns # for a non-dummy encoding named 'name'" do + Encoding.list.to_a.reject {|e| e.dummy? }.each do |enc| + if enc.name == "ASCII-8BIT" + enc.inspect.should == "#" + else + enc.inspect.should =~ /#/ + end + end end end 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/fiber/raise_spec.rb b/spec/ruby/core/fiber/raise_spec.rb index eb4b39c8be55df..b3e021e6360b7a 100644 --- a/spec/ruby/core/fiber/raise_spec.rb +++ b/spec/ruby/core/fiber/raise_spec.rb @@ -91,6 +91,40 @@ fiber_two.resume.should == [:yield_one, :rescued] end + + ruby_version_is "3.4" do + it "raises on the resumed fiber" do + root_fiber = Fiber.current + f1 = Fiber.new { root_fiber.transfer } + f2 = Fiber.new { f1.resume } + f2.transfer + + -> do + f2.raise(RuntimeError, "Expected error") + end.should raise_error(RuntimeError, "Expected error") + end + + it "raises on itself" do + -> do + Fiber.current.raise(RuntimeError, "Expected error") + end.should raise_error(RuntimeError, "Expected error") + end + + it "should raise on parent fiber" do + f2 = nil + f1 = Fiber.new do + # This is equivalent to Kernel#raise: + f2.raise(RuntimeError, "Expected error") + end + f2 = Fiber.new do + f1.resume + end + + -> do + f2.resume + end.should raise_error(RuntimeError, "Expected error") + end + end end 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/thread/each_caller_location_spec.rb b/spec/ruby/core/thread/each_caller_location_spec.rb index dbece06cd8183e..29c271789b5225 100644 --- a/spec/ruby/core/thread/each_caller_location_spec.rb +++ b/spec/ruby/core/thread/each_caller_location_spec.rb @@ -40,10 +40,10 @@ }.should raise_error(LocalJumpError, "no block given") end - it "doesn't accept positional and keyword arguments" do + it "doesn't accept keyword arguments" do -> { Thread.each_caller_location(12, foo: 10) {} - }.should raise_error(ArgumentError, "wrong number of arguments (given 2, expected 0)") + }.should raise_error(ArgumentError); end end 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/core/time/fixtures/classes.rb b/spec/ruby/core/time/fixtures/classes.rb index 1a9511b261c36f..21c4e1effb782b 100644 --- a/spec/ruby/core/time/fixtures/classes.rb +++ b/spec/ruby/core/time/fixtures/classes.rb @@ -59,7 +59,6 @@ def self.result Zone = Struct.new(:std, :dst, :dst_range) Zones = { "Asia/Colombo" => Zone[Z[5*3600+30*60, "MMT"], nil, nil], - "Europe/Kiev" => Zone[Z[2*3600, "EET"], Z[3*3600, "EEST"], 4..10], "PST" => Zone[Z[(-9*60*60), "PST"], nil, nil], } 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/break_spec.rb b/spec/ruby/language/break_spec.rb index 627cb4a071fca4..e725e77e80d0b3 100644 --- a/spec/ruby/language/break_spec.rb +++ b/spec/ruby/language/break_spec.rb @@ -372,7 +372,7 @@ def three end.should_not raise_error end - it "raises LocalJumpError when converted into a proc during a a super call" do + it "raises LocalJumpError when converted into a proc during a super call" do cls1 = Class.new { def foo(&b); b; end } cls2 = Class.new(cls1) { def foo; super { break 1 }.call; 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