diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0ea0e49076081f..430a2c950d21b6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -80,15 +80,15 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8 + uses: github/codeql-action/init@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8 + uses: github/codeql-action/autobuild@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8 + uses: github/codeql-action/analyze@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 with: category: '/language:${{ matrix.language }}' upload: False @@ -118,7 +118,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8 + uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 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 9a7855b78475b5..4b58d9d10a1cb6 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,7 +214,7 @@ 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: >- diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml index 5858507ae73763..ca9e63f1ec8749 100644 --- a/.github/workflows/dependabot_automerge.yml +++ b/.github/workflows/dependabot_automerge.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Dependabot metadata - uses: dependabot/fetch-metadata@c9c4182bf1b97f5224aee3906fd373f6b61b4526 # v1.6.0 + uses: dependabot/fetch-metadata@0fb21704c18a42ce5aa8d720ea4b912f5e6babef # v2.0.0 id: metadata - name: Wait for status checks diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c5d88e30d70a6c..c89635ea822430 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -88,7 +88,7 @@ jobs: - name: Set up Launchable uses: ./.github/actions/launchable/setup with: - os: ${{ matrix.os }} + os: ${{ matrix.os || (github.repository == 'ruby/ruby' && 'macos-arm-oss' || 'macos-14')}} test-opts: ${{ matrix.test_opts }} launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} builddir: build @@ -105,7 +105,7 @@ jobs: timeout-minutes: 60 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'minitest,test-unit,debug,bigdecimal,drb,typeprof' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' - name: make skipped tests diff --git a/.github/workflows/prism.yml b/.github/workflows/prism.yml index 49058c232f616a..7fc10e2ab12775 100644 --- a/.github/workflows/prism.yml +++ b/.github/workflows/prism.yml @@ -92,7 +92,7 @@ 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="prism/encoding_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-prism-spec diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 51bce58de44e81..2889d4d47c4747 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -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@05963f47d870e2cb19a537396c1f668a348c7d8f # v2.1.27 + uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v2.1.27 with: sarif_file: results.sarif diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 62b55845b9e8d8..62e1b564b8828b 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -118,7 +118,7 @@ jobs: timeout-minutes: 40 env: RUBY_TESTOPTS: '-q --tty=no' - TEST_BUNDLED_GEMS_ALLOW_FAILURES: 'minitest,test-unit,debug,bigdecimal,drb,typeprof' + TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' PRECHECK_BUNDLED_GEMS: 'no' - name: make skipped tests diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index d93fb24e419bac..b74629340f0a7e 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -185,7 +185,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' SYNTAX_SUGGEST_TIMEOUT: '5' YJIT_BINDGEN_DIFF_OPTS: '--exit-code' 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 83e5a4073fc9a7..77e48d8ca0ef38 100644 --- a/NEWS.md +++ b/NEWS.md @@ -46,7 +46,7 @@ The following default gems are updated. * net-http 0.4.1 * prism 0.24.0 * rdoc 6.6.3.1 -* reline 0.5.0.pre.1 +* reline 0.5.0 * resolv 0.4.0 * stringio 3.1.1 * strscan 3.1.1 @@ -54,13 +54,14 @@ The following default gems are updated. The following bundled gems are updated. * minitest 5.22.3 +* rake 13.2.0 * 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. diff --git a/array.c b/array.c index 00f5ab91812d82..bcf98fc0125ae1 100644 --- a/array.c +++ b/array.c @@ -6643,6 +6643,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/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_syntax.rb b/bootstraptest/test_syntax.rb index 18528db7a53396..44bd697d4f9848 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]' diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 3eef86e0a9f4b7..6b7d4839265d04 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -4725,3 +4725,64 @@ def test_cases(file, chain) test_cases(File, Enumerator::Chain) } + +# singleton class should invalidate Type::CString assumption +assert_equal 'foo', %q{ + def define_singleton(str, define) + if define + # Wrap a C method frame to avoid exiting JIT code on defineclass + [nil].reverse_each do + class << str + def +(_) + "foo" + end + end + end + end + "bar" + end + + def entry(define) + str = "" + # When `define` is false, #+ compiles to rb_str_plus() without a class guard. + # When the code is reused with `define` is true, the class of `str` is changed + # to a singleton class, so the block should be invalidated. + str + define_singleton(str, define) + end + + entry(false) + entry(true) +} + +assert_equal '[:ok, :ok, :ok]', %q{ + def identity(x) = x + def foo(x, _) = x + def bar(_, _, _, _, x) = x + + def tests + [ + identity(:ok), + foo(:ok, 2), + bar(1, 2, 3, 4, :ok), + ] + end + + tests +} + +# test integer left shift with constant rhs +assert_equal [0x80000000000, 'a+', :ok].inspect, %q{ + def shift(val) = val << 43 + + def tests + int = shift(1) + str = shift("a") + + Integer.define_method(:<<) { |_| :ok } + redef = shift(1) + + [int, str, redef] + end + + tests +} diff --git a/class.c b/class.c index 46b8c1f74a5a6b..879c0ead2af2ed 100644 --- a/class.c +++ b/class.c @@ -29,6 +29,7 @@ #include "internal/variable.h" #include "ruby/st.h" #include "vm_core.h" +#include "yjit.h" /* Flags of T_CLASS * @@ -805,6 +806,7 @@ make_singleton_class(VALUE obj) FL_SET(klass, FL_SINGLETON); RBASIC_SET_CLASS(obj, klass); rb_singleton_class_attached(klass, obj); + rb_yjit_invalidate_no_singleton_class(orig_class); SET_METACLASS_OF(klass, METACLASS_OF(rb_class_real(orig_class))); return klass; diff --git a/common.mk b/common.mk index 9401127651b672..173b4fb2a725bd 100644 --- a/common.mk +++ b/common.mk @@ -471,8 +471,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 $@ @@ -732,10 +730,10 @@ clean-local:: clean-runnable bin/clean-runnable:: PHONY $(Q)$(CHDIR) bin 2>$(NULL) && $(RM) $(PROGRAM) $(WPROGRAM) $(GORUBY)$(EXEEXT) bin/*.$(DLEXT) 2>$(NULL) || $(NULLCMD) lib/clean-runnable:: PHONY - $(Q)$(CHDIR) lib 2>$(NULL) && $(RM) $(LIBRUBY_A) $(LIBRUBY) $(LIBRUBY_ALIASES) $(RUBY_BASE_NAME)/$(RUBY_PROGRAM_VERSION) $(RUBY_BASE_NAME)/vendor_ruby 2>$(NULL) || $(NULLCMD) + $(Q)$(CHDIR) lib 2>$(NULL) && $(RM) $(LIBRUBY_A) $(LIBRUBY) $(LIBRUBY_ALIASES) $(RUBY_BASE_NAME)/$(ruby_version) $(RUBY_BASE_NAME)/vendor_ruby 2>$(NULL) || $(NULLCMD) clean-runnable:: bin/clean-runnable lib/clean-runnable PHONY $(Q)$(RMDIR) lib/$(RUBY_BASE_NAME) lib bin 2>$(NULL) || $(NULLCMD) - -$(Q)$(RM) $(EXTOUT)/$(arch)/rbconfig.rb + -$(Q)$(RM) $(EXTOUT)/$(arch)/rbconfig.rb $(EXTOUT)/common/$(arch) -$(Q)$(RMALL) exe/ clean-ext:: PHONY clean-golf: PHONY @@ -3108,6 +3106,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 diff --git a/compile.c b/compile.c index 5516f64f54621f..a66d5e1b93d81a 100644 --- a/compile.c +++ b/compile.c @@ -999,6 +999,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; @@ -11317,6 +11318,7 @@ iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *const anchor, } } DATA_PTR(labels_wrapper) = 0; + RB_GC_GUARD(labels_wrapper); validate_labels(iseq, labels_table); if (!ret) return ret; return iseq_setup(iseq, anchor); diff --git a/configure.ac b/configure.ac index 530c073764caa2..7badc5f46a8849 100644 --- a/configure.ac +++ b/configure.ac @@ -75,8 +75,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 +86,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) diff --git a/cont.c b/cont.c index b07b5e380f4b0a..c6a94e0709f369 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); 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/doc/ChangeLog/ChangeLog-2.1.0 b/doc/ChangeLog/ChangeLog-2.1.0 index 5b670b31c97c8c..7964a682ebd9c8 100644 --- a/doc/ChangeLog/ChangeLog-2.1.0 +++ b/doc/ChangeLog/ChangeLog-2.1.0 @@ -659,7 +659,7 @@ Mon Dec 9 02:10:32 2013 NARUSE, Yui * lib/net/http/responses.rb: Add `HTTPIMUsed`, as it is also supported by rack/rails. - RFC - http://tools.ietf.org/html/rfc3229 + RFC - https://www.rfc-editor.org/rfc/rfc3229 by Vipul A M https://github.com/ruby/ruby/pull/447 fix GH-447 diff --git a/doc/ChangeLog/ChangeLog-2.2.0 b/doc/ChangeLog/ChangeLog-2.2.0 index 5a7dbf826d3135..0edcf0122b190d 100644 --- a/doc/ChangeLog/ChangeLog-2.2.0 +++ b/doc/ChangeLog/ChangeLog-2.2.0 @@ -5648,7 +5648,7 @@ Wed Aug 6 04:16:05 2014 NARUSE, Yui * lib/net/http/requests.rb (Net::HTTP::Options::RESPONSE_HAS_BODY): OPTIONS requests may have response bodies. [Feature #8429] - http://tools.ietf.org/html/rfc7231#section-4.3.7 + https://www.rfc-editor.org/rfc/rfc7231#section-4.3.7 Wed Aug 6 03:18:04 2014 NARUSE, Yui diff --git a/doc/ChangeLog/ChangeLog-2.4.0 b/doc/ChangeLog/ChangeLog-2.4.0 index a297a579d1d4ba..30e9ccab3d9aac 100644 --- a/doc/ChangeLog/ChangeLog-2.4.0 +++ b/doc/ChangeLog/ChangeLog-2.4.0 @@ -7356,7 +7356,7 @@ Thu Mar 17 11:51:48 2016 NARUSE, Yui Note: CryptGenRandom function is PRNG and doesn't check its entropy, so it won't block. [Bug #12139] https://msdn.microsoft.com/ja-jp/library/windows/desktop/aa379942.aspx - https://tools.ietf.org/html/rfc4086#section-7.1.3 + https://www.rfc-editor.org/rfc/rfc4086#section-7.1.3 https://eprint.iacr.org/2007/419.pdf http://www.cs.huji.ac.il/~dolev/pubs/thesis/msc-thesis-leo.pdf diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index 18464458896b74..2991385b94a6e6 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -181,12 +181,16 @@ mkdir build && cd build ../configure CC=clang cflags="-fsanitize=address -fno-omit-frame-pointer -DUSE_MN_THREADS=0" # and any other options you might like make ``` -The compiled Ruby will now automatically crash with a report and a backtrace if ASAN detects a memory safety issue. +The compiled Ruby will now automatically crash with a report and a backtrace if ASAN detects a memory safety issue. To run Ruby's test suite under ASAN, issue the following command. Note that this will take quite a long time (over two hours on my laptop); the `RUBY_TEST_TIMEOUT_SCALE` and `SYNTAX_SUGEST_TIMEOUT` variables are required to make sure tests don't spuriously fail with timeouts when in fact they're just slow. + +``` shell +RUBY_TEST_TIMEOUT_SCALE=5 SYNTAX_SUGGEST_TIMEOUT=600 make check +``` Please note, however, the following caveats! -* ASAN will not work properly on any currently released version of Ruby; the necessary support is currently only present on Ruby's master branch. -* Due to [this bug](https://bugs.ruby-lang.org/issues/20243), Clang generates code for threadlocal variables which doesn't work with M:N threading. Thus, it's necessary to disable M:N threading support at build time for now. +* ASAN will not work properly on any currently released version of Ruby; the necessary support is currently only present on Ruby's master branch (and the whole test suite passes only as of commit [9d0a5148ae062a0481a4a18fbeb9cfd01dc10428](https://bugs.ruby-lang.org/projects/ruby-master/repository/git/revisions/9d0a5148ae062a0481a4a18fbeb9cfd01dc10428)) +* Due to [this bug](https://bugs.ruby-lang.org/issues/20243), Clang generates code for threadlocal variables which doesn't work with M:N threading. Thus, it's necessary to disable M:N threading support at build time for now (with the `-DUSE_MN_THREADS=0` configure argument). * Currently, ASAN will only work correctly when using a recent head build of LLVM/Clang - it requires [this bugfix](https://github.com/llvm/llvm-project/pull/75290) related to multithreaded `fork`, which is not yet in any released version. See [here](https://llvm.org/docs/CMake.html) for instructions on how to build LLVM/Clang from source (note you will need at least the `clang` and `compiler-rt` projects enabled). Then, you will need to replace `CC=clang` in the instructions with an explicit path to your built Clang binary. * ASAN has only been tested so far with Clang on Linux. It may or may not work with other compilers or on other platforms - please file an issue on [https://bugs.ruby-lang.org](https://bugs.ruby-lang.org) if you run into problems with such configurations (or, to report that they actually work properly!) * In particular, although I have not yet tried it, I have reason to believe ASAN will _not_ work properly on macOS yet - the fix for the multithreaded fork issue was actually reverted for macOS (see [here](https://github.com/llvm/llvm-project/commit/2a03854e4ce9bb1bcd79a211063bc63c4657f92c)). Please open an issue on [https://bugs.ruby-lang.org](https://bugs.ruby-lang.org) if this is a problem for you. diff --git a/doc/csv/recipes/generating.rdoc b/doc/csv/recipes/generating.rdoc index a6bd88a7147a1e..e61838d31a5b3c 100644 --- a/doc/csv/recipes/generating.rdoc +++ b/doc/csv/recipes/generating.rdoc @@ -163,7 +163,7 @@ This example defines and uses two custom write converters to strip and upcase ge === RFC 4180 Compliance By default, \CSV generates data that is compliant with -{RFC 4180}[https://tools.ietf.org/html/rfc4180] +{RFC 4180}[https://www.rfc-editor.org/rfc/rfc4180] with respect to: - Column separator. - Quote character. diff --git a/doc/csv/recipes/parsing.rdoc b/doc/csv/recipes/parsing.rdoc index f3528fbdf1bc8b..1b7071e33f6543 100644 --- a/doc/csv/recipes/parsing.rdoc +++ b/doc/csv/recipes/parsing.rdoc @@ -191,7 +191,7 @@ Output: === RFC 4180 Compliance By default, \CSV parses data that is compliant with -{RFC 4180}[https://tools.ietf.org/html/rfc4180] +{RFC 4180}[https://www.rfc-editor.org/rfc/rfc4180] with respect to: - Row separator. - Column separator. diff --git a/doc/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/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/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/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/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/ripper/ripper_init.c.tmpl b/ext/ripper/ripper_init.c.tmpl index 894cbc0b447b51..bc1b2128f771fb 100644 --- a/ext/ripper/ripper_init.c.tmpl +++ b/ext/ripper/ripper_init.c.tmpl @@ -3,13 +3,13 @@ #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" diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 1ce90030a82126..f0e5ee4e85ff09 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -15,6 +15,8 @@ static const char *const STRINGIO_VERSION = "3.1.1"; +#include + #include "ruby.h" #include "ruby/io.h" #include "ruby/encoding.h" @@ -49,6 +51,19 @@ 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 : !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"); } } @@ -287,7 +308,8 @@ strio_init(int argc, VALUE *argv, struct StringIO *ptr, VALUE self) else if (!argc) { string = rb_enc_str_new("", 0, rb_default_external_encoding()); } - if (!NIL_P(string) && OBJ_FROZEN_RAW(string)) { + + if (!NIL_P(string) && readonly_string_p(string)) { if (ptr->flags & FMODE_WRITABLE) { rb_syserr_fail(EACCES, 0); } @@ -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); @@ -973,10 +995,10 @@ strio_ungetbyte(VALUE self, VALUE c) 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; @@ -1157,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(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)); - } - } + 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; @@ -1203,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; } @@ -1245,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'; @@ -1255,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; @@ -1590,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); @@ -1626,28 +1648,28 @@ strio_pread(int argc, VALUE *argv, VALUE self) long offset = NUM2LONG(rb_offset); if (len < 0) { - rb_raise(rb_eArgError, "negative string size (or size too big): %" PRIsVALUE, rb_len); + rb_raise(rb_eArgError, "negative string size (or size too big): %" PRIsVALUE, rb_len); } if (len == 0) { - if (NIL_P(rb_buf)) { - return rb_str_new("", 0); - } - return rb_buf; + if (NIL_P(rb_buf)) { + return rb_str_new("", 0); + } + return rb_buf; } if (offset < 0) { - rb_syserr_fail_str(EINVAL, rb_sprintf("pread: Invalid offset argument: %" PRIsVALUE, rb_offset)); + rb_syserr_fail_str(EINVAL, rb_sprintf("pread: Invalid offset argument: %" PRIsVALUE, rb_offset)); } struct StringIO *ptr = readable(self); if (offset >= RSTRING_LEN(ptr->string)) { - rb_eof_error(); + rb_eof_error(); } if (NIL_P(rb_buf)) { - return strio_substr(ptr, offset, len, rb_ascii8bit_encoding()); + return strio_substr(ptr, offset, len, rb_ascii8bit_encoding()); } long rest = RSTRING_LEN(ptr->string) - offset; diff --git a/gc.c b/gc.c index eaa98928ffc6d2..8d65fa31fc032f 100644 --- a/gc.c +++ b/gc.c @@ -439,8 +439,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 +460,6 @@ static ruby_gc_params_t gc_params = { GC_OLDMALLOC_LIMIT_MIN, GC_OLDMALLOC_LIMIT_MAX, GC_OLDMALLOC_LIMIT_GROWTH_FACTOR, - - FALSE, }; /* GC_DEBUG: @@ -949,7 +945,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; @@ -1135,10 +1131,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 @@ -1389,7 +1381,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); @@ -1885,10 +1876,22 @@ calloc1(size_t n) return calloc(1, n); } +static VALUE initial_stress = Qfalse; + +void +rb_gc_initial_stress_set(VALUE flag) +{ + initial_stress = flag; +} + rb_objspace_t * rb_objspace_alloc(void) { rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t)); + + 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); @@ -1907,7 +1910,10 @@ rb_objspace_alloc(void) rb_darray_make_without_gc(&objspace->weak_references, 0); + // TODO: debug why on Windows Ruby crashes on boot when GC is on. +#ifdef _WIN32 dont_gc_on(); +#endif return objspace; } @@ -3552,14 +3558,6 @@ Init_heap(void) finalizer_table = st_init_numtable(); } -void -Init_gc_stress(void) -{ - rb_objspace_t *objspace = &rb_objspace; - - gc_stress_set(objspace, ruby_initial_gc_stress); -} - typedef int each_obj_callback(void *, void *, size_t, void *); typedef int each_page_callback(struct heap_page *, void *); @@ -6406,11 +6404,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 ); @@ -6495,13 +6493,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 @@ -8696,11 +8706,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) { if (RVALUE_WB_UNPROTECTED(obj)) { rb_gc_writebarrier_unprotect(dest); } + rb_gc_copy_finalizer(dest, obj); } size_t @@ -8955,7 +8966,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); @@ -9351,18 +9362,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); + } } } } @@ -9548,20 +9561,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)); @@ -10183,9 +10202,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: @@ -11151,18 +11168,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; } diff --git a/gems/bundled_gems b/gems/bundled_gems index 8a3555f9f826a6..38c97c77a01754 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,24 +6,21 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -# Waiting for https://github.com/minitest/minitest/pull/991 -minitest 5.22.3 https://github.com/Shopify/minitest b5f5202575894796e00109a8f8a5041b778991ee - -# Waiting for https://github.com/ruby/power_assert/pull/48 -power_assert 2.0.3 https://github.com/ruby/power_assert 78dd2ab3ccd93796d83c0b35b978c39bfabb938c -rake 13.1.0 https://github.com/ruby/rake +minitest 5.22.3 https://github.com/minitest/minitest 287b35d60c8e19c11ba090efc6eeb225325a8520 +power_assert 2.0.3 https://github.com/ruby/power_assert 84e85124c5014a139af39161d484156cfe87a9ed +rake 13.2.0 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 ba7872795d5de04adb8ff500c0e6afdc81a041dd typeprof 0.21.11 https://github.com/ruby/typeprof b19a6416da3a05d57fadd6ffdadb382b6d236ca5 -debug 1.9.1 https://github.com/ruby/debug 2d602636d99114d55a32fedd652c9c704446a749 +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 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/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/internal/gc.h b/internal/gc.h index f86af2659456b2..5b1180fd91ccdc 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -189,7 +189,6 @@ typedef struct ractor_newobj_cache { } rb_ractor_newobj_cache_t; /* gc.c */ -extern VALUE *ruby_initial_gc_stress_ptr; extern int ruby_disable_gc; RUBY_ATTR_MALLOC void *ruby_mimmalloc(size_t size); void ruby_mimfree(void *ptr); @@ -198,7 +197,7 @@ void rb_objspace_set_event_hook(const rb_event_flag_t event); VALUE rb_objspace_gc_enable(struct rb_objspace *); VALUE rb_objspace_gc_disable(struct rb_objspace *); void ruby_gc_set_params(void); -void rb_copy_wb_protected_attribute(VALUE dest, VALUE obj); +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); @@ -224,6 +223,8 @@ void rb_gc_remove_weak(VALUE parent_obj, VALUE *ptr); void rb_gc_ref_update_table_values_only(st_table *tbl); +void rb_gc_initial_stress_set(VALUE flag); + #define rb_gc_mark_and_move_ptr(ptr) do { \ VALUE _obj = (VALUE)*(ptr); \ rb_gc_mark_and_move(&_obj); \ diff --git a/internal/parse.h b/internal/parse.h index fc78836e5c48e4..f4f2f0c0f521ca 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; @@ -67,6 +65,7 @@ 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_check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc); 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*); diff --git a/iseq.c b/iseq.c index 057e047ef9fded..fffdb79edc7ff2 100644 --- a/iseq.c +++ b/iseq.c @@ -1253,8 +1253,6 @@ pm_iseq_compile_with_option(VALUE src, VALUE file, VALUE realpath, VALUE line, V pm_parse_result_t result = { 0 }; pm_options_line_set(&result.options, NUM2INT(line)); - pm_options_frozen_string_literal_init(&result, option.frozen_string_literal); - VALUE error; if (RB_TYPE_P(src, T_FILE)) { VALUE filepath = rb_io_path(src); @@ -1405,6 +1403,10 @@ static VALUE iseqw_new(const rb_iseq_t *iseq) { if (iseq->wrapper) { + if (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 { @@ -1428,6 +1430,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 @@ -1468,26 +1508,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()); } /* @@ -1529,26 +1550,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); } /* diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index 55286725c0fb45..15ce9000ee8e27 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -26,6 +26,7 @@ module Gem::BUNDLED_GEMS "resolv-replace" => "3.4.0", "rinda" => "3.4.0", "syslog" => "3.4.0", + "ostruct" => "3.5.0", }.freeze EXACT = { @@ -41,6 +42,7 @@ module Gem::BUNDLED_GEMS "resolv-replace" => true, "rinda" => true, "syslog" => true, + "ostruct" => true, }.freeze PREFIXED = { @@ -95,8 +97,10 @@ def self.find_gem(path) end def self.warning?(name, specs: nil) - feature = File.path(name) # name can be a feature name or a file path with String or Pathname - name = feature.tr("/", "-") + # name can be a feature name or a file path with String or Pathname + feature = File.path(name) + # bootsnap expand `require "csv"` to `require "#{LIBDIR}/csv.rb"` + name = feature.delete_prefix(LIBDIR).chomp(".rb").tr("/", "-") name.sub!(LIBEXT, "") return if specs.include?(name) _t, path = $:.resolve_feature_path(feature) @@ -109,12 +113,7 @@ def self.warning?(name, specs: nil) 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 +133,29 @@ def self.build_message(gem) if defined?(Bundler) msg += " Add #{gem} to your Gemfile or gemspec." + # We detect the gem name from caller_locations. We need to skip 2 frames like: # lib/ruby/3.3.0+0/bundled_gems.rb:90:in `warning?'", # lib/ruby/3.3.0+0/bundler/rubygems_integration.rb:247:in `block (2 levels) in replace_require'", - location = caller_locations(3,1)[0]&.path - if File.file?(location) && !location.start_with?(Gem::BUNDLED_GEMS::LIBDIR) + # + # Additionally, we need to skip Bootsnap and Zeitwerk if present, these + # gems decorate Kernel#require, so they are not really the ones issuing + # the require call users should be warned about. Those are upwards. + frames_to_skip = 2 + location = nil + Thread.each_caller_location do |cl| + if frames_to_skip >= 1 + frames_to_skip -= 1 + next + end + + if cl.base_label != "require" + location = cl.path + break + end + end + + if location && File.file?(location) && !location.start_with?(Gem::BUNDLED_GEMS::LIBDIR) caller_gem = nil Gem.path.each do |path| if location =~ %r{#{path}/gems/([\w\-\.]+)} diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb index d946e495e93532..fd61ef0d954dea 100644 --- a/lib/bundler/cli/plugin.rb +++ b/lib/bundler/cli/plugin.rb @@ -5,7 +5,7 @@ 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. 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" @@ -13,6 +13,7 @@ class CLI::Plugin < Thor method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (deprecated)" method_option "branch", type: :string, default: nil, banner: "The git branch to checkout" method_option "ref", type: :string, default: nil, banner: "The git revision to check out" + method_option "path", type: :string, default: nil, banner: "Path of a local gem to directly use" def install(*plugins) Bundler::Plugin.install(plugins, options) end diff --git a/lib/bundler/gem_version_promoter.rb b/lib/bundler/gem_version_promoter.rb index c7eacd193049a3..ecc65b49560292 100644 --- a/lib/bundler/gem_version_promoter.rb +++ b/lib/bundler/gem_version_promoter.rb @@ -45,17 +45,37 @@ def level=(value) # Given a Resolver::Package and an Array of Specifications of available # versions for a gem, this method will return the Array of Specifications - # sorted (and possibly truncated if strict is true) in an order to give - # preference to the current level (:major, :minor or :patch) when resolution - # is deciding what versions best resolve all dependencies in the bundle. + # sorted in an order to give preference to the current level (:major, :minor + # or :patch) when resolution is deciding what versions best resolve all + # dependencies in the bundle. # @param package [Resolver::Package] The package being resolved. # @param specs [Specification] An array of Specifications for the package. - # @return [Specification] A new instance of the Specification Array sorted and - # possibly filtered. + # @return [Specification] A new instance of the Specification Array sorted. def sort_versions(package, specs) - specs = filter_dep_specs(specs, package) if strict + locked_version = package.locked_version - sort_dep_specs(specs, package) + result = specs.sort do |a, b| + unless package.prerelease_specified? || pre? + a_pre = a.prerelease? + b_pre = b.prerelease? + + next 1 if a_pre && !b_pre + next -1 if b_pre && !a_pre + end + + if major? || locked_version.nil? + b <=> a + elsif either_version_older_than_locked?(a, b, locked_version) + b <=> a + elsif segments_do_not_match?(a, b, :major) + a <=> b + elsif !minor? && segments_do_not_match?(a, b, :minor) + a <=> b + else + b <=> a + end + end + post_sort(result, package.unlock?, locked_version) end # @return [bool] Convenience method for testing value of level variable. @@ -73,9 +93,18 @@ def pre? pre == true end - private + # Given a Resolver::Package and an Array of Specifications of available + # versions for a gem, this method will truncate the Array if strict + # is true. That means filtering out downgrades from the version currently + # locked, and filtering out upgrades that go past the selected level (major, + # minor, or patch). + # @param package [Resolver::Package] The package being resolved. + # @param specs [Specification] An array of Specifications for the package. + # @return [Specification] A new instance of the Specification Array + # truncated. + def filter_versions(package, specs) + return specs unless strict - def filter_dep_specs(specs, package) locked_version = package.locked_version return specs if locked_version.nil? || major? @@ -89,32 +118,7 @@ def filter_dep_specs(specs, package) end end - def sort_dep_specs(specs, package) - locked_version = package.locked_version - - result = specs.sort do |a, b| - unless package.prerelease_specified? || pre? - a_pre = a.prerelease? - b_pre = b.prerelease? - - next -1 if a_pre && !b_pre - next 1 if b_pre && !a_pre - end - - if major? || locked_version.nil? - a <=> b - elsif either_version_older_than_locked?(a, b, locked_version) - a <=> b - elsif segments_do_not_match?(a, b, :major) - b <=> a - elsif !minor? && segments_do_not_match?(a, b, :minor) - b <=> a - else - a <=> b - end - end - post_sort(result, package.unlock?, locked_version) - end + private def either_version_older_than_locked?(a, b, locked_version) a.version < locked_version || b.version < locked_version @@ -133,13 +137,13 @@ def post_sort(result, unlock, locked_version) if unlock || locked_version.nil? result else - move_version_to_end(result, locked_version) + move_version_to_beginning(result, locked_version) end end - def move_version_to_end(result, version) + def move_version_to_beginning(result, version) move, keep = result.partition {|s| s.version.to_s == version.to_s } - keep.concat(move) + move.concat(keep) end end end diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index f85d21f9599b83..a6cbc88f344e27 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-ADD" "1" "February 2024" "" +.TH "BUNDLE\-ADD" "1" "March 2024" "" .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index dd0b3cd4e899bc..2b35bc956a7837 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-BINSTUBS" "1" "February 2024" "" +.TH "BUNDLE\-BINSTUBS" "1" "March 2024" "" .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 8e39bb92c3c464..3b86b995a62689 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CACHE" "1" "February 2024" "" +.TH "BUNDLE\-CACHE" "1" "March 2024" "" .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index 82920d71887425..7f18e265375288 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CHECK" "1" "February 2024" "" +.TH "BUNDLE\-CHECK" "1" "March 2024" "" .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index 04cf55275caea2..0180eb38a213ac 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CLEAN" "1" "February 2024" "" +.TH "BUNDLE\-CLEAN" "1" "March 2024" "" .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 4b0728c213c2e1..b768f1e3d29ec6 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CONFIG" "1" "February 2024" "" +.TH "BUNDLE\-CONFIG" "1" "March 2024" "" .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index 467a375f89de92..1368a50eb163fa 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CONSOLE" "1" "February 2024" "" +.TH "BUNDLE\-CONSOLE" "1" "March 2024" "" .SH "NAME" \fBbundle\-console\fR \- Deprecated way to open an IRB session with the bundle pre\-loaded .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index e77bafec29b46c..80eaf2a8882524 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-DOCTOR" "1" "February 2024" "" +.TH "BUNDLE\-DOCTOR" "1" "March 2024" "" .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index 926675aa5fdb06..191863c045ba5f 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-EXEC" "1" "February 2024" "" +.TH "BUNDLE\-EXEC" "1" "March 2024" "" .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index 744c4917c7e152..464d8d11264553 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-GEM" "1" "February 2024" "" +.TH "BUNDLE\-GEM" "1" "March 2024" "" .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1 index 613ff261dde66a..3604ad6127d6bd 100644 --- a/lib/bundler/man/bundle-help.1 +++ b/lib/bundler/man/bundle-help.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-HELP" "1" "February 2024" "" +.TH "BUNDLE\-HELP" "1" "March 2024" "" .SH "NAME" \fBbundle\-help\fR \- Displays detailed help for each subcommand .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index 7df617d859cea3..647f5987befbbc 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INFO" "1" "February 2024" "" +.TH "BUNDLE\-INFO" "1" "March 2024" "" .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index 4a9c6b01a1b58f..2c41a3c7deff9a 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INIT" "1" "February 2024" "" +.TH "BUNDLE\-INIT" "1" "March 2024" "" .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1 index f88f6dbe605253..c7269db34d0a21 100644 --- a/lib/bundler/man/bundle-inject.1 +++ b/lib/bundler/man/bundle-inject.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INJECT" "1" "February 2024" "" +.TH "BUNDLE\-INJECT" "1" "March 2024" "" .SH "NAME" \fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index f41def305fe71b..3fa1a467e2b7ce 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INSTALL" "1" "February 2024" "" +.TH "BUNDLE\-INSTALL" "1" "March 2024" "" .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index e5b56767518278..f91fd95739351f 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-LIST" "1" "February 2024" "" +.TH "BUNDLE\-LIST" "1" "March 2024" "" .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index a63c6fd5a5283a..f992f5ee5f0a78 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-LOCK" "1" "February 2024" "" +.TH "BUNDLE\-LOCK" "1" "March 2024" "" .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index 8cf0ec0e364805..53d3541555153b 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-OPEN" "1" "February 2024" "" +.TH "BUNDLE\-OPEN" "1" "March 2024" "" .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index 30e795748c0d24..f79eff5ae99808 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-OUTDATED" "1" "February 2024" "" +.TH "BUNDLE\-OUTDATED" "1" "March 2024" "" .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index 4f5240c242c430..d2133ec4d3fecb 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PLATFORM" "1" "February 2024" "" +.TH "BUNDLE\-PLATFORM" "1" "March 2024" "" .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index 29db99376010ba..cbdfac11b6bfb5 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -1,10 +1,10 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PLUGIN" "1" "February 2024" "" +.TH "BUNDLE\-PLUGIN" "1" "March 2024" "" .SH "NAME" \fBbundle\-plugin\fR \- Manage Bundler plugins .SH "SYNOPSIS" -\fBbundle plugin\fR install PLUGINS [\-\-source=\fISOURCE\fR] [\-\-version=\fIversion\fR] [\-\-git=\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 @@ -37,7 +37,10 @@ Install bundler\-graph gem from Git repository\. You can use standard Git URLs l .br \fBfile:///path/to/repo\fR .IP -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\. 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 e38776a47cd7a5..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=] [--branch=|--ref=]
+ [--git=] [--branch=|--ref=] + [--path=]
`bundle plugin` uninstall PLUGINS
`bundle plugin` list
`bundle plugin` help [COMMAND] @@ -36,7 +37,10 @@ Install the given plugin(s). `/path/to/repo`
`file:///path/to/repo` - When you specify `--git`, you can use `--branch` or `--ref` to specify any branch, tag, or commit hash (revision) to use. When you specify both, only the latter is used. + When you specify `--git`, you can use `--branch` or `--ref` to specify any branch, tag, or commit hash (revision) to use. + +* `bundle plugin install bundler-graph --path ../bundler-graph`: + Install bundler-graph gem from a local path. ### uninstall diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 81a4758374fa86..faa04d76762a30 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PRISTINE" "1" "February 2024" "" +.TH "BUNDLE\-PRISTINE" "1" "March 2024" "" .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index 1172577bca9d68..3f8cbbd9b684bf 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-REMOVE" "1" "February 2024" "" +.TH "BUNDLE\-REMOVE" "1" "March 2024" "" .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index 338502ae358d18..bc72c6e3b6baf3 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-SHOW" "1" "February 2024" "" +.TH "BUNDLE\-SHOW" "1" "March 2024" "" .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index 8174d9b84133e9..d1284c2e72b54f 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-UPDATE" "1" "February 2024" "" +.TH "BUNDLE\-UPDATE" "1" "March 2024" "" .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1 index 5361b0493e2fee..05905e1347fc35 100644 --- a/lib/bundler/man/bundle-version.1 +++ b/lib/bundler/man/bundle-version.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-VERSION" "1" "February 2024" "" +.TH "BUNDLE\-VERSION" "1" "March 2024" "" .SH "NAME" \fBbundle\-version\fR \- Prints Bundler version information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1 index ea726ceb499537..681563cd4c3d7f 100644 --- a/lib/bundler/man/bundle-viz.1 +++ b/lib/bundler/man/bundle-viz.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-VIZ" "1" "February 2024" "" +.TH "BUNDLE\-VIZ" "1" "March 2024" "" .SH "NAME" \fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 053b56d00a3b9c..1d2c780060f7c1 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE" "1" "February 2024" "" +.TH "BUNDLE" "1" "March 2024" "" .SH "NAME" \fBbundle\fR \- Ruby Dependency Management .SH "SYNOPSIS" diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 6db532c34ae96d..39503f22a66233 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,6 +1,6 @@ .\" generated with nRonn/v0.11.1 .\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "GEMFILE" "5" "February 2024" "" +.TH "GEMFILE" "5" "March 2024" "" .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs .SH "SYNOPSIS" diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb index 7ae56440fbbf2f..6771f3f1537698 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,6 +19,8 @@ def install(names, options) if options[:git] install_git(names, version, options) + elsif options[:path] + install_path(names, version, options[:path]) else sources = options[:source] || Gem.sources install_rubygems(names, version, sources) @@ -43,18 +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_path(names, version, path) + source_list = SourceList.new + source = source_list.add_path_source({ "path" => path }) + + install_all_sources(names, version, source_list, source) end # Installs the plugin from rubygems source and returns the path where the @@ -66,16 +91,15 @@ def install_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..1b60724b5e4ea3 --- /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 + 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/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/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/prism/ffi.rb b/lib/prism/ffi.rb index 1ca99db6810034..0a064a5c941850 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -382,9 +382,6 @@ def dump_options(options) template << "l" values << options.fetch(:line, 1) - template << "L" - values << options.fetch(:offset, 0) - template << "L" if (encoding = options[:encoding]) name = encoding.name 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/translation/parser.rb b/lib/prism/translation/parser.rb index 8df71646882d75..0d11b8f5668cca 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -173,6 +173,8 @@ def error_diagnostic(error, offset_cache) Diagnostic.new(:error, :duplicate_argument, {}, diagnostic_location, []) when :parameter_numbered_reserved Diagnostic.new(:error, :reserved_for_numparam, { name: location.slice }, diagnostic_location, []) + when :regexp_unknown_options + Diagnostic.new(:error, :regexp_options, { options: location.slice[1..] }, diagnostic_location, []) when :singleton_for_literals Diagnostic.new(:error, :singleton_literal, {}, diagnostic_location, []) when :string_literal_eof diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index 108e6c6928c5cd..5c59fe31810562 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -192,19 +192,19 @@ def visit_block_parameters_node(node) if node.opening == "(" result.line = node.opening_loc.start_line - result.max_line = node.closing_loc.end_line + result.line_max = node.closing_loc.end_line shadow_loc = false end if node.locals.any? shadow = s(node, :shadow).concat(visit_all(node.locals)) shadow.line = node.locals.first.location.start_line - shadow.max_line = node.locals.last.location.end_line + shadow.line_max = node.locals.last.location.end_line result << shadow if shadow_loc result.line = shadow.line - result.max_line = shadow.max_line + result.line_max = shadow.line_max end end @@ -1412,7 +1412,7 @@ def visit_x_string_node(node) if node.heredoc? result.line = node.content_loc.start_line - result.max_line = node.content_loc.end_line + result.line_max = node.content_loc.end_line end result @@ -1439,7 +1439,7 @@ def s(node, *arguments) result = Sexp.new(*arguments) result.file = file result.line = node.location.start_line - result.max_line = node.location.end_line + result.line_max = node.location.end_line result end diff --git a/lib/random/formatter.rb b/lib/random/formatter.rb index 0548e86ccaafc6..037f9d8748f27b 100644 --- a/lib/random/formatter.rb +++ b/lib/random/formatter.rb @@ -165,7 +165,7 @@ def urlsafe_base64(n=nil, padding=false) # # The result contains 122 random bits (15.25 random bytes). # - # See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID. + # See RFC4122[https://www.rfc-editor.org/rfc/rfc4122] for details of UUID. # def uuid ary = random_bytes(16) diff --git a/lib/reline.rb b/lib/reline.rb index 713d1a8b60c78c..b43584fc9bccbf 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -219,26 +219,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 @@ -331,8 +321,10 @@ def readline(prompt = '', add_hist = false) line_editor.auto_indent_proc = auto_indent_proc line_editor.dig_perfect_match_proc = dig_perfect_match_proc pre_input_hook&.call - @dialog_proc_list.each_pair do |name_sym, d| - line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context) + 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 diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb index 8fb0f25a3471ab..0ac1c6c56d40bc 100644 --- a/lib/reline/general_io.rb +++ b/lib/reline/general_io.rb @@ -102,14 +102,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/line_editor.rb b/lib/reline/line_editor.rb index 5a04e5650f6c87..d5c158ac746e54 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -41,15 +41,36 @@ module CompletionState NORMAL = :normal COMPLETION = :completion MENU = :menu - JOURNEY = :journey MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match PERFECT_MATCH = :perfect_match end RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true) - CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer) - MenuInfo = Struct.new(:target, :list) + 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 MINIMUM_SCROLLBAR_HEIGHT = 1 @@ -85,7 +106,7 @@ def set_pasting_state(in_pasting) end end - private def check_multiline_prompt(buffer) + private def check_multiline_prompt(buffer, mode_string) if @vi_arg prompt = "(arg: #{@vi_arg}) " elsif @searching_prompt @@ -97,7 +118,6 @@ def set_pasting_state(in_pasting) 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? @@ -109,7 +129,6 @@ def set_pasting_state(in_pasting) end prompt_list else - mode_string = check_mode_string prompt = mode_string + prompt if mode_string [prompt] * buffer.size end @@ -201,7 +220,7 @@ def reset_variables(prompt = '', encoding:) @waiting_proc = nil @waiting_operator_proc = nil @waiting_operator_vi_arg = nil - @completion_journey_data = nil + @completion_journey_state = nil @completion_state = CompletionState::NORMAL @perfect_matched = nil @menu_info = nil @@ -319,8 +338,8 @@ def modified_lines end def prompt_list - with_cache(__method__, whole_lines, @vi_arg, @searching_prompt) do |lines| - check_multiline_prompt(lines) + with_cache(__method__, whole_lines, check_mode_string, @vi_arg, @searching_prompt) do |lines, mode_string| + check_multiline_prompt(lines, mode_string) end end @@ -460,7 +479,7 @@ def render_differential [[0, Reline::Unicode.calculate_width(l, true), l]] end if @menu_info - @menu_info.list.sort!.each do |item| + @menu_info.lines(screen_width).each do |item| new_lines << [[0, Reline::Unicode.calculate_width(item), item]] end @menu_info = nil # TODO: do not change state here @@ -539,6 +558,8 @@ def rerender 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 @@ -602,7 +623,7 @@ def preferred_dialog_height end def completion_journey_data - @line_editor.instance_variable_get(:@completion_journey_data) + @line_editor.dialog_proc_scope_completion_journey_data end def config @@ -781,8 +802,8 @@ 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) @@ -831,9 +852,9 @@ def editing_mode [target, preposing, completed, postposing] end - private def complete(list, just_show_list = false) + private def complete(list, just_show_list) case @completion_state - when CompletionState::NORMAL, CompletionState::JOURNEY + when CompletionState::NORMAL @completion_state = CompletionState::COMPLETION when CompletionState::PERFECT_MATCH @dig_perfect_match_proc&.(@perfect_matched) @@ -873,46 +894,44 @@ def editing_mode 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] - line_to_pointer = (@completion_journey_data.preposing + completed).split("\n")[@line_index] || String.new(encoding: @encoding) - new_line = line_to_pointer + (@completion_journey_data.postposing.split("\n").first || '') - set_current_line(new_line, 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) @@ -1101,50 +1120,56 @@ def input_key(key) @first_char = false completion_occurs = false if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord - unless @config.disable_completion - result = call_completion_proc - if result.is_a?(Array) - completion_occurs = true - process_insert - if @config.autocompletion - move_completed_list(result, :down) - else - complete(result) + if !@config.disable_completion + process_insert(force: true) + if @config.autocompletion + @completion_state = CompletionState::NORMAL + completion_occurs = move_completed_list(:down) + else + @completion_journey_state = nil + result = call_completion_proc + if result.is_a?(Array) + completion_occurs = true + complete(result, false) end end end elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up if not @config.disable_completion and @config.autocompletion - result = call_completion_proc - if result.is_a?(Array) - completion_occurs = true - process_insert - move_completed_list(result, :up) - end + process_insert(force: true) + @completion_state = CompletionState::NORMAL + completion_occurs = move_completed_list(:up) end - elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char) - unless @config.disable_completion - result = call_completion_proc - if result.is_a?(Array) - completion_occurs = true - process_insert - move_completed_list(result, "\C-p".ord == key.char ? :up : :down) - end + elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char) + # In vi mode, move completed list even if autocompletion is off + if not @config.disable_completion + process_insert(force: true) + @completion_state = CompletionState::NORMAL + completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down) end elsif Symbol === key.char and respond_to?(key.char, true) process_key(key.char, key.char) else normal_char(key) end + unless completion_occurs @completion_state = CompletionState::NORMAL - @completion_journey_data = nil + @completion_journey_state = nil end + if @in_pasting clear_dialogs - else - return old_lines != @buffer_of_lines + 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 @@ -2022,7 +2047,7 @@ def finish private def em_delete_or_list(key) 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) diff --git a/lib/reline/version.rb b/lib/reline/version.rb index 8f21e6a9cfb2d4..06bb3acc8454e9 100644 --- a/lib/reline/version.rb +++ b/lib/reline/version.rb @@ -1,3 +1,3 @@ module Reline - VERSION = '0.5.0.pre.1' + VERSION = '0.5.0' end 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/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 72a179da376d87..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 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/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/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/misc/lldb_rb/utils.py b/misc/lldb_rb/utils.py index a321426234befd..054c206cef02f3 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) diff --git a/numeric.c b/numeric.c index 41beff0919f7f3..f0a0e3c279e9d1 100644 --- a/numeric.c +++ b/numeric.c @@ -6251,19 +6251,25 @@ Init_Numeric(void) rb_define_method(rb_cInteger, "digits", rb_int_digits, -1); - rb_fix_to_s_static[0] = rb_fstring_literal("0"); - rb_fix_to_s_static[1] = rb_fstring_literal("1"); - rb_fix_to_s_static[2] = rb_fstring_literal("2"); - rb_fix_to_s_static[3] = rb_fstring_literal("3"); - rb_fix_to_s_static[4] = rb_fstring_literal("4"); - rb_fix_to_s_static[5] = rb_fstring_literal("5"); - rb_fix_to_s_static[6] = rb_fstring_literal("6"); - rb_fix_to_s_static[7] = rb_fstring_literal("7"); - rb_fix_to_s_static[8] = rb_fstring_literal("8"); - rb_fix_to_s_static[9] = rb_fstring_literal("9"); - for(int i = 0; i < 10; i++) { - rb_vm_register_global_object(rb_fix_to_s_static[i]); - } +#define fix_to_s_static(n) do { \ + VALUE lit = rb_fstring_literal(#n); \ + rb_fix_to_s_static[n] = lit; \ + rb_vm_register_global_object(lit); \ + RB_GC_GUARD(lit); \ + } while (0) + + fix_to_s_static(0); + fix_to_s_static(1); + fix_to_s_static(2); + fix_to_s_static(3); + fix_to_s_static(4); + fix_to_s_static(5); + fix_to_s_static(6); + fix_to_s_static(7); + fix_to_s_static(8); + fix_to_s_static(9); + +#undef fix_to_s_static rb_cFloat = rb_define_class("Float", rb_cNumeric); diff --git a/object.c b/object.c index 8eee22fbb4a73c..4673ba9f6965ce 100644 --- a/object.c +++ b/object.c @@ -396,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); } diff --git a/parse.y b/parse.y index 28b03f8cd149b8..5784cc4b20dfd0 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 @@ -73,6 +71,22 @@ #include "ruby/ractor.h" #include "symbol.h" +#ifndef RIPPER +static VALUE +syntax_error_new(void) +{ + return rb_class_new_instance(0, 0, rb_eSyntaxError); +} +#endif + +static NODE *reg_named_capture_assign(struct parser_params* p, VALUE regexp, const YYLTYPE *loc); + +#define compile_callback rb_suppress_tracing +VALUE rb_io_gets_internal(VALUE io); + +VALUE rb_node_case_when_optimizable_literal(const NODE *const node); +#endif /* !UNIVERSAL_PARSER */ + #ifndef RIPPER static int rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2); @@ -117,15 +131,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 +144,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 +170,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 +182,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,32 +200,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 */ +#endif /* !RIPPER */ static inline int parse_isascii(int c) @@ -567,7 +544,7 @@ struct parser_params { VALUE ruby_sourcefile_string; rb_encoding *enc; token_info *token_info; - VALUE case_labels; + st_table *case_labels; rb_node_exits_t *exits; VALUE debug_buffer; @@ -1532,8 +1509,6 @@ int reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); static int literal_concat0(struct parser_params *p, rb_parser_string_t *head, rb_parser_string_t *tail); static NODE *heredoc_dedent(struct parser_params*,NODE*); -static void check_literal_when(struct parser_params *p, NODE *args, const YYLTYPE *loc); - #ifdef RIPPER static VALUE var_field(struct parser_params *p, VALUE a); #define get_value(idx) (rb_ary_entry(p->s_value_stack, idx)) @@ -1615,6 +1590,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); @@ -2063,8 +2041,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: @@ -2078,7 +2057,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) { @@ -2097,7 +2075,6 @@ parser_memhash(const void *ptr, long len) return djb2(ptr, len); } #endif -#endif #define PARSER_STRING_PTR(str) (str->ptr) #define PARSER_STRING_LEN(str) (str->len) @@ -2162,13 +2139,17 @@ rb_parser_string_free(rb_parser_t *p, rb_parser_string_t *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 + +static st_index_t +rb_char_p_hash(const char *c) +{ + return parser_memhash((const void *)c, strlen(c)); +} #endif static size_t @@ -2566,7 +2547,6 @@ rb_parser_str_resize(struct parser_params *p, rb_parser_string_t *str, long len) } #ifndef RIPPER -#ifndef UNIVERSAL_PARSER # define PARSER_ENC_STRING_GETMEM(str, ptrvar, lenvar, encvar) \ ((ptrvar) = str->ptr, \ (lenvar) = str->len, \ @@ -2586,7 +2566,6 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2) enc1 != enc2 || memcmp(ptr1, ptr2, len1) != 0); } -#endif static void rb_parser_ary_extend(rb_parser_t *p, rb_parser_ary_t *ary, long len) @@ -2693,6 +2672,10 @@ rb_parser_tokens_free(rb_parser_t *p, rb_parser_ary_t *tokens) 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 @@ -2706,7 +2689,6 @@ rb_parser_tokens_free(rb_parser_t *p, rb_parser_ary_t *tokens) %after-pop-stack after_pop_stack %union { - VALUE val; NODE *node; rb_node_fcall_t *node_fcall; rb_node_args_t *node_args; @@ -2720,6 +2702,7 @@ rb_parser_tokens_free(rb_parser_t *p, rb_parser_ary_t *tokens) ID id; int num; st_table *tbl; + st_table *labels; const struct vtable *vars; struct rb_strterm_struct *strterm; struct lex_context ctxt; @@ -4535,28 +4518,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) %*/ } @@ -5414,7 +5397,7 @@ do_body : { case_args : arg_value { - check_literal_when(p, $1, &@1); + rb_parser_check_literal_when(p, $1, &@1); $$ = NEW_LIST($1, &@$); /*% ripper: args_add!(args_new!, $:1) %*/ } @@ -5425,7 +5408,7 @@ case_args : arg_value } | case_args ',' arg_value { - check_literal_when(p, $3, &@3); + rb_parser_check_literal_when(p, $3, &@3); $$ = last_arg_append(p, $1, $3, &@$); /*% ripper: args_add!($:1, $:3) %*/ } @@ -7922,7 +7905,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 * @@ -7939,7 +7922,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; } @@ -10163,6 +10146,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); } } @@ -11566,7 +11550,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); @@ -13485,36 +13469,35 @@ new_xstring(struct parser_params *p, NODE *node, const YYLTYPE *loc) } #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 void -check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc) -{ - VALUE lit; +static int nd_type_st_key_enable_p(NODE *node); +void +rb_parser_check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc) +{ + /* 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_IVAL(INT2NUM((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); } +#endif #ifdef RIPPER static int @@ -15227,14 +15210,7 @@ nd_value(struct parser_params *p, NODE *node) void rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) { -#ifndef UNIVERSAL_PARSER - static const -#endif - struct st_hash_type literal_type = { - literal_cmp, - literal_hash, - }; - + /* See https://bugs.ruby-lang.org/issues/20331 for discussion about what is warned. */ st_table *literal_keys = st_init_table_with_size(&literal_type, RNODE_LIST(hash)->as.nd_alen / 2); while (hash && RNODE_LIST(hash)->nd_next) { NODE *head = RNODE_LIST(hash)->nd_head; @@ -16157,7 +16133,6 @@ rb_ruby_parser_mark(void *ptr) rb_gc_mark(p->lex.input); rb_gc_mark(p->ruby_sourcefile_string); rb_gc_mark((VALUE)p->ast); - rb_gc_mark(p->case_labels); rb_gc_mark(p->delayed.token); #ifndef RIPPER rb_gc_mark(p->debug_lines); @@ -16211,6 +16186,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); } 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 269bfa73ec5efa..c813b02d9f15b2 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -1,5 +1,6 @@ errors: - ALIAS_ARGUMENT + - ALIAS_ARGUMENT_NUMBERED_REFERENCE - AMPAMPEQ_MULTI_ASSIGN - ARGUMENT_AFTER_BLOCK - ARGUMENT_AFTER_FORWARDING_ELLIPSES @@ -46,6 +47,7 @@ errors: - CLASS_SUPERCLASS - CLASS_TERM - CLASS_UNEXPECTED_END + - CLASS_VARIABLE_BARE - CONDITIONAL_ELSIF_PREDICATE - CONDITIONAL_IF_PREDICATE - CONDITIONAL_PREDICATE_TERM @@ -107,6 +109,7 @@ errors: - FOR_IN - FOR_INDEX - FOR_TERM + - GLOBAL_VARIABLE_BARE - HASH_EXPRESSION_AFTER_LABEL - HASH_KEY - HASH_ROCKET @@ -118,6 +121,7 @@ errors: - INCOMPLETE_VARIABLE_CLASS_3_3_0 - INCOMPLETE_VARIABLE_INSTANCE - INCOMPLETE_VARIABLE_INSTANCE_3_3_0 + - INSTANCE_VARIABLE_BARE - INVALID_CHARACTER - INVALID_ENCODING_MAGIC_COMMENT - INVALID_FLOAT_EXPONENT @@ -166,7 +170,7 @@ errors: - PARAMETER_BLOCK_MULTI - PARAMETER_CIRCULAR - PARAMETER_METHOD_NAME - - PARAMETER_NAME_REPEAT + - PARAMETER_NAME_DUPLICATED - PARAMETER_NO_DEFAULT - PARAMETER_NO_DEFAULT_KW - PARAMETER_NUMBERED_RESERVED @@ -175,6 +179,7 @@ errors: - PARAMETER_STAR - PARAMETER_UNEXPECTED_FWD - PARAMETER_WILD_LOOSE_COMMA + - PATTERN_CAPTURE_DUPLICATE - PATTERN_EXPRESSION_AFTER_BRACKET - PATTERN_EXPRESSION_AFTER_COMMA - PATTERN_EXPRESSION_AFTER_HROCKET @@ -186,6 +191,7 @@ errors: - PATTERN_EXPRESSION_AFTER_RANGE - PATTERN_EXPRESSION_AFTER_REST - PATTERN_HASH_KEY + - PATTERN_HASH_KEY_DUPLICATE - PATTERN_HASH_KEY_LABEL - PATTERN_IDENT_AFTER_HROCKET - PATTERN_LABEL_AFTER_COMMA @@ -199,12 +205,14 @@ errors: - REGEXP_INVALID_UNICODE_RANGE - REGEXP_NON_ESCAPED_MBC - REGEXP_TERM + - REGEXP_UNKNOWN_OPTIONS - REGEXP_UTF8_CHAR_NON_UTF8_REGEXP - RESCUE_EXPRESSION - RESCUE_MODIFIER_VALUE - RESCUE_TERM - RESCUE_VARIABLE - RETURN_INVALID + - SCRIPT_NOT_FOUND - SINGLETON_FOR_LITERALS - STATEMENT_ALIAS - STATEMENT_POSTEXE_END @@ -254,6 +262,7 @@ warnings: - KEYWORD_EOL - LITERAL_IN_CONDITION_DEFAULT - LITERAL_IN_CONDITION_VERBOSE + - SHEBANG_CARRIAGE_RETURN - UNEXPECTED_CARRIAGE_RETURN tokens: - name: EOF @@ -625,6 +634,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 @@ -2259,6 +2275,9 @@ nodes: ^^^^^^^^^^^^^^^^ - name: InterpolatedStringNode fields: + - name: flags + type: flags + kind: InterpolatedStringNodeFlags - name: opening_loc type: location? - name: parts diff --git a/prism/defines.h b/prism/defines.h index aca3c6dc088fcf..849ca6d05160ff 100644 --- a/prism/defines.h +++ b/prism/defines.h @@ -151,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 @@ -159,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 @@ -167,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 @@ -175,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/extension.c b/prism/extension.c index 7c8636e3dfc15c..27e799a8da645e 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -28,7 +28,6 @@ ID rb_option_id_encoding; ID rb_option_id_filepath; ID rb_option_id_frozen_string_literal; ID rb_option_id_line; -ID rb_option_id_offset; ID rb_option_id_scopes; ID rb_option_id_version; @@ -139,8 +138,6 @@ build_options_i(VALUE key, VALUE value, VALUE argument) { if (!NIL_P(value)) pm_options_encoding_set(options, rb_enc_name(rb_to_encoding(value))); } 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_offset) { - if (!NIL_P(value)) pm_options_offset_set(options, NUM2UINT(value)); } else if (key_id == rb_option_id_frozen_string_literal) { if (!NIL_P(value)) pm_options_frozen_string_literal_set(options, RTEST(value)); } else if (key_id == rb_option_id_version) { @@ -270,6 +267,8 @@ file_options(int argc, VALUE *argv, pm_string_t *input, pm_options_t *options) { } } +#ifndef PRISM_EXCLUDE_SERIALIZATION + /******************************************************************************/ /* Serializing the AST */ /******************************************************************************/ @@ -351,6 +350,8 @@ dump_file(int argc, VALUE *argv, VALUE self) { return value; } +#endif + /******************************************************************************/ /* Extracting values for the parse result */ /******************************************************************************/ @@ -444,12 +445,15 @@ parser_errors(pm_parser_t *parser, rb_encoding *encoding, VALUE source) { VALUE level = Qnil; switch (error->level) { - case PM_ERROR_LEVEL_FATAL: - level = ID2SYM(rb_intern("fatal")); + case PM_ERROR_LEVEL_SYNTAX: + level = ID2SYM(rb_intern("syntax")); break; case PM_ERROR_LEVEL_ARGUMENT: level = ID2SYM(rb_intern("argument")); break; + case PM_ERROR_LEVEL_LOAD: + level = ID2SYM(rb_intern("load")); + break; default: rb_raise(rb_eRuntimeError, "Unknown level: %" PRIu8, error->level); } @@ -726,7 +730,7 @@ parse_input(pm_string_t *input, const pm_options_t *options) { * parsed. This should be an array of arrays of symbols or nil. Scopes are * ordered from the outermost scope to the innermost one. * * `version` - the version of Ruby syntax that prism should used to parse Ruby - * code. By default prism assumes you want to parse with the latest vesion + * code. By default prism assumes you want to parse with the latest version * of Ruby syntax (which you can trigger with `nil` or `"latest"`). You * may also restrict the syntax to a specific version of Ruby. The * supported values are `"3.3.0"` and `"3.4.0"`. @@ -1129,6 +1133,8 @@ profile_file(VALUE self, VALUE filepath) { return Qnil; } +#ifndef PRISM_EXCLUDE_PRETTYPRINT + /** * call-seq: * Debug::inspect_node(source) -> inspected @@ -1159,6 +1165,8 @@ inspect_node(VALUE self, VALUE source) { return string; } +#endif + /** * call-seq: * Debug::format_errors(source, colorize) -> String @@ -1176,7 +1184,7 @@ format_errors(VALUE self, VALUE source, VALUE colorize) { pm_node_t *node = pm_parse(&parser); pm_buffer_t buffer = { 0 }; - pm_parser_errors_format(&parser, &buffer, RTEST(colorize)); + pm_parser_errors_format(&parser, &parser.error_list, &buffer, RTEST(colorize), true); rb_encoding *encoding = rb_enc_find(parser.encoding->name); VALUE result = rb_enc_str_new(pm_buffer_value(&buffer), pm_buffer_length(&buffer), encoding); @@ -1339,7 +1347,6 @@ Init_prism(void) { rb_option_id_filepath = rb_intern_const("filepath"); rb_option_id_frozen_string_literal = rb_intern_const("frozen_string_literal"); rb_option_id_line = rb_intern_const("line"); - rb_option_id_offset = rb_intern_const("offset"); rb_option_id_scopes = rb_intern_const("scopes"); rb_option_id_version = rb_intern_const("version"); @@ -1349,8 +1356,6 @@ Init_prism(void) { rb_define_const(rb_cPrism, "VERSION", rb_str_new2(EXPECTED_PRISM_VERSION)); // First, the functions that have to do with lexing and parsing. - rb_define_singleton_method(rb_cPrism, "dump", dump, -1); - rb_define_singleton_method(rb_cPrism, "dump_file", dump_file, -1); rb_define_singleton_method(rb_cPrism, "lex", lex, -1); rb_define_singleton_method(rb_cPrism, "lex_file", lex_file, -1); rb_define_singleton_method(rb_cPrism, "parse", parse, -1); @@ -1363,6 +1368,11 @@ Init_prism(void) { rb_define_singleton_method(rb_cPrism, "parse_success?", parse_success_p, -1); rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1); +#ifndef PRISM_EXCLUDE_SERIALIZATION + rb_define_singleton_method(rb_cPrism, "dump", dump, -1); + rb_define_singleton_method(rb_cPrism, "dump_file", dump_file, -1); +#endif + // Next, the functions that will be called by the parser to perform various // internal tasks. We expose these to make them easier to test. VALUE rb_cPrismDebug = rb_define_module_under(rb_cPrism, "Debug"); @@ -1370,10 +1380,13 @@ 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. rb_cPrismDebugEncoding = rb_define_class_under(rb_cPrismDebug, "Encoding", rb_cObject); diff --git a/prism/options.c b/prism/options.c index cd575357aae58f..2854b765b9323b 100644 --- a/prism/options.c +++ b/prism/options.c @@ -24,14 +24,6 @@ pm_options_line_set(pm_options_t *options, int32_t line) { options->line = line; } -/** - * Set the offset option on the given options struct. - */ -PRISM_EXPORTED_FUNCTION void -pm_options_offset_set(pm_options_t *options, uint32_t offset) { - options->offset = offset; -} - /** * Set the frozen string literal option on the given options struct. */ @@ -201,9 +193,6 @@ pm_options_read(pm_options_t *options, const char *data) { options->line = pm_options_read_s32(data); data += 4; - options->offset = pm_options_read_u32(data); - data += 4; - uint32_t encoding_length = pm_options_read_u32(data); data += 4; diff --git a/prism/options.h b/prism/options.h index d07f5aa4fa7e72..d0b46a086491c7 100644 --- a/prism/options.h +++ b/prism/options.h @@ -66,12 +66,6 @@ typedef struct { */ int32_t line; - /** - * The offset within the file that the parse starts on. This value is - * 0-indexed. - */ - uint32_t offset; - /** * The name of the encoding that the source file is in. Note that this must * correspond to a name that can be found with Encoding.find in Ruby. @@ -164,14 +158,6 @@ PRISM_EXPORTED_FUNCTION void pm_options_filepath_set(pm_options_t *options, cons */ PRISM_EXPORTED_FUNCTION void pm_options_line_set(pm_options_t *options, int32_t line); -/** - * Set the offset option on the given options struct. - * - * @param options The options struct to set the offset on. - * @param offset The offset to set. - */ -PRISM_EXPORTED_FUNCTION void pm_options_offset_set(pm_options_t *options, uint32_t offset); - /** * Set the encoding option on the given options struct. * @@ -267,7 +253,6 @@ PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options); * | `4` | the length of the filepath | * | ... | the filepath bytes | * | `4` | the line number | - * | `4` | the offset | * | `4` | the length the encoding | * | ... | the encoding bytes | * | `1` | frozen string literal | @@ -286,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.h b/prism/pack.h index cfdc251fe6d589..0b0b4b19cc85d4 100644 --- a/prism/pack.h +++ b/prism/pack.h @@ -6,6 +6,8 @@ #ifndef PRISM_PACK_H #define PRISM_PACK_H +#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. @@ -15,8 +17,6 @@ void pm_pack_parse(void); #else -#include "prism/defines.h" - #include #include diff --git a/prism/parser.h b/prism/parser.h index b685fa377d7eac..f706a67de7c75d 100644 --- a/prism/parser.h +++ b/prism/parser.h @@ -6,8 +6,8 @@ #ifndef PRISM_PARSER_H #define PRISM_PARSER_H -#include "prism/ast.h" #include "prism/defines.h" +#include "prism/ast.h" #include "prism/encoding.h" #include "prism/options.h" #include "prism/util/pm_constant_pool.h" @@ -173,7 +173,7 @@ typedef struct pm_lex_mode { * This is the character set that should be used to delimit the * tokens within the regular expression. */ - uint8_t breakpoints[6]; + uint8_t breakpoints[7]; } regexp; struct { @@ -206,7 +206,7 @@ typedef struct pm_lex_mode { * This is the character set that should be used to delimit the * tokens within the string. */ - uint8_t breakpoints[6]; + uint8_t breakpoints[7]; } string; struct { diff --git a/prism/prettyprint.h b/prism/prettyprint.h index ea11b4a24687b3..5a52b2b6b8eb43 100644 --- a/prism/prettyprint.h +++ b/prism/prettyprint.h @@ -6,14 +6,14 @@ #ifndef PRISM_PRETTYPRINT_H #define PRISM_PRETTYPRINT_H +#include "prism/defines.h" + #ifdef PRISM_EXCLUDE_PRETTYPRINT void pm_prettyprint(void); #else -#include "prism/defines.h" - #include #include "prism/ast.h" diff --git a/prism/prism.c b/prism/prism.c index 8a6ca0eccc4e6a..d03a11583f7a4d 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -260,10 +260,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 +311,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 +346,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. @@ -500,6 +509,9 @@ debug_lex_state_set(pm_parser_t *parser, pm_lex_state_t state, char const * call /** 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 */ /******************************************************************************/ @@ -812,12 +824,21 @@ pm_parser_warn_conditional_predicate_literal(pm_parser_t *parser, pm_node_t *nod } /** - * Add a warning to the parser if the value that is being written inside of a - * predicate to a conditional is a literal. + * Return true if the value being written within the predicate of a conditional + * is a literal value. */ -static void -pm_conditional_predicate_warn_write_literal(pm_parser_t *parser, pm_node_t *node) { +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; + for (size_t index = 0; index < cast->elements.size; index++) { + if (!pm_conditional_predicate_warn_write_literal_p(cast->elements.nodes[index])) { + return false; + } + } + return true; + } case PM_FALSE_NODE: case PM_FLOAT_NODE: case PM_IMAGINARY_NODE: @@ -831,10 +852,20 @@ pm_conditional_predicate_warn_write_literal(pm_parser_t *parser, pm_node_t *node case PM_STRING_NODE: case PM_SYMBOL_NODE: case PM_TRUE_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); - break; + return true; default: - break; + return false; + } +} + +/** + * Add a warning to the parser if the value that is being written inside of a + * predicate to a conditional is a literal. + */ +static inline void +pm_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); } } @@ -973,9 +1004,9 @@ pm_conditional_predicate(pm_parser_t *parser, pm_node_t *node, pm_conditional_pr /** * 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); */ @@ -1162,6 +1193,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 */ /******************************************************************************/ @@ -1214,10 +1316,12 @@ pm_node_flag_set_repeated_parameter(pm_node_t *node) { * 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; @@ -1230,9 +1334,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; @@ -1877,7 +1988,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) { @@ -3314,6 +3424,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] == '_') { @@ -4272,6 +4391,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, @@ -4293,14 +4413,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; + } + } } /** @@ -4313,6 +4474,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, @@ -4324,30 +4486,43 @@ pm_interpolated_string_node_create(pm_parser_t *parser, const pm_token_t *openin }; if (parts != NULL) { - node->parts = *parts; + for (size_t index = 0; index < parts->size; index++) { + pm_interpolated_string_node_append(parser, node, parts->nodes[index]); + } } 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; } @@ -4362,6 +4537,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, @@ -4373,22 +4549,14 @@ pm_interpolated_symbol_node_create(pm_parser_t *parser, const pm_token_t *openin }; if (parts != NULL) { - node->parts = *parts; + for (size_t index = 0; index < parts->size; index++) { + pm_interpolated_symbol_node_append(node, parts->nodes[index]); + } } 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. */ @@ -4414,6 +4582,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; } @@ -4775,7 +4947,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) { @@ -4790,36 +4963,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. */ @@ -5473,7 +5616,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; } @@ -5528,7 +5671,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) @@ -5593,7 +5736,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) { @@ -6922,9 +7065,9 @@ pm_parser_local_add_location(pm_parser_t *parser, const uint8_t *start, const ui /** * Add a local variable from a token to the current scope. */ -static inline void +static inline pm_constant_id_t pm_parser_local_add_token(pm_parser_t *parser, pm_token_t *token) { - pm_parser_local_add_location(parser, token->start, token->end); + return pm_parser_local_add_location(parser, token->start, token->end); } /** @@ -7012,7 +7155,7 @@ pm_parser_parameter_name_check(pm_parser_t *parser, const pm_token_t *name) { if (pm_constant_id_list_includes(&parser->current_scope->locals, constant_id)) { // 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; } @@ -7104,7 +7247,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) { @@ -7427,7 +7570,7 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) { // 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; @@ -7860,8 +8003,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; } @@ -7932,10 +8074,11 @@ lex_global_variable(pm_parser_t *parser) { parser->current.end += width; } while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0); } 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; @@ -8152,7 +8295,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]) { @@ -8184,7 +8327,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; @@ -8888,7 +9031,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; @@ -8896,6 +9039,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 @@ -8922,7 +9068,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) { @@ -9557,7 +9703,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); @@ -10407,7 +10553,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); @@ -10492,19 +10638,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++); @@ -10597,12 +10743,6 @@ parser_lex(pm_parser_t *parser) { pm_token_buffer_t token_buffer = { 0 }; while (breakpoint != NULL) { - // If we hit a null byte, skip directly past it. - if (*breakpoint == '\0') { - breakpoint = pm_strpbrk(parser, breakpoint + 1, breakpoints, parser->end - (breakpoint + 1), true); - continue; - } - // If we hit whitespace, then we must have received content by // now, so we can return an element of the list. if (pm_char_is_whitespace(*breakpoint)) { @@ -10639,6 +10779,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. @@ -10774,36 +10920,6 @@ parser_lex(pm_parser_t *parser) { pm_regexp_token_buffer_t token_buffer = { 0 }; while (breakpoint != NULL) { - // If we hit a null byte, skip directly past it. - if (*breakpoint == '\0') { - parser->current.end = breakpoint + 1; - breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - continue; - } - - // If we've hit a newline, then we need to track that in the - // list of newlines. - if (*breakpoint == '\n') { - // For the special case of a newline-terminated regular expression, we will pass - // through this branch twice -- once with PM_TOKEN_REGEXP_BEGIN and then again - // with PM_TOKEN_STRING_CONTENT. Let's avoid tracking the newline twice, by - // tracking it only in the REGEXP_BEGIN case. - if ( - !(lex_mode->as.regexp.terminator == '\n' && parser->current.type != PM_TOKEN_REGEXP_BEGIN) - && parser->heredoc_end == NULL - ) { - pm_newline_list_append(&parser->newline_list, breakpoint); - } - - if (lex_mode->as.regexp.terminator != '\n') { - // If the terminator is not a newline, then we can set - // the next breakpoint and continue. - parser->current.end = breakpoint + 1; - breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - continue; - } - } - // If we hit the terminator, we need to determine what kind of // token to return. if (*breakpoint == lex_mode->as.regexp.terminator) { @@ -10823,9 +10939,17 @@ parser_lex(pm_parser_t *parser) { LEX(PM_TOKEN_STRING_CONTENT); } + // Check here if we need to track the newline. + size_t eol_length = match_eol_at(parser, breakpoint); + if (eol_length) { + parser->current.end = breakpoint + eol_length; + pm_newline_list_append(&parser->newline_list, parser->current.end - 1); + } else { + parser->current.end = breakpoint + 1; + } + // Since we've hit the terminator of the regular expression, // we now need to parse the options. - parser->current.end = breakpoint + 1; parser->current.end += pm_strspn_regexp_option(parser->current.end, parser->end - parser->current.end); lex_mode_pop(parser); @@ -10833,114 +10957,152 @@ parser_lex(pm_parser_t *parser) { LEX(PM_TOKEN_REGEXP_END); } - // If we hit escapes, then we need to treat the next token - // literally. In this case we'll skip past the next character + // If we've hit the incrementor, then we need to skip past it // and find the next breakpoint. - if (*breakpoint == '\\') { + if (*breakpoint && *breakpoint == lex_mode->as.regexp.incrementor) { parser->current.end = breakpoint + 1; + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); + lex_mode->as.regexp.nesting++; + continue; + } - // If we've hit the end of the file, then break out of the - // loop by setting the breakpoint to NULL. - if (parser->current.end == parser->end) { - breakpoint = NULL; - continue; - } + switch (*breakpoint) { + case '\0': + // If we hit a null byte, skip directly past it. + parser->current.end = breakpoint + 1; + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); + break; + case '\r': + if (peek_at(parser, breakpoint + 1) != '\n') { + parser->current.end = breakpoint + 1; + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); + break; + } - pm_regexp_token_buffer_escape(parser, &token_buffer); - uint8_t peeked = peek(parser); + breakpoint++; + parser->current.end = breakpoint; + pm_regexp_token_buffer_escape(parser, &token_buffer); + token_buffer.base.cursor = breakpoint; - switch (peeked) { - case '\r': - parser->current.end++; - if (peek(parser) != '\n') { - if (lex_mode->as.regexp.terminator != '\r') { - pm_token_buffer_push_byte(&token_buffer.base, '\\'); - } - pm_regexp_token_buffer_push_byte(&token_buffer, '\r'); - pm_token_buffer_push_byte(&token_buffer.base, '\r'); - break; - } /* fallthrough */ - case '\n': - if (parser->heredoc_end) { - // ... if we are on the same line as a heredoc, - // flush the heredoc and continue parsing after - // heredoc_end. - parser_flush_heredoc_end(parser); - pm_regexp_token_buffer_copy(parser, &token_buffer); - LEX(PM_TOKEN_STRING_CONTENT); - } else { - // ... else track the newline. - pm_newline_list_append(&parser->newline_list, parser->current.end); - } - - parser->current.end++; + case '\n': + // If we've hit a newline, then we need to track that in + // the list of newlines. + if (parser->heredoc_end == NULL) { + pm_newline_list_append(&parser->newline_list, breakpoint); + parser->current.end = breakpoint + 1; + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); break; - case 'c': - case 'C': - case 'M': - case 'u': - case 'x': - escape_read(parser, &token_buffer.regexp_buffer, &token_buffer.base.buffer, PM_ESCAPE_FLAG_REGEXP); + } + + parser->current.end = breakpoint + 1; + parser_flush_heredoc_end(parser); + pm_regexp_token_buffer_flush(parser, &token_buffer); + LEX(PM_TOKEN_STRING_CONTENT); + case '\\': { + // If we hit escapes, then we need to treat the next + // token literally. In this case we'll skip past the + // next character and find the next breakpoint. + parser->current.end = breakpoint + 1; + + // If we've hit the end of the file, then break out of + // the loop by setting the breakpoint to NULL. + if (parser->current.end == parser->end) { + breakpoint = NULL; break; - default: - if (lex_mode->as.regexp.terminator == peeked) { - // Some characters when they are used as the - // terminator also receive an escape. They are - // enumerated here. - switch (peeked) { - case '$': case ')': case '*': case '+': - case '.': case '>': case '?': case ']': - case '^': case '|': case '}': + } + + pm_regexp_token_buffer_escape(parser, &token_buffer); + uint8_t peeked = peek(parser); + + switch (peeked) { + case '\r': + parser->current.end++; + if (peek(parser) != '\n') { + if (lex_mode->as.regexp.terminator != '\r') { pm_token_buffer_push_byte(&token_buffer.base, '\\'); - break; - default: - break; + } + pm_regexp_token_buffer_push_byte(&token_buffer, '\r'); + pm_token_buffer_push_byte(&token_buffer.base, '\r'); + break; + } + /* fallthrough */ + case '\n': + if (parser->heredoc_end) { + // ... if we are on the same line as a heredoc, + // flush the heredoc and continue parsing after + // heredoc_end. + parser_flush_heredoc_end(parser); + pm_regexp_token_buffer_copy(parser, &token_buffer); + LEX(PM_TOKEN_STRING_CONTENT); + } else { + // ... else track the newline. + pm_newline_list_append(&parser->newline_list, parser->current.end); } - pm_regexp_token_buffer_push_byte(&token_buffer, peeked); - pm_token_buffer_push_byte(&token_buffer.base, peeked); parser->current.end++; break; - } - - if (peeked < 0x80) pm_token_buffer_push_byte(&token_buffer.base, '\\'); - pm_regexp_token_buffer_push_escaped(&token_buffer, parser); - break; - } + case 'c': + case 'C': + case 'M': + case 'u': + case 'x': + escape_read(parser, &token_buffer.regexp_buffer, &token_buffer.base.buffer, PM_ESCAPE_FLAG_REGEXP); + break; + default: + if (lex_mode->as.regexp.terminator == peeked) { + // Some characters when they are used as the + // terminator also receive an escape. They are + // enumerated here. + switch (peeked) { + case '$': case ')': case '*': case '+': + case '.': case '>': case '?': case ']': + case '^': case '|': case '}': + pm_token_buffer_push_byte(&token_buffer.base, '\\'); + break; + default: + break; + } - token_buffer.base.cursor = parser->current.end; - breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - continue; - } + pm_regexp_token_buffer_push_byte(&token_buffer, peeked); + pm_token_buffer_push_byte(&token_buffer.base, peeked); + parser->current.end++; + break; + } - // If we hit a #, then we will attempt to lex interpolation. - if (*breakpoint == '#') { - pm_token_type_t type = lex_interpolation(parser, breakpoint); + if (peeked < 0x80) pm_token_buffer_push_byte(&token_buffer.base, '\\'); + pm_regexp_token_buffer_push_escaped(&token_buffer, parser); + break; + } - if (type == PM_TOKEN_NOT_PROVIDED) { - // If we haven't returned at this point then we had - // something that looked like an interpolated class or - // instance variable like "#@" but wasn't actually. In - // this case we'll just skip to the next breakpoint. + token_buffer.base.cursor = parser->current.end; breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - continue; + break; } + case '#': { + // If we hit a #, then we will attempt to lex + // interpolation. + pm_token_type_t type = lex_interpolation(parser, breakpoint); - if (type == PM_TOKEN_STRING_CONTENT) { - pm_regexp_token_buffer_flush(parser, &token_buffer); - } + if (type == PM_TOKEN_NOT_PROVIDED) { + // If we haven't returned at this point then we had + // something that looked like an interpolated class or + // instance variable like "#@" but wasn't actually. In + // this case we'll just skip to the next breakpoint. + breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); + break; + } - LEX(type); - } + if (type == PM_TOKEN_STRING_CONTENT) { + pm_regexp_token_buffer_flush(parser, &token_buffer); + } - // If we've hit the incrementor, then we need to skip past it - // and find the next breakpoint. - assert(*breakpoint == lex_mode->as.regexp.incrementor); - parser->current.end = breakpoint + 1; - breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, false); - lex_mode->as.regexp.nesting++; - continue; + LEX(type); + } + default: + assert(false && "unreachable"); + break; + } } if (parser->current.end > parser->current.start) { @@ -11033,29 +11195,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; @@ -11258,11 +11434,11 @@ parser_lex(pm_parser_t *parser) { // Otherwise we'll be parsing string content. These are the places // where we need to split up the content of the heredoc. We'll use // strpbrk to find the first of these characters. - uint8_t breakpoints[] = "\n\\#"; + uint8_t breakpoints[] = "\r\n\\#"; pm_heredoc_quote_t quote = lex_mode->as.heredoc.quote; if (quote == PM_HEREDOC_QUOTE_SINGLE) { - breakpoints[2] = '\0'; + breakpoints[3] = '\0'; } const uint8_t *breakpoint = pm_strpbrk(parser, parser->current.end, breakpoints, parser->end - parser->current.end, true); @@ -11276,6 +11452,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); @@ -11354,7 +11545,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. @@ -11566,7 +11757,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, @@ -11835,7 +12026,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. */ @@ -11868,7 +12058,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); @@ -12433,7 +12623,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) { @@ -12871,7 +13061,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; @@ -13322,7 +13512,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; @@ -13408,7 +13598,28 @@ parse_block_parameters( if (accept1(parser, PM_TOKEN_SEMICOLON)) { do { - expect1(parser, PM_TOKEN_IDENTIFIER, PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE); + 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); @@ -14051,14 +14262,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); } } @@ -14069,7 +14278,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; @@ -14090,14 +14300,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); @@ -14105,7 +14315,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 }; @@ -14377,7 +14589,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 @@ -14416,13 +14628,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)) { @@ -14430,7 +14659,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 @@ -14449,7 +14678,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); } @@ -14461,7 +14690,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); } @@ -14544,18 +14773,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); + } + + 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. @@ -14566,7 +14807,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); @@ -14578,8 +14819,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); + } + + 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); @@ -14590,30 +14843,44 @@ parse_pattern_keyword_rest(pm_parser_t *parser) { * value. This will use an implicit local variable target. */ static pm_node_t * -parse_pattern_hash_implicit_value(pm_parser_t *parser, pm_symbol_node_t *key) { +parse_pattern_hash_implicit_value(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_symbol_node_t *key) { const pm_location_t *value_loc = &((pm_symbol_node_t *) key)->value_loc; - pm_constant_id_t name = pm_parser_constant_id_location(parser, value_loc->start, value_loc->end); - - int current_depth = pm_parser_local_depth_constant_id(parser, name); - uint32_t depth; + pm_constant_id_t constant_id = pm_parser_constant_id_location(parser, value_loc->start, value_loc->end); - if (current_depth == -1) { - pm_parser_local_add_location(parser, value_loc->start, value_loc->end); - depth = 0; - } else { - depth = (uint32_t) current_depth; + int depth; + if ((depth = pm_parser_local_depth_constant_id(parser, constant_id)) == -1) { + pm_parser_local_add(parser, constant_id); } - pm_local_variable_target_node_t *target = pm_local_variable_target_node_create_values(parser, value_loc, name, depth); + parse_pattern_capture(parser, captures, constant_id, value_loc); + pm_local_variable_target_node_t *target = pm_local_variable_target_node_create( + parser, + value_loc, + constant_id, + (uint32_t) (depth == -1 ? 0 : depth) + ); + return (pm_node_t *) pm_implicit_node_create(parser, (pm_node_t *) target); } +/** + * Add a node to the list of keys for a hash pattern, and if it is a duplicate + * then add an error to the parser. + */ +static void +parse_pattern_hash_key(pm_parser_t *parser, pm_static_literals_t *keys, pm_node_t *node) { + if (pm_static_literals_add(parser, keys, node) != NULL) { + pm_parser_err_node(parser, node, PM_ERR_PATTERN_HASH_KEY_DUPLICATE); + } +} + /** * Parse a hash pattern. */ static pm_hash_pattern_node_t * -parse_pattern_hash(pm_parser_t *parser, pm_node_t *first_node) { +parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node_t *first_node) { pm_node_list_t assocs = { 0 }; + pm_static_literals_t keys = { 0 }; pm_node_t *rest = NULL; switch (PM_NODE_TYPE(first_node)) { @@ -14623,16 +14890,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); @@ -14666,7 +14934,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; @@ -14677,14 +14945,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); @@ -14701,6 +14969,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; } @@ -14708,18 +14977,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); } - 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; @@ -14728,15 +15004,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; @@ -14798,7 +15073,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); @@ -14812,7 +15087,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); @@ -14959,14 +15234,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); @@ -14979,7 +15254,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 { @@ -14996,9 +15271,9 @@ 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); } @@ -15008,7 +15283,7 @@ parse_pattern_primitives(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { pm_token_t opening = parser->current; parser_lex(parser); - pm_node_t *body = 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); @@ -15040,16 +15315,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); } - 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); } @@ -15060,7 +15342,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; @@ -15070,30 +15352,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)) { @@ -15113,7 +15395,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 @@ -15124,7 +15406,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); @@ -15185,7 +15467,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) { @@ -15273,6 +15555,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)) { @@ -15327,6 +15611,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 @@ -15350,6 +15636,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) { @@ -15378,11 +15666,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); } } @@ -15958,6 +16246,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); @@ -16043,7 +16332,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); @@ -16084,8 +16373,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b 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; @@ -16163,8 +16452,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; @@ -16174,11 +16464,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); @@ -16189,9 +16484,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)) { @@ -16204,8 +16499,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; @@ -16213,8 +16508,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); } @@ -16330,7 +16625,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b (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); } @@ -17316,15 +17611,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"); @@ -17349,7 +17644,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 @@ -17358,7 +17653,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: { @@ -17378,7 +17673,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 @@ -17389,7 +17684,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: @@ -17490,7 +17785,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b expect1(parser, PM_TOKEN_REGEXP_END, PM_ERR_REGEXP_TERM); } - pm_interpolated_regular_expression_node_closing_set(interpolated, &closing); + pm_interpolated_regular_expression_node_closing_set(parser, interpolated, &closing); return (pm_node_t *) interpolated; } case PM_TOKEN_BACKTICK: @@ -17849,7 +18144,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 @@ -17870,18 +18165,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; } @@ -17900,7 +18209,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 }; @@ -17915,14 +18224,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. @@ -17933,11 +18241,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) { @@ -17946,19 +18249,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) { + // 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); } + // 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); } } @@ -18730,11 +19036,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); } @@ -18745,11 +19053,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); } @@ -18790,8 +19100,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; } @@ -18989,6 +19299,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. */ @@ -19075,22 +19417,6 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm // line option parser->start_line = options->line; - // offset option - if (options->offset != 0) { - const uint8_t *cursor = parser->start; - const uint8_t *offset = cursor + options->offset; - - const uint8_t *newline = NULL; - while ((newline = next_newline(cursor, parser->end - cursor)) != NULL) { - if (newline > offset) break; - pm_newline_list_append(&parser->newline_list, newline); - cursor = newline + 1; - } - - parser->previous = (pm_token_t) { .type = PM_TOKEN_EOF, .start = offset, .end = offset }; - parser->current = (pm_token_t) { .type = PM_TOKEN_EOF, .start = offset, .end = offset }; - } - // encoding option size_t encoding_length = pm_string_length(&options->encoding); if (encoding_length > 0) { @@ -19137,16 +19463,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); } /** @@ -19474,7 +19865,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) { @@ -19530,7 +19921,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; @@ -19553,8 +19948,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 @@ -19567,9 +19961,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) { @@ -19651,14 +20053,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)) { @@ -19677,33 +20079,34 @@ 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); + pm_buffer_append_string(buffer, PM_COLOR_RED "^" PM_COLOR_RESET, 11); } else { pm_buffer_append_byte(buffer, '^'); } @@ -19712,16 +20115,18 @@ pm_parser_errors_format(const pm_parser_t *parser, pm_buffer_t *buffer, bool col column += (char_width == 0 ? 1 : char_width); } - pm_buffer_append_byte(buffer, ' '); + if (inline_messages) { + pm_buffer_append_byte(buffer, ' '); + const char *message = error->error->message; + pm_buffer_append_string(buffer, message, strlen(message)); + } - const char *message = error->error->message; - pm_buffer_append_string(buffer, message, strlen(message)); pm_buffer_append_byte(buffer, '\n'); // Here we determine how many lines of padding to display after the // error, depending on where the next error is in source. last_line = error->line; - int32_t next_line = (index == error_list->size - 1) ? ((int32_t) newline_list->size) : errors[index + 1].line; + int32_t next_line = (index == error_list->size - 1) ? (((int32_t) newline_list->size) + parser->start_line) : errors[index + 1].line; if (next_line - last_line > 1) { pm_buffer_append_string(buffer, " ", 2); diff --git a/prism/prism.h b/prism/prism.h index 34540b9441954a..59067c3021a70a 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -222,10 +223,12 @@ 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. diff --git a/prism/static_literals.c b/prism/static_literals.c index 1708e31a1674de..469bdfd5ea2bd4 100644 --- a/prism/static_literals.c +++ b/prism/static_literals.c @@ -95,13 +95,17 @@ node_hash(const pm_parser_t *parser, const pm_node_t *node) { // Strings hash their value and mix in their flags so that different // encodings are not considered equal. const pm_string_t *value = &((const pm_string_node_t *) node)->unescaped; - return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) node->flags); + + pm_node_flags_t flags = node->flags; + flags &= (PM_STRING_FLAGS_FORCED_BINARY_ENCODING | PM_STRING_FLAGS_FORCED_UTF8_ENCODING); + + return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) flags); } case PM_SOURCE_FILE_NODE: { // Source files hash their value and mix in their flags so that // different encodings are not considered equal. const pm_string_t *value = &((const pm_source_file_node_t *) node)->filepath; - return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) node->flags); + return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)); } case PM_REGULAR_EXPRESSION_NODE: { // Regular expressions hash their value and mix in their flags so @@ -316,8 +320,6 @@ pm_compare_regular_expression_nodes(PRISM_ATTRIBUTE_UNUSED const pm_parser_t *pa */ pm_node_t * pm_static_literals_add(const pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *node) { - if (!PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)) return NULL; - switch (PM_NODE_TYPE(node)) { case PM_INTEGER_NODE: case PM_SOURCE_LINE_NODE: @@ -435,8 +437,6 @@ pm_rational_inspect(pm_buffer_t *buffer, pm_rational_node_t *node) { */ PRISM_EXPORTED_FUNCTION void pm_static_literal_inspect(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *node) { - assert(PM_NODE_FLAG_P(node, PM_NODE_FLAG_STATIC_LITERAL)); - switch (PM_NODE_TYPE(node)) { case PM_FALSE_NODE: pm_buffer_append_string(buffer, "false", 5); diff --git a/prism/templates/include/prism/diagnostic.h.erb b/prism/templates/include/prism/diagnostic.h.erb index 53a700d409397d..07bbc8fae79264 100644 --- a/prism/templates/include/prism/diagnostic.h.erb +++ b/prism/templates/include/prism/diagnostic.h.erb @@ -66,11 +66,14 @@ typedef struct { * The levels of errors generated during parsing. */ typedef enum { - /** For errors that cannot be recovered from. */ - PM_ERROR_LEVEL_FATAL = 0, + /** For errors that should raise a syntax error. */ + PM_ERROR_LEVEL_SYNTAX = 0, /** For errors that should raise an argument error. */ - PM_ERROR_LEVEL_ARGUMENT = 1 + PM_ERROR_LEVEL_ARGUMENT = 1, + + /** For errors that should raise a load error. */ + PM_ERROR_LEVEL_LOAD = 2 } pm_error_level_t; /** diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 1a723f64afcd3f..ac7ab4fff3c65d 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -131,8 +131,8 @@ module Prism comments = load_comments magic_comments = Array.new(load_varuint) { MagicComment.new(load_location_object, load_location_object) } data_loc = load_optional_location_object - errors = Array.new(load_varuint) { ParseError.new(DIAGNOSTIC_TYPES[io.getbyte], load_embedded_string, load_location_object, load_error_level) } - warnings = Array.new(load_varuint) { ParseWarning.new(DIAGNOSTIC_TYPES[io.getbyte], load_embedded_string, load_location_object, load_warning_level) } + errors = Array.new(load_varuint) { ParseError.new(DIAGNOSTIC_TYPES[load_varuint], load_embedded_string, load_location_object, load_error_level) } + warnings = Array.new(load_varuint) { ParseWarning.new(DIAGNOSTIC_TYPES[load_varuint], load_embedded_string, load_location_object, load_warning_level) } [comments, magic_comments, data_loc, errors, warnings] end @@ -296,9 +296,11 @@ module Prism case level when 0 - :fatal + :syntax when 1 :argument + when 2 + :load else raise "Unknown level: #{level}" end diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 2a3ac1993009e8..3d5073147a9ffb 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -65,8 +65,9 @@ typedef struct { * * For errors, they are: * - * * `PM_ERROR_LEVEL_FATAL` - The default level for errors. + * * `PM_ERROR_LEVEL_SYNTAX` - Errors that should raise SyntaxError. * * `PM_ERROR_LEVEL_ARGUMENT` - Errors that should raise ArgumentError. + * * `PM_ERROR_LEVEL_LOAD` - Errors that should raise LoadError. * * For warnings, they are: * @@ -75,241 +76,251 @@ typedef struct { */ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { // Special error that can be replaced - [PM_ERR_CANNOT_PARSE_EXPRESSION] = { "cannot parse the expression", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_CANNOT_PARSE_EXPRESSION] = { "cannot parse the expression", PM_ERROR_LEVEL_SYNTAX }, // Errors that should raise argument errors [PM_ERR_INVALID_ENCODING_MAGIC_COMMENT] = { "unknown or invalid encoding in the magic comment", PM_ERROR_LEVEL_ARGUMENT }, + // Errors that should raise load errors + [PM_ERR_SCRIPT_NOT_FOUND] = { "no Ruby script found in input", PM_ERROR_LEVEL_LOAD }, + // Errors that should raise syntax errors - [PM_ERR_ALIAS_ARGUMENT] = { "invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_AMPAMPEQ_MULTI_ASSIGN] = { "unexpected `&&=` in a multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_AFTER_BLOCK] = { "unexpected argument after a block argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES] = { "unexpected argument after `...`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_BARE_HASH] = { "unexpected bare hash argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_BLOCK_FORWARDING] = { "both a block argument and a forwarding argument; only one block is allowed", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_BLOCK_MULTI] = { "multiple block arguments; only one block is allowed", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_FORMAL_CLASS] = { "invalid formal argument; formal argument cannot be a class variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_FORMAL_CONSTANT] = { "invalid formal argument; formal argument cannot be a constant", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_FORMAL_GLOBAL] = { "invalid formal argument; formal argument cannot be a global variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_FORMAL_IVAR] = { "invalid formal argument; formal argument cannot be an instance variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_FORWARDING_UNBOUND] = { "unexpected `...` in an non-parenthesized call", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_IN] = { "unexpected `in` keyword in arguments", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_NO_FORWARDING_AMP] = { "unexpected `&` when the parent method is not forwarding", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES] = { "unexpected `...` when the parent method is not forwarding", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_NO_FORWARDING_STAR] = { "unexpected `*` when the parent method is not forwarding", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ARGUMENT_NO_FORWARDING_STAR_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; else without rescue is useless", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BEGIN_TERM] = { "expected an `end` to close the `begin` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BEGIN_UPCASE_BRACE] = { "expected a `{` after `BEGIN`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BEGIN_UPCASE_TERM] = { "expected a `}` to close the `BEGIN` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BEGIN_UPCASE_TOPLEVEL] = { "BEGIN is permitted only at toplevel", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE] = { "expected a local variable name in the block parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BLOCK_PARAM_PIPE_TERM] = { "expected the block parameters to end with `|`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BLOCK_TERM_BRACE] = { "expected a block beginning with `{` to end with `}`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_BLOCK_TERM_END] = { "expected a block beginning with `do` to end with `end`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CANNOT_PARSE_STRING_PART] = { "cannot parse the string part", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CASE_EXPRESSION_AFTER_CASE] = { "expected an expression after `case`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CASE_EXPRESSION_AFTER_WHEN] = { "expected an expression after `when`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CASE_MATCH_MISSING_PREDICATE] = { "expected a predicate for a case matching statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CASE_MISSING_CONDITIONS] = { "expected a `when` or `in` clause after `case`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CASE_TERM] = { "expected an `end` to close the `case` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CLASS_IN_METHOD] = { "unexpected class definition in a method definition", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CLASS_NAME] = { "expected a constant name after `class`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CLASS_SUPERCLASS] = { "expected a superclass after `<`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CLASS_TERM] = { "expected an `end` to close the `class` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CLASS_UNEXPECTED_END] = { "unexpected `end`, expecting ';' or '\\n'", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_ELSIF_PREDICATE] = { "expected a predicate expression for the `elsif` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_IF_PREDICATE] = { "expected a predicate expression for the `if` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_PREDICATE_TERM] = { "expected `then` or `;` or '\\n'", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_TERM] = { "expected an `end` to close the conditional clause", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_TERM_ELSE] = { "expected an `end` to close the `else` clause", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_UNLESS_PREDICATE] = { "expected a predicate expression for the `unless` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_UNTIL_PREDICATE] = { "expected a predicate expression for the `until` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONDITIONAL_WHILE_PREDICATE] = { "expected a predicate expression for the `while` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_NAME] = { "expected a method name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_NAME_AFTER_RECEIVER] = { "expected a method name after the receiver", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_PARAMS_TERM_PAREN] = { "expected a `)` to close the parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_RECEIVER] = { "expected a receiver for the method definition", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_RECEIVER_TERM] = { "expected a `.` or `::` after the receiver in a method definition", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEF_TERM] = { "expected an `end` to close the `def` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_DEFINED_EXPRESSION] = { "expected an expression after `defined?`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EMBDOC_TERM] = { "could not find a terminator for the embedded document", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EMBEXPR_END] = { "expected a `}` to close the embedded expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EMBVAR_INVALID] = { "invalid embedded variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_END_UPCASE_BRACE] = { "expected a `{` after `END`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_END_UPCASE_TERM] = { "expected a `}` to close the `END` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_CONTROL] = { "invalid control escape sequence", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT] = { "invalid control escape sequence; control cannot be repeated", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_HEXADECIMAL] = { "invalid hexadecimal escape sequence", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_META] = { "invalid meta escape sequence", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_META_REPEAT] = { "invalid meta escape sequence; meta cannot be repeated", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_UNICODE] = { "invalid Unicode escape sequence", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS] = { "invalid Unicode escape sequence; Unicode cannot be combined with control or meta flags", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL] = { "invalid Unicode escape sequence; multiple codepoints are not allowed in a character literal", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_UNICODE_LONG] = { "invalid Unicode escape sequence; maximum length is 6 digits", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_ESCAPE_INVALID_UNICODE_TERM] = { "invalid Unicode escape sequence; needs closing `}`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_ARGUMENT] = { "expected an argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EOL_AFTER_STATEMENT] = { "unexpected %s, expecting end-of-input", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ] = { "expected an expression after `&&=`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ] = { "expected an expression after `||=`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA] = { "expected an expression after `,`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL] = { "expected an expression after `=`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS] = { "expected an expression after `<<`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN] = { "expected an expression after `(`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR] = { "expected an expression after the operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT] = { "expected an expression after `*` splat in an argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH] = { "expected an expression after `**` in a hash", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EXPRESSION_AFTER_STAR] = { "expected an expression after `*`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_RPAREN] = { "expected a matching `)`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_RPAREN_AFTER_MULTI] = { "expected a `)` after multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_RPAREN_REQ_PARAMETER] = { "expected a `)` to end a required parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_STRING_CONTENT] = { "expected string content after opening string delimiter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_WHEN_DELIMITER] = { "expected a delimiter after the predicates of a `when` clause", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPRESSION_BARE_HASH] = { "unexpected bare hash in expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_FLOAT_PARSE] = { "could not parse the float '%.*s'", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_FOR_COLLECTION] = { "expected a collection after the `in` in a `for` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_FOR_INDEX] = { "expected an index after `for`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_FOR_IN] = { "expected an `in` after the index in a `for` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_FOR_TERM] = { "expected an `end` to close the `for` loop", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HASH_EXPRESSION_AFTER_LABEL] = { "expected an expression after the label in a hash", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HASH_KEY] = { "unexpected %s, expecting '}' or a key in the hash literal", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HASH_ROCKET] = { "expected a `=>` between the hash key and value", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HASH_TERM] = { "expected a `}` to close the hash literal", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HASH_VALUE] = { "expected a value in the hash literal", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_HEREDOC_TERM] = { "could not find a terminator for the heredoc", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_QUESTION_MARK] = { "incomplete expression at `?`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_VARIABLE_CLASS_3_3_0] = { "`%.*s' is not allowed as a class variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_VARIABLE_CLASS] = { "'%.*s' is not allowed as a class variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE_3_3_0] = { "`%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE] = { "'%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_FLOAT_EXPONENT] = { "invalid exponent", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_NUMBER_BINARY] = { "invalid binary number", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_NUMBER_DECIMAL] = { "invalid decimal number", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_NUMBER_HEXADECIMAL] = { "invalid hexadecimal number", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_NUMBER_OCTAL] = { "invalid octal number", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_NUMBER_UNDERSCORE] = { "invalid underscore placement in number", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_CHARACTER] = { "invalid character 0x%X", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_MULTIBYTE_CHAR] = { "invalid multibyte char (%s)", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_MULTIBYTE_CHARACTER] = { "invalid multibyte character 0x%X", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_MULTIBYTE_ESCAPE] = { "invalid multibyte escape: /%.*s/", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_PRINTABLE_CHARACTER] = { "invalid character `%c`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_PERCENT] = { "invalid `%` token", PM_ERROR_LEVEL_FATAL }, // TODO WHAT? - [PM_ERR_INVALID_VARIABLE_GLOBAL_3_3_0] = { "`%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INVALID_VARIABLE_GLOBAL] = { "'%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_IT_NOT_ALLOWED_NUMBERED] = { "`it` is not allowed when an numbered parameter is defined", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_IT_NOT_ALLOWED_ORDINARY] = { "`it` is not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LAMBDA_OPEN] = { "expected a `do` keyword or a `{` to open the lambda block", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LAMBDA_TERM_BRACE] = { "expected a lambda block beginning with `{` to end with `}`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LAMBDA_TERM_END] = { "expected a lambda block beginning with `do` to end with `end`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_I_LOWER_ELEMENT] = { "expected a symbol in a `%i` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_I_LOWER_TERM] = { "expected a closing delimiter for the `%i` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_I_UPPER_ELEMENT] = { "expected a symbol in a `%I` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_I_UPPER_TERM] = { "expected a closing delimiter for the `%I` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_W_LOWER_ELEMENT] = { "expected a string in a `%w` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_W_LOWER_TERM] = { "expected a closing delimiter for the `%w` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_W_UPPER_ELEMENT] = { "expected a string in a `%W` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_LIST_W_UPPER_TERM] = { "expected a closing delimiter for the `%W` list", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MALLOC_FAILED] = { "failed to allocate memory", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MIXED_ENCODING] = { "UTF-8 mixed within %s source", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MODULE_IN_METHOD] = { "unexpected module definition in a method definition", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MODULE_NAME] = { "expected a constant name after `module`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MODULE_TERM] = { "expected an `end` to close the `module` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MULTI_ASSIGN_MULTI_SPLATS] = { "multiple splats in multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST] = { "unexpected '%.*s' resulting in multiple splats in multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NOT_EXPRESSION] = { "expected an expression after `not`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NO_LOCAL_VARIABLE] = { "%.*s: no such local variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NUMBER_LITERAL_UNDERSCORE] = { "number literal ending with a `_`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NUMBERED_PARAMETER_IT] = { "numbered parameters are not allowed when an 'it' parameter is defined", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NUMBERED_PARAMETER_ORDINARY] = { "numbered parameters are not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_NUMBERED_PARAMETER_OUTER_SCOPE] = { "numbered parameter is already used in outer scope", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_OPERATOR_MULTI_ASSIGN] = { "unexpected operator for a multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_OPERATOR_WRITE_ARGUMENTS] = { "unexpected operator after a call with arguments", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_OPERATOR_WRITE_BLOCK] = { "unexpected operator after a call with a block", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI] = { "unexpected multiple `**` splat parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_BLOCK_MULTI] = { "multiple block parameters; only one block is allowed", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_CIRCULAR] = { "parameter default value references itself", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_METHOD_NAME] = { "unexpected name for a parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_NAME_REPEAT] = { "repeated parameter name", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_NO_DEFAULT] = { "expected a default value for the parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_NO_DEFAULT_KW] = { "expected a default value for the keyword parameter", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_NUMBERED_RESERVED] = { "%.2s is reserved for numbered parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_ORDER] = { "unexpected parameter order", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_SPLAT_MULTI] = { "unexpected multiple `*` splat parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_STAR] = { "unexpected parameter `*`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_UNEXPECTED_FWD] = { "unexpected `...` in parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PARAMETER_WILD_LOOSE_COMMA] = { "unexpected `,` in parameters", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET] = { "expected a pattern expression after the `[` operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA] = { "expected a pattern expression after `,`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET] = { "expected a pattern expression after `=>`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_IN] = { "expected a pattern expression after the `in` keyword", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_KEY] = { "expected a pattern expression after the key", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN] = { "expected a pattern expression after the `(` operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_PIN] = { "expected a pattern expression after the `^` pin operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE] = { "expected a pattern expression after the `|` operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE] = { "expected a pattern expression after the range operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_EXPRESSION_AFTER_REST] = { "unexpected pattern expression after the `**` expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_HASH_KEY] = { "expected a key in the hash pattern", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_HASH_KEY_LABEL] = { "expected a label as the key in the hash pattern", PM_ERROR_LEVEL_FATAL }, // TODO // THIS // AND // ABOVE // IS WEIRD - [PM_ERR_PATTERN_IDENT_AFTER_HROCKET] = { "expected an identifier after the `=>` operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_LABEL_AFTER_COMMA] = { "expected a label after the `,` in the hash pattern", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_REST] = { "unexpected rest pattern", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_TERM_BRACE] = { "expected a `}` to close the pattern expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_TERM_BRACKET] = { "expected a `]` to close the pattern expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PATTERN_TERM_PAREN] = { "expected a `)` to close the pattern expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN] = { "unexpected `||=` in a multiple assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_ENCODING_OPTION_MISMATCH] = { "regexp encoding option '%c' differs from source encoding '%s'", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_INCOMPAT_CHAR_ENCODING] = { "incompatible character encoding: /%.*s/", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_NON_ESCAPED_MBC] = { "/.../n has a non escaped non ASCII character in non ASCII-8BIT script: /%.*s/", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_INVALID_UNICODE_RANGE] = { "invalid Unicode range: /%.*s/", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_TERM] = { "expected a closing delimiter for the regular expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_REGEXP_UTF8_CHAR_NON_UTF8_REGEXP] = { "UTF-8 character in non UTF-8 regexp: /%s/", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_RESCUE_EXPRESSION] = { "expected a rescued expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_RESCUE_MODIFIER_VALUE] = { "expected a value after the `rescue` modifier", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_RESCUE_TERM] = { "expected a closing delimiter for the `rescue` clause", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_RESCUE_VARIABLE] = { "expected an exception variable after `=>` in a rescue statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_RETURN_INVALID] = { "invalid `return` in a class or module body", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_SINGLETON_FOR_LITERALS] = { "cannot define singleton method for literals", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STATEMENT_ALIAS] = { "unexpected an `alias` at a non-statement position", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STATEMENT_POSTEXE_END] = { "unexpected an `END` at a non-statement position", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STATEMENT_PREEXE_BEGIN] = { "unexpected a `BEGIN` at a non-statement position", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STATEMENT_UNDEF] = { "unexpected an `undef` at a non-statement position", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STRING_CONCATENATION] = { "expected a string for concatenation", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STRING_INTERPOLATED_TERM] = { "expected a closing delimiter for the interpolated string", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STRING_LITERAL_EOF] = { "unterminated string meets end of file", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STRING_LITERAL_TERM] = { "unexpected %s, expected a string literal terminator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_SYMBOL_INVALID] = { "invalid symbol", PM_ERROR_LEVEL_FATAL }, // TODO expected symbol? prism.c ~9719 - [PM_ERR_SYMBOL_TERM_DYNAMIC] = { "expected a closing delimiter for the dynamic symbol", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_SYMBOL_TERM_INTERPOLATED] = { "expected a closing delimiter for the interpolated symbol", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_TERNARY_COLON] = { "expected a `:` after the true expression of a ternary operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_TERNARY_EXPRESSION_FALSE] = { "expected an expression after `:` in the ternary operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_TERNARY_EXPRESSION_TRUE] = { "expected an expression after `?` in the ternary operator", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_UNDEF_ARGUMENT] = { "invalid argument being passed to `undef`; expected a bare word, constant, or symbol argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_UNARY_RECEIVER] = { "unexpected %s, expected a receiver for unary `%c`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT] = { "unexpected %s, assuming it is closing the parent %s", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_UNEXPECTED_TOKEN_IGNORE] = { "unexpected %s, ignoring it", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_UNTIL_TERM] = { "expected an `end` to close the `until` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_VOID_EXPRESSION] = { "unexpected void value expression", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_WHILE_TERM] = { "expected an `end` to close the `while` statement", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_WRITE_TARGET_IN_METHOD] = { "dynamic constant assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_WRITE_TARGET_READONLY] = { "Can't set variable %.*s", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_WRITE_TARGET_UNEXPECTED] = { "unexpected write target", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_XSTRING_TERM] = { "expected a closing delimiter for the `%x` or backtick string", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_ALIAS_ARGUMENT] = { "invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ALIAS_ARGUMENT_NUMBERED_REFERENCE] = { "invalid argument being passed to `alias`; can't make alias for the number variables", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_AMPAMPEQ_MULTI_ASSIGN] = { "unexpected `&&=` in a multiple assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_AFTER_BLOCK] = { "unexpected argument after a block argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES] = { "unexpected argument after `...`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_BARE_HASH] = { "unexpected bare hash argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_BLOCK_FORWARDING] = { "both a block argument and a forwarding argument; only one block is allowed", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_BLOCK_MULTI] = { "both block arg and actual block given; only one block is allowed", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_FORMAL_CLASS] = { "invalid formal argument; formal argument cannot be a class variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_FORMAL_CONSTANT] = { "invalid formal argument; formal argument cannot be a constant", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_FORMAL_GLOBAL] = { "invalid formal argument; formal argument cannot be a global variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_FORMAL_IVAR] = { "invalid formal argument; formal argument cannot be an instance variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_FORWARDING_UNBOUND] = { "unexpected `...` in an non-parenthesized call", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_IN] = { "unexpected `in` keyword in arguments", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_AMP] = { "unexpected `&` when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES] = { "unexpected `...` when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_STAR] = { "unexpected `*` when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR] = { "unexpected `**` when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT] = { "unexpected `*` splat argument after a `**` keyword splat argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT] = { "unexpected `*` splat argument after a `*` splat argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_TERM_PAREN] = { "expected a `)` to close the arguments", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_UNEXPECTED_BLOCK] = { "unexpected `{` after a method call without parenthesis", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARRAY_ELEMENT] = { "expected an element for the array", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARRAY_EXPRESSION] = { "expected an expression for the array element", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARRAY_EXPRESSION_AFTER_STAR] = { "expected an expression after `*` in the array", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARRAY_SEPARATOR] = { "expected a `,` separator for the array elements", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARRAY_TERM] = { "expected a `]` to close the array", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BEGIN_LONELY_ELSE] = { "unexpected `else` in `begin` block; else without rescue is useless", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BEGIN_TERM] = { "expected an `end` to close the `begin` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BEGIN_UPCASE_BRACE] = { "expected a `{` after `BEGIN`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BEGIN_UPCASE_TERM] = { "expected a `}` to close the `BEGIN` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BEGIN_UPCASE_TOPLEVEL] = { "BEGIN is permitted only at toplevel", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE] = { "expected a local variable name in the block parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BLOCK_PARAM_PIPE_TERM] = { "expected the block parameters to end with `|`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BLOCK_TERM_BRACE] = { "expected a block beginning with `{` to end with `}`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_BLOCK_TERM_END] = { "expected a block beginning with `do` to end with `end`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CANNOT_PARSE_STRING_PART] = { "cannot parse the string part", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CASE_EXPRESSION_AFTER_CASE] = { "expected an expression after `case`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CASE_EXPRESSION_AFTER_WHEN] = { "expected an expression after `when`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CASE_MATCH_MISSING_PREDICATE] = { "expected a predicate for a case matching statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CASE_MISSING_CONDITIONS] = { "expected a `when` or `in` clause after `case`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CASE_TERM] = { "expected an `end` to close the `case` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_IN_METHOD] = { "unexpected class definition in method body", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_NAME] = { "unexpected constant path after `class`; class/module name must be CONSTANT", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_SUPERCLASS] = { "expected a superclass after `<`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_TERM] = { "expected an `end` to close the `class` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_UNEXPECTED_END] = { "unexpected `end`, expecting ';' or '\\n'", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CLASS_VARIABLE_BARE] = { "'@@' without identifiers is not allowed as a class variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_ELSIF_PREDICATE] = { "expected a predicate expression for the `elsif` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_IF_PREDICATE] = { "expected a predicate expression for the `if` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_PREDICATE_TERM] = { "expected `then` or `;` or '\\n'", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_TERM] = { "expected an `end` to close the conditional clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_TERM_ELSE] = { "expected an `end` to close the `else` clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_UNLESS_PREDICATE] = { "expected a predicate expression for the `unless` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_UNTIL_PREDICATE] = { "expected a predicate expression for the `until` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONDITIONAL_WHILE_PREDICATE] = { "expected a predicate expression for the `while` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_NAME] = { "expected a method name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_NAME_AFTER_RECEIVER] = { "expected a method name after the receiver", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_PARAMS_TERM_PAREN] = { "expected a `)` to close the parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_RECEIVER] = { "expected a receiver for the method definition", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_RECEIVER_TERM] = { "expected a `.` or `::` after the receiver in a method definition", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_TERM] = { "expected an `end` to close the `def` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEFINED_EXPRESSION] = { "expected an expression after `defined?`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EMBDOC_TERM] = { "could not find a terminator for the embedded document", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EMBEXPR_END] = { "expected a `}` to close the embedded expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EMBVAR_INVALID] = { "invalid embedded variable", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_END_UPCASE_BRACE] = { "expected a `{` after `END`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_END_UPCASE_TERM] = { "expected a `}` to close the `END` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_CONTROL] = { "invalid control escape sequence", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT] = { "invalid control escape sequence; control cannot be repeated", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_HEXADECIMAL] = { "invalid hexadecimal escape sequence", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_META] = { "invalid meta escape sequence", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_META_REPEAT] = { "invalid meta escape sequence; meta cannot be repeated", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_UNICODE] = { "invalid Unicode escape sequence", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS] = { "invalid Unicode escape sequence; Unicode cannot be combined with control or meta flags", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL] = { "invalid Unicode escape sequence; multiple codepoints are not allowed in a character literal", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_UNICODE_LONG] = { "invalid Unicode escape sequence; maximum length is 6 digits", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ESCAPE_INVALID_UNICODE_TERM] = { "invalid Unicode escape sequence; needs closing `}`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_ARGUMENT] = { "expected an argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EOL_AFTER_STATEMENT] = { "unexpected %s, expecting end-of-input", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ] = { "expected an expression after `&&=`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ] = { "expected an expression after `||=`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA] = { "expected an expression after `,`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL] = { "expected an expression after `=`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS] = { "expected an expression after `<<`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN] = { "expected an expression after `(`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR] = { "expected an expression after the operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT] = { "expected an expression after `*` splat in an argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH] = { "expected an expression after `**` in a hash", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_EXPRESSION_AFTER_STAR] = { "expected an expression after `*`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_RPAREN] = { "expected a matching `)`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_RPAREN_AFTER_MULTI] = { "expected a `)` after multiple assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_RPAREN_REQ_PARAMETER] = { "expected a `)` to end a required parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_STRING_CONTENT] = { "expected string content after opening string delimiter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_WHEN_DELIMITER] = { "expected a delimiter after the predicates of a `when` clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPRESSION_BARE_HASH] = { "unexpected bare hash in expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_FLOAT_PARSE] = { "could not parse the float '%.*s'", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_FOR_COLLECTION] = { "expected a collection after the `in` in a `for` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_FOR_INDEX] = { "expected an index after `for`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_FOR_IN] = { "expected an `in` after the index in a `for` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_FOR_TERM] = { "expected an `end` to close the `for` loop", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_GLOBAL_VARIABLE_BARE] = { "'$' without identifiers is not allowed as a global variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HASH_EXPRESSION_AFTER_LABEL] = { "expected an expression after the label in a hash", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HASH_KEY] = { "unexpected %s, expecting '}' or a key in the hash literal", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HASH_ROCKET] = { "expected a `=>` between the hash key and value", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HASH_TERM] = { "expected a `}` to close the hash literal", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HASH_VALUE] = { "expected a value in the hash literal", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_HEREDOC_TERM] = { "could not find a terminator for the heredoc", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INCOMPLETE_QUESTION_MARK] = { "incomplete expression at `?`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INCOMPLETE_VARIABLE_CLASS_3_3_0] = { "`%.*s' is not allowed as a class variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INCOMPLETE_VARIABLE_CLASS] = { "'%.*s' is not allowed as a class variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE_3_3_0] = { "`%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE] = { "'%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INSTANCE_VARIABLE_BARE] = { "'@' without identifiers is not allowed as an instance variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_FLOAT_EXPONENT] = { "invalid exponent", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_NUMBER_BINARY] = { "invalid binary number", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_NUMBER_DECIMAL] = { "invalid decimal number", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_NUMBER_HEXADECIMAL] = { "invalid hexadecimal number", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_NUMBER_OCTAL] = { "invalid octal number", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_NUMBER_UNDERSCORE] = { "invalid underscore placement in number", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_CHARACTER] = { "invalid character 0x%X", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_MULTIBYTE_CHAR] = { "invalid multibyte char (%s)", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_MULTIBYTE_CHARACTER] = { "invalid multibyte character 0x%X", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_MULTIBYTE_ESCAPE] = { "invalid multibyte escape: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_PRINTABLE_CHARACTER] = { "invalid character `%c`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_INVALID_PERCENT] = { "invalid `%` token", PM_ERROR_LEVEL_SYNTAX }, // TODO WHAT? + [PM_ERR_INVALID_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_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] = { "parameter default value references itself", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_METHOD_NAME] = { "unexpected name for a parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_NAME_DUPLICATED] = { "duplicated argument name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_NO_DEFAULT] = { "expected a default value for the parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_NO_DEFAULT_KW] = { "expected a default value for the keyword parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_NUMBERED_RESERVED] = { "%.2s is reserved for numbered parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_ORDER] = { "unexpected parameter order", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_SPLAT_MULTI] = { "unexpected multiple `*` splat parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_STAR] = { "unexpected parameter `*`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_UNEXPECTED_FWD] = { "unexpected `...` in parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PARAMETER_WILD_LOOSE_COMMA] = { "unexpected `,` in parameters", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_CAPTURE_DUPLICATE] = { "duplicated variable name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET] = { "expected a pattern expression after the `[` operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA] = { "expected a pattern expression after `,`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET] = { "expected a pattern expression after `=>`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_IN] = { "expected a pattern expression after the `in` keyword", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_KEY] = { "expected a pattern expression after the key", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN] = { "expected a pattern expression after the `(` operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_PIN] = { "expected a pattern expression after the `^` pin operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE] = { "expected a pattern expression after the `|` operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE] = { "expected a pattern expression after the range operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_EXPRESSION_AFTER_REST] = { "unexpected pattern expression after the `**` expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_HASH_KEY] = { "expected a key in the hash pattern", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_HASH_KEY_DUPLICATE] = { "duplicated key name", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_HASH_KEY_LABEL] = { "expected a label as the key in the hash pattern", PM_ERROR_LEVEL_SYNTAX }, // TODO // THIS // AND // ABOVE // IS WEIRD + [PM_ERR_PATTERN_IDENT_AFTER_HROCKET] = { "expected an identifier after the `=>` operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_LABEL_AFTER_COMMA] = { "expected a label after the `,` in the hash pattern", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_REST] = { "unexpected rest pattern", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_TERM_BRACE] = { "expected a `}` to close the pattern expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_TERM_BRACKET] = { "expected a `]` to close the pattern expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PATTERN_TERM_PAREN] = { "expected a `)` to close the pattern expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN] = { "unexpected `||=` in a multiple assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_ENCODING_OPTION_MISMATCH] = { "regexp encoding option '%c' differs from source encoding '%s'", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_INCOMPAT_CHAR_ENCODING] = { "incompatible character encoding: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_NON_ESCAPED_MBC] = { "/.../n has a non escaped non ASCII character in non ASCII-8BIT script: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_INVALID_UNICODE_RANGE] = { "invalid Unicode range: /%.*s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_UNKNOWN_OPTIONS] = { "unknown regexp %s: %.*s", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_TERM] = { "expected a closing delimiter for the regular expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_REGEXP_UTF8_CHAR_NON_UTF8_REGEXP] = { "UTF-8 character in non UTF-8 regexp: /%s/", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_RESCUE_EXPRESSION] = { "expected a rescued expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_RESCUE_MODIFIER_VALUE] = { "expected a value after the `rescue` modifier", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_RESCUE_TERM] = { "expected a closing delimiter for the `rescue` clause", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_RESCUE_VARIABLE] = { "expected an exception variable after `=>` in a rescue statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_RETURN_INVALID] = { "invalid `return` in a class or module body", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_SINGLETON_FOR_LITERALS] = { "cannot define singleton method for literals", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STATEMENT_ALIAS] = { "unexpected an `alias` at a non-statement position", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STATEMENT_POSTEXE_END] = { "unexpected an `END` at a non-statement position", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STATEMENT_PREEXE_BEGIN] = { "unexpected a `BEGIN` at a non-statement position", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STATEMENT_UNDEF] = { "unexpected an `undef` at a non-statement position", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STRING_CONCATENATION] = { "expected a string for concatenation", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STRING_INTERPOLATED_TERM] = { "expected a closing delimiter for the interpolated string", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STRING_LITERAL_EOF] = { "unterminated string meets end of file", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_STRING_LITERAL_TERM] = { "unexpected %s, expected a string literal terminator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_SYMBOL_INVALID] = { "invalid symbol", PM_ERROR_LEVEL_SYNTAX }, // TODO expected symbol? prism.c ~9719 + [PM_ERR_SYMBOL_TERM_DYNAMIC] = { "expected a closing delimiter for the dynamic symbol", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_SYMBOL_TERM_INTERPOLATED] = { "expected a closing delimiter for the interpolated symbol", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_TERNARY_COLON] = { "expected a `:` after the true expression of a ternary operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_TERNARY_EXPRESSION_FALSE] = { "expected an expression after `:` in the ternary operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_TERNARY_EXPRESSION_TRUE] = { "expected an expression after `?` in the ternary operator", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNDEF_ARGUMENT] = { "invalid argument being passed to `undef`; expected a bare word, constant, or symbol argument", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNARY_RECEIVER] = { "unexpected %s, expected a receiver for unary `%c`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT] = { "unexpected %s, assuming it is closing the parent %s", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNEXPECTED_TOKEN_IGNORE] = { "unexpected %s, ignoring it", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNTIL_TERM] = { "expected an `end` to close the `until` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_VOID_EXPRESSION] = { "unexpected void value expression", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_WHILE_TERM] = { "expected an `end` to close the `while` statement", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_WRITE_TARGET_IN_METHOD] = { "dynamic constant assignment", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_WRITE_TARGET_READONLY] = { "Can't set variable %.*s", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_WRITE_TARGET_UNEXPECTED] = { "unexpected write target", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_XSTRING_TERM] = { "expected a closing delimiter for the `%x` or backtick string", PM_ERROR_LEVEL_SYNTAX }, // Warnings [PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS] = { "ambiguous first argument; put parentheses or a space even after `-` operator", PM_WARNING_LEVEL_VERBOSE }, @@ -334,6 +345,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [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 } }; @@ -377,7 +389,7 @@ pm_diagnostic_level(pm_diagnostic_id_t diag_id) { */ bool pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id) { - pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(sizeof(pm_diagnostic_t), 1); + pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(1, sizeof(pm_diagnostic_t)); if (diagnostic == NULL) return false; *diagnostic = (pm_diagnostic_t) { @@ -409,7 +421,7 @@ pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const ui return false; } - pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(sizeof(pm_diagnostic_t), 1); + pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(1, sizeof(pm_diagnostic_t)); if (diagnostic == NULL) { return false; } diff --git a/prism/templates/src/serialize.c.erb b/prism/templates/src/serialize.c.erb index 94b976645dd1d1..97101e36d52d91 100644 --- a/prism/templates/src/serialize.c.erb +++ b/prism/templates/src/serialize.c.erb @@ -220,7 +220,7 @@ pm_serialize_data_loc(const pm_parser_t *parser, pm_buffer_t *buffer) { static void pm_serialize_diagnostic(pm_parser_t *parser, pm_diagnostic_t *diagnostic, pm_buffer_t *buffer) { // serialize the type - pm_buffer_append_byte(buffer, (uint8_t) diagnostic->diag_id); + pm_buffer_append_varuint(buffer, (uint32_t) diagnostic->diag_id); // serialize message size_t message_length = strlen(diagnostic->message); 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_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_compile.c b/prism_compile.c index e919a44c0885ae..5aeef191f780a7 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -129,7 +129,8 @@ parse_integer(const pm_integer_node_t *node) if (integer->values == NULL) { result = UINT2NUM(integer->value); - } else { + } + else { VALUE string = rb_str_new(NULL, integer->length * 8); unsigned char *bytes = (unsigned char *) RSTRING_PTR(string); @@ -251,9 +252,11 @@ 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; } @@ -286,99 +289,275 @@ pm_optimizable_range_item_p(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(const 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); + } + + ADD_INSN1(ret, &dummy_line_node, putobject, rb_fstring(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); + ADD_INSN1(ret, &dummy_line_node, putobject, current_string); + current_string = Qnil; + number_of_items_pushed++; + } + } + else { + PM_PUTNIL; + } + + return number_of_items_pushed; +} + +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) +{ + NODE dummy_line_node = generate_dummy_line_node(node_location->line, node_location->column); + int length = pm_interpolated_node_compile(parts, iseq, dummy_line_node, 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); - return regex; + if (length > 0) { + rb_encoding *filepath_encoding = scope_node->filepath_encoding != NULL ? scope_node->filepath_encoding : rb_utf8_encoding(); + return rb_fstring(rb_enc_str_new((const char *) pm_string_source(filepath), length, filepath_encoding)); + } + else { + return rb_fstring_lit(""); + } +} + +/** + * Return a static literal string, optionally with attached debugging + * information. + */ +static VALUE +pm_static_literal_string(rb_iseq_t *iseq, VALUE string, int line_number) +{ + if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { + VALUE debug_info = rb_ary_new_from_args(2, rb_iseq_path(iseq), INT2FIX(line_number)); + rb_ivar_set(string, id_debug_created_info, rb_obj_freeze(debug_info)); + return rb_str_freeze(string); + } + else { + return rb_fstring(string); + } } /** @@ -386,12 +565,12 @@ 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: { @@ -400,7 +579,7 @@ pm_static_literal_value(const pm_node_t *node, const pm_scope_node_t *scope_node 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); @@ -418,7 +597,7 @@ pm_static_literal_value(const pm_node_t *node, const pm_scope_node_t *scope_node 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) }; + 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); } @@ -433,22 +612,50 @@ pm_static_literal_value(const pm_node_t *node, const pm_scope_node_t *scope_node return parse_imaginary((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 rb_fstring(parse_string_encoded(scope_node, node, &((pm_string_node_t *)node)->unescaped)); + case PM_STRING_NODE: { + VALUE string = parse_string_encoded(scope_node, node, &((const pm_string_node_t *) node)->unescaped); + int line_number = pm_node_line_number(scope_node->parser, node); + return pm_static_literal_string(iseq, string, line_number); + } case PM_SYMBOL_NODE: return ID2SYM(parse_string_symbol(scope_node, (const pm_symbol_node_t *) node)); case PM_TRUE_NODE: @@ -538,8 +745,6 @@ pm_compile_logical(rb_iseq_t *iseq, LINK_ANCHOR *const ret, pm_node_t *cond, LAB 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) { @@ -757,7 +962,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); } @@ -778,113 +984,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; - - bool literal = true; - 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)) { - literal = false; - break; - } - } - - if (literal) { - for (size_t index = 0; index < parts_size; index++) { - const pm_node_t *part = parts->nodes[index]; - 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; - } - } - - const pm_node_t *part = parts->nodes[0]; - current_string = rb_fstring(current_string); - if (PM_NODE_FLAG_P(part, PM_STRING_FLAGS_FROZEN)) { - ADD_INSN1(ret, &dummy_line_node, putobject, current_string); - } - else if (PM_NODE_FLAG_P(part, PM_STRING_FLAGS_MUTABLE)) { - ADD_INSN1(ret, &dummy_line_node, putstring, current_string); - } - else { - ADD_INSN1(ret, &dummy_line_node, putchilledstring, current_string); - } - return 1; - } - - 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); - } - - ADD_INSN1(ret, &dummy_line_node, putobject, rb_fstring(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); - ADD_INSN1(ret, &dummy_line_node, putobject, 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. @@ -904,7 +1003,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); @@ -1173,7 +1273,7 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b // Retrieve the stored index from the hash for this // keyword. - VALUE keyword = pm_static_literal_value(assoc->key, scope_node); + 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, @@ -1205,7 +1305,7 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b bool popped = true; if (rb_ary_entry(keyword_indices, (long) element_index) == Qtrue) { - keywords[keyword_index++] = pm_static_literal_value(assoc->key, scope_node); + keywords[keyword_index++] = pm_static_literal_value(iseq, assoc->key, scope_node); popped = false; } @@ -1213,7 +1313,8 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b } RUBY_ASSERT(keyword_index == size); - } else { + } + else { // If they aren't all symbol keys then we need to // construct a new hash and pass that as an argument. orig_argc++; @@ -1819,7 +1920,8 @@ pm_compile_pattern_deconstruct(rb_iseq_t *iseq, pm_scope_node_t *scope_node, con 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 { + } + else { ADD_INSNL(ret, &line.node, jump, deconstruct_label); } @@ -2006,7 +2108,8 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t ADD_INSN1(ret, &line.node, setn, INT2FIX(4)); ADD_SEND(ret, &line.node, 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) { + } + 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)); @@ -2237,7 +2340,8 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t if (NIL_P(keys)) { ADD_INSN(ret, &line.node, putnil); - } else { + } + else { ADD_INSN1(ret, &line.node, duparray, keys); RB_OBJ_WRITTEN(iseq, Qundef, rb_obj_hide(keys)); } @@ -2301,7 +2405,8 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t } ADD_SEQ(ret, match_values); - } else { + } + else { ADD_INSN(ret, &line.node, dup); ADD_SEND(ret, &line.node, idEmptyP, INT2FIX(0)); if (in_single_pattern) { @@ -2516,7 +2621,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; @@ -2533,7 +2639,8 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t ADD_INSN(ret, &line.node, dup); if (PM_NODE_TYPE_P(node, PM_IF_NODE)) { ADD_INSNL(ret, &line.node, branchif, match_succeeded_label); - } else { + } + else { ADD_INSNL(ret, &line.node, branchunless, match_succeeded_label); } @@ -2550,7 +2657,8 @@ pm_compile_pattern(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_node_t if (PM_NODE_TYPE_P(node, PM_IF_NODE)) { ADD_INSNL(ret, &line.node, branchunless, unmatched_label); - } else { + } + else { ADD_INSNL(ret, &line.node, branchif, unmatched_label); } @@ -2592,6 +2700,7 @@ pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_ if (previous) { scope->parser = previous->parser; scope->encoding = previous->encoding; + scope->filepath_encoding = previous->filepath_encoding; scope->constants = previous->constants; } @@ -3254,7 +3363,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); } @@ -3276,7 +3386,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); } @@ -3324,7 +3435,8 @@ pm_compile_destructured_param_writes(rb_iseq_t *iseq, const pm_multi_target_node 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); } @@ -3351,7 +3463,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); } @@ -3420,7 +3533,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; } @@ -3585,13 +3699,15 @@ 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 { + } + else { ADD_INSN1(parents, &dummy_line_node, putobject, rb_cObject); } if (state == NULL) { ADD_INSN(writes, &dummy_line_node, swap); - } else { + } + else { ADD_INSN1(writes, &dummy_line_node, topn, INT2FIX(1)); pm_multi_target_state_push(state, (INSN *) LAST_ELEMENT(writes), 1); } @@ -3660,7 +3776,8 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons if (argc == 0) { ADD_INSN(writes, &dummy_line_node, swap); - } else { + } + else { for (int index = 0; index < argc; index++) { ADD_INSN1(writes, &dummy_line_node, topn, INT2FIX(argc + 1)); } @@ -4188,13 +4305,13 @@ 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(node, scope_node); + key = pm_static_literal_value(iseq, node, scope_node); double intptr; if (modf(RFLOAT_VALUE(key), &intptr) == 0.0) { @@ -4210,11 +4327,14 @@ pm_compile_case_node_dispatch(VALUE dispatch, const pm_node_t *node, LABEL *labe 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)); + VALUE string = parse_string_encoded(scope_node, node, &cast->unescaped); + + int line_number = pm_node_line_number(scope_node->parser, node); + key = pm_static_literal_string(iseq, string, line_number); break; } default: @@ -4335,13 +4455,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 { @@ -4529,7 +4649,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, ADD_ADJUST_RESTORE(ret, splabel); PM_PUTNIL_UNLESS_POPPED; - } else { + } + else { const rb_iseq_t *ip = iseq; while (ip) { @@ -4823,7 +4944,7 @@ 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)) { @@ -4985,7 +5106,8 @@ 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) { + } + else if (!popped) { ADD_INSN(body_seq, &in_line.node, putnil); } @@ -5013,7 +5135,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (else_node->statements != NULL) { PM_COMPILE_INTO_ANCHOR(cond_seq, (const pm_node_t *) else_node->statements); - } else if (!popped) { + } + else if (!popped) { ADD_INSN(cond_seq, &dummy_line_node, putnil); } @@ -5022,7 +5145,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (popped) { ADD_INSN(cond_seq, &dummy_line_node, putnil); } - } else { + } + 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); @@ -5032,7 +5156,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (in_single_pattern) { pm_compile_pattern_error_handler(iseq, scope_node, node, cond_seq, end_label, popped); - } else { + } + 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)); @@ -5837,16 +5962,17 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, 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); + VALUE value = pm_static_literal_value(iseq, node, scope_node); ADD_INSN1(ret, &dummy_line_node, 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 @@ -6018,71 +6144,110 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, 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(&cast->parts, iseq, dummy_line_node, 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; + int length; - 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; + if ((length = pm_interpolated_node_compile(&cast->parts, iseq, dummy_line_node, ret, popped, scope_node)) > 1) { + PUSH_INSN1(ret, location, concatstrings, INT2FIX(length)); + } + + if (!popped) { + PUSH_INSN(ret, location, intern); + } + else { + PUSH_INSN(ret, location, pop); + } } return; @@ -6228,17 +6393,14 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, return; } case PM_MATCH_LAST_LINE_NODE: { - if (!popped) { - pm_match_last_line_node_t *cast = (pm_match_last_line_node_t *) 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); + // if /foo/ then end + // ^^^^^ + VALUE regexp = pm_static_literal_value(iseq, node, scope_node); - 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; } @@ -6634,7 +6796,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); } @@ -6645,27 +6808,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. + ADD_SEQ(outer_pre, inner_pre); + ADD_SEQ(outer_pre, inner_body); + scope_node->pre_execution_anchor = outer_pre; return; } @@ -6714,13 +6888,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); } @@ -6802,8 +6978,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; } @@ -6837,7 +7013,8 @@ 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 { + } + else { ADD_GETLOCAL(ret, &dummy_line_node, LVAR_ERRINFO, 0); PUSH_INSN1(ret, location, putobject, rb_eStandardError); PUSH_INSN1(ret, location, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE)); @@ -6885,7 +7062,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // Now restore the end_label ISEQ_COMPILE_DATA(iseq)->end_label = prev_end; - } else { + } + else { PUSH_INSN(ret, location, putnil); } @@ -6898,7 +7076,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_LABEL(ret, rescue_end_label); if (cast->consequent) { PM_COMPILE((pm_node_t *) cast->consequent); - } else { + } + else { ADD_GETLOCAL(ret, &dummy_line_node, 1, 0); } @@ -7005,7 +7184,8 @@ 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; } @@ -7403,12 +7583,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, 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); @@ -7595,7 +7771,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; } @@ -7716,10 +7893,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, 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)) { + 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(nd_line(&dummy_line_node)); pm_local_index_t index = pm_lookup_local_index(iseq, scope_node, name, 0); @@ -7812,18 +7986,16 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, 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 { + } + else { PM_PUTNIL; } @@ -7851,7 +8023,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, ADD_TRACE(ret, RUBY_EVENT_CALL); if (scope_node->body) { PM_COMPILE((pm_node_t *)scope_node->body); - } else { + } + else { PM_PUTNIL; } @@ -7886,7 +8059,8 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, default: if (scope_node->body) { PM_COMPILE((pm_node_t *)scope_node->body); - } else { + } + else { PM_PUTNIL; } break; @@ -7936,7 +8110,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; @@ -7946,7 +8120,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // ^^^^^^^^ if (!popped) { const pm_source_file_node_t *cast = (const pm_source_file_node_t *) node; - VALUE string = rb_fstring(parse_string(scope_node, &cast->filepath)); + 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); @@ -7964,7 +8138,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; @@ -8003,7 +8177,8 @@ 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_string_encoded(scope_node, node, &cast->unescaped); + value = pm_static_literal_string(iseq, value, location.line); if (PM_NODE_FLAG_P(node, PM_STRING_FLAGS_FROZEN)) { PUSH_INSN1(ret, location, putobject, value); @@ -8055,7 +8230,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // :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; @@ -8178,6 +8353,20 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } } +/** 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; + } +} + /** * This is the main entry-point into the prism compiler. It accepts the iseq * that it should be compiling instruction into and a pointer to the scope node @@ -8191,7 +8380,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. + ADD_SEQ(ret, pre); + ADD_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); @@ -8245,43 +8459,91 @@ pm_parse_process_error_utf8_p(const pm_parser_t *parser, const pm_location_t *lo static VALUE 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_process_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) { 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; @@ -8300,7 +8562,10 @@ pm_parse_process(pm_parse_result_t *result, pm_node_t *node) // First, set up the scope node so that the AST node is attached and can be // freed regardless of whether or we return an error. pm_scope_node_t *scope_node = &result->node; + rb_encoding *filepath_encoding = scope_node->filepath_encoding; + pm_scope_node_init(node, scope_node, NULL); + scope_node->filepath_encoding = filepath_encoding; // If there are errors, raise an appropriate error and free the result. if (parser->error_list.size > 0) { @@ -8351,6 +8616,30 @@ pm_parse_process(pm_parse_result_t *result, pm_node_t *node) 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. @@ -8386,24 +8675,6 @@ pm_parse_file_script_lines(const pm_scope_node_t *scope_node, const pm_parser_t return lines; } -void -pm_options_frozen_string_literal_init(pm_parse_result_t *result, int 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(&result->options, false); - break; - case ISEQ_FROZEN_STRING_LITERAL_ENABLED: - pm_options_frozen_string_literal_set(&result->options, true); - break; - default: - rb_bug("pm_options_frozen_string_literal_init: invalid frozen_string_literal=%d", frozen_string_literal); - break; - } -} - /** * Attempt to load the file into memory. Return a Ruby error if the file cannot * be read. @@ -8423,6 +8694,7 @@ pm_load_file(pm_parse_result_t *result, VALUE filepath) return err; } + pm_options_frozen_string_literal_init(&result->options); return Qnil; } @@ -8483,11 +8755,16 @@ pm_load_parse_file(pm_parse_result_t *result, VALUE filepath) 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)); + result->node.filepath_encoding = rb_enc_get(filepath); pm_options_filepath_set(&result->options, RSTRING_PTR(filepath)); RB_GC_GUARD(filepath); @@ -8527,6 +8804,8 @@ pm_parse_stdin_fgets(char *string, int size, void *stream) 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); diff --git a/prism_compile.h b/prism_compile.h index 0c1510d67f9814..e58bed271f2b66 100644 --- a/prism_compile.h +++ b/prism_compile.h @@ -13,6 +13,9 @@ typedef struct pm_local_index_struct { int index, level; } pm_local_index_t; +// A declaration for the struct that lives in compile.c. +struct iseq_link_anchor; + // ScopeNodes are helper nodes, and will never be part of the AST. We manually // declare them here to avoid generating them. typedef struct pm_scope_node { @@ -26,6 +29,13 @@ typedef struct pm_scope_node { const pm_parser_t *parser; rb_encoding *encoding; + /** + * This is the encoding of the actual filepath object that will be used when + * a __FILE__ node is compiled or when the path has to be set on a syntax + * error. + */ + rb_encoding *filepath_encoding; + // The size of the local table // on the iseq which includes // locals and hidden variables @@ -33,6 +43,12 @@ typedef struct pm_scope_node { ID *constants; st_table *index_lookup_table; + + /** + * This will only be set on the top-level scope node. It will contain all of + * the instructions pertaining to BEGIN{} nodes. + */ + struct iseq_link_anchor *pre_execution_anchor; } pm_scope_node_t; void pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_t *previous); @@ -40,14 +56,22 @@ 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; -void pm_options_frozen_string_literal_init(pm_parse_result_t *result, int frozen_string_literal); VALUE pm_load_file(pm_parse_result_t *result, VALUE filepath); VALUE pm_parse_file(pm_parse_result_t *result, VALUE filepath); VALUE pm_load_parse_file(pm_parse_result_t *result, VALUE filepath); diff --git a/process.c b/process.c index e5415dd170a362..72483815d68f1b 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(); diff --git a/ruby.c b/ruby.c index 5c1d44ec6df56c..59cbbd0360b690 100644 --- a/ruby.c +++ b/ruby.c @@ -2116,8 +2116,6 @@ prism_script(ruby_cmdline_options_t *opt, pm_parse_result_t *result) pm_options_t *options = &result->options; pm_options_line_set(options, 1); - pm_options_frozen_string_literal_init(result, rb_iseq_opt_frozen_string_literal()); - if (opt->ext.enc.name != 0) { pm_options_encoding_set(options, StringValueCStr(opt->ext.enc.name)); } diff --git a/ruby_parser.c b/ruby_parser.c index 6d85a72c5b0c18..90ee4504a241e6 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -93,32 +93,6 @@ dvar_defined(ID id, const void *p) return rb_dvar_defined(id, (const rb_iseq_t *)p); } -static bool -hash_literal_key_p(VALUE k) -{ - switch (OBJ_BUILTIN_TYPE(k)) { - case T_NODE: - return false; - default: - return true; - } -} - -static int -literal_cmp(VALUE val, VALUE lit) -{ - if (val == lit) return 0; - if (!hash_literal_key_p(val) || !hash_literal_key_p(lit)) return -1; - return rb_iseq_cdhash_cmp(val, lit); -} - -static st_index_t -literal_hash(VALUE a) -{ - if (!hash_literal_key_p(a)) return (st_index_t)a; - return rb_iseq_cdhash_hash(a); -} - static int is_usascii_enc(void *enc) { @@ -263,18 +237,6 @@ enc_from_encoding(void *enc) return rb_enc_from_encoding((rb_encoding *)enc); } -static int -encoding_get(VALUE obj) -{ - return ENCODING_GET(obj); -} - -static void -encoding_set(VALUE obj, int encindex) -{ - ENCODING_SET(obj, encindex); -} - static int encoding_is_ascii8bit(VALUE obj) { @@ -603,8 +565,6 @@ static const rb_parser_config_t rb_global_parser_config = { .ascii8bit_encoding = ascii8bit_encoding, .enc_codelen = enc_codelen, .enc_mbcput = enc_mbcput, - .char_to_option_kcode = rb_char_to_option_kcode, - .ascii8bit_encindex = rb_ascii8bit_encindex, .enc_find_index = rb_enc_find_index, .enc_from_index = enc_from_index, .enc_associate_index = rb_enc_associate_index, @@ -613,8 +573,6 @@ static const rb_parser_config_t rb_global_parser_config = { .enc_coderange_unknown = ENC_CODERANGE_UNKNOWN, .enc_compatible = enc_compatible, .enc_from_encoding = enc_from_encoding, - .encoding_get = encoding_get, - .encoding_set = encoding_set, .encoding_is_ascii8bit = encoding_is_ascii8bit, .usascii_encoding = usascii_encoding, .enc_coderange_broken = ENC_CODERANGE_BROKEN, @@ -627,9 +585,6 @@ static const rb_parser_config_t rb_global_parser_config = { .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, diff --git a/rubyparser.h b/rubyparser.h index 9a809aa059f220..28e675c39fa8c0 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -200,6 +200,8 @@ 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; @@ -673,7 +675,7 @@ typedef struct RNode_LIT { typedef struct RNode_INTEGER { NODE node; - char* val; + char *val; int minus; int base; } rb_node_integer_t; @@ -681,14 +683,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; @@ -703,7 +705,7 @@ enum rb_numeric_type { typedef struct RNode_IMAGINARY { NODE node; - char* val; + char *val; int minus; int base; int seen_point; @@ -1356,16 +1358,12 @@ typedef struct rb_parser_config_struct { rb_encoding *(*ascii8bit_encoding)(void); int (*enc_codelen)(int c, rb_encoding *enc); int (*enc_mbcput)(unsigned int c, void *buf, rb_encoding *enc); - int (*char_to_option_kcode)(int c, int *option, int *kcode); - int (*ascii8bit_encindex)(void); int (*enc_find_index)(const char *name); rb_encoding *(*enc_from_index)(int idx); VALUE (*enc_associate_index)(VALUE obj, int encindex); int (*enc_isspace)(OnigCodePoint c, rb_encoding *enc); rb_encoding *(*enc_compatible)(VALUE str1, VALUE str2); VALUE (*enc_from_encoding)(rb_encoding *enc); - int (*encoding_get)(VALUE obj); - void (*encoding_set)(VALUE obj, int encindex); int (*encoding_is_ascii8bit)(VALUE obj); rb_encoding *(*usascii_encoding)(void); int enc_coderange_broken; @@ -1382,10 +1380,6 @@ typedef struct rb_parser_config_struct { // int rb_dvar_defined(ID id, const rb_iseq_t *iseq); int (*dvar_defined)(ID, const void*); - /* Compile (parse.y) */ - int (*literal_cmp)(VALUE val, VALUE lit); - parser_st_index_t (*literal_hash)(VALUE a); - /* Error (Exception) */ RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 6, 0) VALUE (*syntax_error_append)(VALUE, VALUE, int, int, rb_encoding*, const char*, va_list); diff --git a/shape.c b/shape.c index 8241f67d6ab5d6..1158aad52c5f11 100644 --- a/shape.c +++ b/shape.c @@ -863,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; 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/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index 0f1aeef910650e..f6793d393ba828 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -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" diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index 2a09e0531ba7ea..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 diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index d4776bbed7d15a..61c513ed723d76 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 @@ -210,6 +212,16 @@ def exec(command, args) end end + 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 + context "Gemfile eval" do before do allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index 46931e9ca530ba..91e6e6e574b931 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.0" end def build_repo1 diff --git a/spec/prism.mspec b/spec/prism.mspec index 99af52c0f1601c..9f5e2b2a5fdaa5 100644 --- a/spec/prism.mspec +++ b/spec/prism.mspec @@ -1,40 +1,28 @@ # frozen_string_literal: true -## Command line -MSpec.register(:exclude, "The -S command line option runs launcher found in PATH, but only code after the first /#!.*ruby.*/-ish line in target file") -MSpec.register(:exclude, /^The -x command line option/) -MSpec.register(:exclude, "The --enable and --disable flags can be used with frozen-string-literal") -MSpec.register(:exclude, /^The --enable-frozen-string-literal flag/) -MSpec.register(:exclude, "The --debug flag produces debugging info on attempted frozen string modification") - ## Language -MSpec.register(:exclude, "The BEGIN keyword runs multiple begins in FIFO order") MSpec.register(:exclude, "Executing break from within a block works when passing through a super call") MSpec.register(:exclude, "The defined? keyword when called with a method name in a void context warns about the void context when parsing it") -MSpec.register(:exclude, /^Hash literal expands an '\*\*\{\}'/) +MSpec.register(:exclude, "Hash literal expands an '**{}' or '**obj' element with the last key/value pair taking precedence") +MSpec.register(:exclude, "Hash literal expands an '**{}' and warns when finding an additional duplicate key afterwards") MSpec.register(:exclude, "Hash literal merges multiple nested '**obj' in Hash literals") -MSpec.register(:exclude, /^Hash literal raises a SyntaxError at parse time when Symbol key with invalid bytes/) +MSpec.register(:exclude, "Hash literal raises a SyntaxError at parse time when Symbol key with invalid bytes") +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, "The next statement in a method is invalid and raises a SyntaxError") -MSpec.register(:exclude, "Pattern matching variable pattern does not support using variable name (except _) several times") -MSpec.register(:exclude, "Pattern matching Hash pattern raise SyntaxError when keys duplicate in pattern") -MSpec.register(:exclude, "Regexp with character classes supports [[:alpha:][:digit:][:etc:]] (predefined character classes)") -MSpec.register(:exclude, /^Regexps with encoding modifiers/) -MSpec.register(:exclude, "Regexps with grouping raises a SyntaxError when parentheses aren't balanced") -MSpec.register(:exclude, "Regexps with modifiers supports (?imx-imx) (inline modifiers)") -MSpec.register(:exclude, "Regexps with modifiers supports (?imx-imx:expr) (scoped inline modifiers)") -MSpec.register(:exclude, "Literal Regexps throws SyntaxError for malformed literals") -MSpec.register(:exclude, "The rescue keyword raises SyntaxError when else is used without rescue and ensure") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /e (EUC encoding) with interpolation") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /e (EUC encoding) with interpolation /o") +MSpec.register(:exclude, "Regexps with encoding modifiers preserves EUC-JP as /e encoding through interpolation") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /s (Windows_31J encoding) with interpolation") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /s (Windows_31J encoding) with interpolation and /o") +MSpec.register(:exclude, "Regexps with encoding modifiers preserves Windows-31J as /s encoding through interpolation") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /u (UTF8 encoding) with interpolation") +MSpec.register(:exclude, "Regexps with encoding modifiers supports /u (UTF8 encoding) with interpolation and /o") +MSpec.register(:exclude, "Regexps with encoding modifiers preserves UTF-8 as /u encoding through interpolation") MSpec.register(:exclude, "A Symbol literal raises an SyntaxError at parse time when Symbol with invalid bytes") ## Core MSpec.register(:exclude, "IO.popen with a leading Array argument accepts a trailing Hash of Process.exec options") MSpec.register(:exclude, "IO.popen with a leading Array argument accepts an IO mode argument following the Array") -MSpec.register(:exclude, "Kernel#eval includes file and line information in syntax error") -MSpec.register(:exclude, "Kernel#eval evaluates string with given filename and negative linenumber") -MSpec.register(:exclude, "Kernel#eval with a magic encoding comment allows spaces before the magic encoding comment") -MSpec.register(:exclude, "Kernel#eval with a magic encoding comment allows a shebang line and some spaces before the magic encoding comment") -MSpec.register(:exclude, "Kernel#eval with a magic encoding comment ignores the frozen_string_literal magic comment if it appears after a token and warns if $VERBOSE is true") -MSpec.register(:exclude, "Regexp#source has US-ASCII encoding when created from an ASCII-only \\u{} literal") MSpec.register(:exclude, "TracePoint#eval_script is the evald source code") MSpec.register(:exclude, "TracePoint#event returns the type of event") MSpec.register(:exclude, "TracePoint#inspect returns a String showing the event, method, path and line for a :return event") @@ -58,9 +46,4 @@ MSpec.register(:exclude, "Coverage.result does not clear counters when stop: fal 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, "Digest::SHA256.file when passed a path to a file that exists can be used with frozen-string-literal") -MSpec.register(:exclude, "ERB#filename raises an exception if there are errors processing content") -MSpec.register(:exclude, "ERB#filename uses '(erb)' as filename when filename is not set") -MSpec.register(:exclude, "mkmf can be required with --enable-frozen-string-literal") -MSpec.register(:exclude, "RbConfig::CONFIG contains no frozen strings even with --enable-frozen-string-literal") 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/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/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/string.c b/string.c index cbd8f905858410..ecd6b97f883ff1 100644 --- a/string.c +++ b/string.c @@ -1148,6 +1148,7 @@ str_cat_conv_enc_opts(VALUE newstr, long ofs, const char *ptr, long len, rb_str_resize(newstr, olen); } DATA_PTR(econv_wrapper) = 0; + RB_GC_GUARD(econv_wrapper); rb_econv_close(ec); switch (ret) { case econv_finished: @@ -1340,7 +1341,7 @@ rb_str_new_shared(VALUE str) VALUE rb_str_new_frozen(VALUE orig) { - if (OBJ_FROZEN(orig)) return orig; + if (RB_FL_TEST_RAW(orig, FL_FREEZE | STR_CHILLED) == FL_FREEZE) return orig; return str_new_frozen(rb_obj_class(orig), orig); } @@ -1833,6 +1834,12 @@ rb_ec_str_resurrect(struct rb_execution_context_struct *ec, VALUE str, bool chil return new_str; } +bool +rb_str_chilled_p(VALUE str) +{ + return CHILLED_STRING_P(str); +} + /* * * call-seq: diff --git a/template/Doxyfile.tmpl b/template/Doxyfile.tmpl index c04f14977de323..cea28d04c7d278 100644 --- a/template/Doxyfile.tmpl +++ b/template/Doxyfile.tmpl @@ -2359,10 +2359,6 @@ PREDEFINED += DEPRECATED(_)=_ PREDEFINED += DEPRECATED_BY(__,_)=_ PREDEFINED += ENUM_OVER_INT=1 PREDEFINED += ERRORFUNC(__,_)=_ -PREDEFINED += RJIT_FUNC_EXPORTED= -PREDEFINED += RJIT_STATIC=extern -PREDEFINED += RJIT_SYMBOL_EXPORT_BEGIN= -PREDEFINED += RJIT_SYMBOL_EXPORT_END= PREDEFINED += NOINLINE(_)=_ PREDEFINED += NORETURN(_)=_ PREDEFINED += PACKED_STRUCT_UNALIGNED(_)=_ diff --git a/test/-ext-/string/test_chilled.rb b/test/-ext-/string/test_chilled.rb new file mode 100644 index 00000000000000..dccab61cede766 --- /dev/null +++ b/test/-ext-/string/test_chilled.rb @@ -0,0 +1,19 @@ +require 'test/unit' +require '-test-/string' + +class Test_String_ChilledString < Test::Unit::TestCase + def test_rb_str_chilled_p + str = "" + assert_equal true, Bug::String.rb_str_chilled_p(str) + end + + def test_rb_str_chilled_p_frozen + str = "".freeze + assert_equal false, Bug::String.rb_str_chilled_p(str) + end + + def test_rb_str_chilled_p_mutable + str = "".dup + assert_equal false, Bug::String.rb_str_chilled_p(str) + end +end diff --git a/test/.excludes-prism/TestCall.rb b/test/.excludes-prism/TestCall.rb new file mode 100644 index 00000000000000..fa84af685b5196 --- /dev/null +++ b/test/.excludes-prism/TestCall.rb @@ -0,0 +1,3 @@ +exclude(:test_call_op_asgn_keywords, "unknown") +exclude(:test_call_op_asgn_keywords_mutable, "unknown") +exclude(:test_kwsplat_block_order_op_asgn, "unknown") diff --git a/test/.excludes-prism/TestClass.rb b/test/.excludes-prism/TestClass.rb new file mode 100644 index 00000000000000..14b768b9b709e0 --- /dev/null +++ b/test/.excludes-prism/TestClass.rb @@ -0,0 +1,3 @@ +exclude(:test_invalid_break_from_class_definition, "unknown") +exclude(:test_invalid_next_from_class_definition, "unknown") +exclude(:test_invalid_return_from_class_definition, "unknown") diff --git a/test/.excludes-prism/TestEval.rb b/test/.excludes-prism/TestEval.rb deleted file mode 100644 index 83f3e38fc9721f..00000000000000 --- a/test/.excludes-prism/TestEval.rb +++ /dev/null @@ -1,2 +0,0 @@ -exclude(:test_eval_ascii_incompatible, "incorrect encoding") -exclude(:test_file_encoding, "incorrect encoding") diff --git a/test/.excludes-prism/TestException.rb b/test/.excludes-prism/TestException.rb deleted file mode 100644 index 3f6d0e3b812f7e..00000000000000 --- a/test/.excludes-prism/TestException.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:test_syntax_error_path, "unknown") diff --git a/test/.excludes-prism/TestIRB/RubyLexTest.rb b/test/.excludes-prism/TestIRB/RubyLexTest.rb new file mode 100644 index 00000000000000..2274ae62cfd589 --- /dev/null +++ b/test/.excludes-prism/TestIRB/RubyLexTest.rb @@ -0,0 +1 @@ +exclude(:test_code_block_open_with_should_continue, "symbol encoding") diff --git a/test/.excludes-prism/TestISeq.rb b/test/.excludes-prism/TestISeq.rb index de7052cb112d1f..71d158367792bb 100644 --- a/test/.excludes-prism/TestISeq.rb +++ b/test/.excludes-prism/TestISeq.rb @@ -1 +1,7 @@ +exclude(:test_each_child, "unknown") +exclude(:test_frozen_string_literal_compile_option, "unknown") exclude(:test_syntax_error_message, "unknown") +exclude(:test_to_binary_class_tracepoint, "unknown") +exclude(:test_to_binary_end_tracepoint, "unknown") +exclude(:test_trace_points, "unknown") +exclude(:test_unreachable_syntax_error, "unknown") diff --git a/test/.excludes-prism/TestIseqLoad.rb b/test/.excludes-prism/TestIseqLoad.rb new file mode 100644 index 00000000000000..036e03f7e4f500 --- /dev/null +++ b/test/.excludes-prism/TestIseqLoad.rb @@ -0,0 +1,5 @@ +exclude(:test_bug8543, "unknown") +exclude(:test_case_when, "unknown") +exclude(:test_hidden, "unknown") +exclude(:test_kwarg, "unknown") +exclude(:test_splatsplat, "unknown") diff --git a/test/.excludes-prism/TestParse.rb b/test/.excludes-prism/TestParse.rb index b5c4c6202d8cff..50a82029b5f838 100644 --- a/test/.excludes-prism/TestParse.rb +++ b/test/.excludes-prism/TestParse.rb @@ -1,11 +1,43 @@ -exclude(:test_assign_in_conditional, "missing warning") -exclude(:test_here_document, "incorrect heredoc") -exclude(:test_magic_comment, "incorrect encoding") +exclude(:test_alias_backref, "unknown") +exclude(:test_bad_arg, "unknown") +exclude(:test_block_dup, "unknown") +exclude(:test_class_module, "unknown") +exclude(:test_disallowed_class_variable, "unknown") +exclude(:test_disallowed_gloal_variable, "unknown") +exclude(:test_disallowed_instance_variable, "unknown") +exclude(:test_dynamic_constant_assignment, "unknown") +exclude(:test_else_without_rescue, "unknown") +exclude(:test_embedded_rd_error, "unknown") +exclude(:test_embedded_rd, "unknown") +exclude(:test_error_def_in_argument, "unknown") +exclude(:test_float, "unknown") +exclude(:test_global_variable, "unknown") +exclude(:test_here_document, "unknown") +exclude(:test_heredoc_unterminated_interpolation, "unknown") +exclude(:test_invalid_break_from_class_definition, "unknown") +exclude(:test_invalid_char, "unknown") +exclude(:test_invalid_class_variable, "unknown") +exclude(:test_invalid_instance_variable, "unknown") +exclude(:test_invalid_next_from_class_definition, "unknown") +exclude(:test_location_of_invalid_token, "unknown") +exclude(:test_no_blockarg, "unknown") +exclude(:test_op_asgn1_with_block, "unknown") +exclude(:test_parse_string, "unknown") +exclude(:test_percent, "unknown") +exclude(:test_question, "unknown") +exclude(:test_shareable_constant_value_ignored, "unknown") exclude(:test_shareable_constant_value_nested, "ractor support") exclude(:test_shareable_constant_value_nonliteral, "ractor support") exclude(:test_shareable_constant_value_simple, "ractor support") exclude(:test_shareable_constant_value_unfrozen, "ractor support") exclude(:test_shareable_constant_value_unshareable_literal, "ractor support") +exclude(:test_string, "unknown") +exclude(:test_truncated_source_line, "unknown") +exclude(:test_unassignable, "unknown") +exclude(:test_unexpected_eof, "unknown") +exclude(:test_unexpected_token_after_numeric, "unknown") +exclude(:test_unterminated_regexp_error, "unknown") exclude(:test_unused_variable, "missing warning") -exclude(:test_utf8_bom, "incorrect error") exclude(:test_void_expr_stmts_value, "missing warning") +exclude(:test_void_value_in_rhs, "unknown") +exclude(:test_words, "unknown") diff --git a/test/.excludes-prism/TestPatternMatching.rb b/test/.excludes-prism/TestPatternMatching.rb index cb64a6d818787c..cfd0c6bed97058 100644 --- a/test/.excludes-prism/TestPatternMatching.rb +++ b/test/.excludes-prism/TestPatternMatching.rb @@ -1 +1,2 @@ exclude(:test_hash_pattern, "useless literal warning missing") +exclude(:test_invalid_syntax, "[a:] is disallowed") diff --git a/test/.excludes-prism/TestRegexp.rb b/test/.excludes-prism/TestRegexp.rb new file mode 100644 index 00000000000000..2cf1902348455b --- /dev/null +++ b/test/.excludes-prism/TestRegexp.rb @@ -0,0 +1,6 @@ +exclude(:test_invalid_escape_error, "unknown") +exclude(:test_invalid_fragment, "unknown") +exclude(:test_unescape, "unknown") +exclude(:test_unicode_age_14_0, "unknown") +exclude(:test_unicode_age_15_0, "unknown") +exclude(:test_unicode_age, "unknown") diff --git a/test/.excludes-prism/TestRubyLiteral.rb b/test/.excludes-prism/TestRubyLiteral.rb index 1f78ea55a4172b..1174baa95ffb46 100644 --- a/test/.excludes-prism/TestRubyLiteral.rb +++ b/test/.excludes-prism/TestRubyLiteral.rb @@ -1,4 +1,6 @@ +exclude(:test_debug_frozen_string_in_array_literal, "unknown") +exclude(:test_debug_frozen_string, "unknown") exclude(:test_dregexp, "unknown") -exclude(:test_frozen_string_in_array_literal, "incorrect frozen value") -exclude(:test_hash_duplicated_key, "parser implementation dependent") +exclude(:test_hash_value_omission, "unknown") +exclude(:test_integer, "unknown") exclude(:test_string, "https://github.com/ruby/prism/issues/2331") diff --git a/test/.excludes-prism/TestRubyOptimization.rb b/test/.excludes-prism/TestRubyOptimization.rb new file mode 100644 index 00000000000000..df22ca4f714860 --- /dev/null +++ b/test/.excludes-prism/TestRubyOptimization.rb @@ -0,0 +1 @@ +exclude(:test_peephole_string_literal_range, "unknown") diff --git a/test/.excludes-prism/TestRubyVM.rb b/test/.excludes-prism/TestRubyVM.rb new file mode 100644 index 00000000000000..6d4c3ca6fe4513 --- /dev/null +++ b/test/.excludes-prism/TestRubyVM.rb @@ -0,0 +1 @@ +exclude(:test_keep_script_lines, "unknown") diff --git a/test/.excludes-prism/TestSyntax.rb b/test/.excludes-prism/TestSyntax.rb index 23c755738e196c..f2f13e3161c674 100644 --- a/test/.excludes-prism/TestSyntax.rb +++ b/test/.excludes-prism/TestSyntax.rb @@ -1,8 +1,36 @@ +exclude(:test__END___cr, "unknown") +exclude(:test_anonymous_block_forwarding, "unknown") +exclude(:test_anonymous_keyword_rest_forwarding, "unknown") +exclude(:test_anonymous_rest_forwarding, "unknown") +exclude(:test_argument_forwarding_with_anon_rest_kwrest_and_block, "unknown") +exclude(:test_argument_forwarding_with_super, "unknown") +exclude(:test_argument_forwarding, "unknown") +exclude(:test_brace_after_literal_argument, "unknown") exclude(:test_dedented_heredoc_concatenation, "unknown") exclude(:test_dedented_heredoc_continued_line, "unknown") +exclude(:test_dedented_heredoc_invalid_identifer, "unknown") exclude(:test_duplicated_when, "unknown") -exclude(:test_integer_suffix, "unknown") +exclude(:test_error_message_encoding, "unknown") +exclude(:test_heredoc_cr, "unknown") +exclude(:test_heredoc_no_terminator, "unknown") +exclude(:test_invalid_break, "unknown") +exclude(:test_invalid_encoding_symbol, "unknown") +exclude(:test_invalid_literal_message, "unknown") +exclude(:test_invalid_next, "unknown") exclude(:test_it, "https://github.com/ruby/prism/issues/2323") -exclude(:test_keyword_duplicated, "unknown") +exclude(:test_keyword_invalid_name, "unknown") +exclude(:test_keyword_self_reference, "unknown") +exclude(:test_keywords_specified_and_not_accepted, "unknown") +exclude(:test_methoddef_endless_command, "unknown") exclude(:test_numbered_parameter, "unknown") -exclude(:test_too_big_nth_ref, "unknown") +exclude(:test_optional_self_reference, "unknown") +exclude(:test_parenthesised_statement_argument, "unknown") +exclude(:test_range_at_eol, "unknown") +exclude(:test_safe_call_in_massign_lhs, "unknown") +exclude(:test_syntax_error_at_newline, "unknown") +exclude(:test_unassignable, "unknown") +exclude(:test_unexpected_fraction, "unknown") +exclude(:test_unterminated_heredoc_cr, "unknown") +exclude(:test_unterminated_heredoc, "unknown") +exclude(:test_warn_balanced, "unknown") +exclude(:test_warn_unreachable, "unknown") diff --git a/test/.excludes-prism/TestUnicodeEscape.rb b/test/.excludes-prism/TestUnicodeEscape.rb index 93ed9fcb4514a5..add4911bc2f0aa 100644 --- a/test/.excludes-prism/TestUnicodeEscape.rb +++ b/test/.excludes-prism/TestUnicodeEscape.rb @@ -1,2 +1 @@ exclude(:test_fail, "unknown") -exclude(:test_regexp, "unknown") diff --git a/test/irb/test_input_method.rb b/test/irb/test_input_method.rb index 7644d3176aabb7..ce317b4b32ed3d 100644 --- a/test/irb/test_input_method.rb +++ b/test/irb/test_input_method.rb @@ -88,17 +88,18 @@ def setup @driver = RDoc::RI::Driver.new(use_stdout: true) end - def display_document(target, bind) + def display_document(target, bind, driver = nil) input_method = IRB::RelineInputMethod.new(IRB::RegexpCompletor.new) + input_method.instance_variable_set(:@rdoc_ri_driver, driver) if driver input_method.instance_variable_set(:@completion_params, ['', target, '', bind]) - input_method.display_document(target, driver: @driver) + input_method.display_document(target) end def test_perfectly_matched_namespace_triggers_document_display omit unless has_rdoc_content? out, err = capture_output do - display_document("String", binding) + display_document("String", binding, @driver) end assert_empty(err) @@ -109,7 +110,7 @@ def test_perfectly_matched_namespace_triggers_document_display def test_perfectly_matched_multiple_namespaces_triggers_document_display result = nil out, err = capture_output do - result = display_document("{}.nil?", binding) + result = display_document("{}.nil?", binding, @driver) end assert_empty(err) @@ -131,7 +132,7 @@ def test_perfectly_matched_multiple_namespaces_triggers_document_display def test_not_matched_namespace_triggers_nothing result = nil out, err = capture_output do - result = display_document("Stri", binding) + result = display_document("Stri", binding, @driver) end assert_empty(err) @@ -156,7 +157,7 @@ def test_perfect_matching_stops_without_rdoc def test_perfect_matching_handles_nil_namespace out, err = capture_output do # symbol literal has `nil` doc namespace so it's a good test subject - assert_nil(display_document(":aiueo", binding)) + assert_nil(display_document(":aiueo", binding, @driver)) end assert_empty(err) diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 8c4fb5ddee3d7a..966c840135c74f 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -70,11 +70,8 @@ def test_empty_input_echoing_behaviour type "exit" end - # Input cramped together due to how Reline's Reline::GeneralIO works - assert_include( - output, - "irb(main):001> irb(main):002> irb(main):002> irb(main):002> => nil\r\n" - ) + assert_not_match(/irb\(main\):001> (\r*\n)?=> nil/, output) + assert_match(/irb\(main\):002> (\r*\n)?=> nil/, output) end end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 511df58ea6c930..44e07a3a1232c1 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -137,6 +137,7 @@ def b; true; end a .a .b + .itself EOC close assert_screen(<<~EOC) @@ -153,9 +154,10 @@ def b; true; end irb(main):008> irb(main):009> a irb(main):010> .a - irb(main):011> .b + irb(main):011> .b + irb(main):012> .itself => true - irb(main):012> + irb(main):013> EOC end @@ -181,7 +183,6 @@ def c; true; end (a) &.b() - class A def b; self; end; def c; true; end; end; a = A.new a @@ -190,6 +191,7 @@ class A def b; self; end; def c; true; end; end; .c (a) &.b() + .itself EOC close assert_screen(<<~EOC) @@ -214,17 +216,17 @@ class A def b; self; end; def c; true; end; end; irb(main):015> &.b() => # irb(main):016> - irb(main):017> - irb(main):018> class A def b; self; end; def c; true; end; end; - irb(main):019> a = A.new + irb(main):017> class A def b; self; end; def c; true; end; end; + irb(main):018> a = A.new => # - irb(main):020> a - irb(main):021> .b - irb(main):022> # aaa - irb(main):023> .c + irb(main):019> a + irb(main):020> .b + irb(main):021> # aaa + irb(main):022> .c => true - irb(main):024> (a) - irb(main):025> &.b() + irb(main):023> (a) + irb(main):024> &.b() + irb(main):025> .itself => # irb(main):026> EOC @@ -254,9 +256,9 @@ def test_autocomplete_with_multiple_doc_namespaces start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') write("{}.__id_") write("\C-i") + sleep 0.2 close screen = result.join("\n").sub(/\n*\z/, "\n") - # This assertion passes whether showdoc dialog completed or not. assert_match(/start\ IRB\nirb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/, screen) end @@ -276,6 +278,7 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') write("IR") write("\C-i") + sleep 0.2 close # This is because on macOS we display different shortcut for displaying the full doc @@ -313,6 +316,7 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') write("IR") write("\C-i") + sleep 0.2 close assert_screen(<<~EOC) start IRB diff --git a/test/openssl/fixtures/pkey/dsa2048.pem b/test/openssl/fixtures/pkey/dsa2048.pem new file mode 100644 index 00000000000000..3f22b22b58bd0a --- /dev/null +++ b/test/openssl/fixtures/pkey/dsa2048.pem @@ -0,0 +1,15 @@ +-----BEGIN PRIVATE KEY----- +MIICXgIBADCCAjYGByqGSM44BAEwggIpAoIBAQDXZhJ/dQoWkQELzjzlx8FtIp96 +voCYe5NY0H8j0jz7GyHpXt41+MteqkZK3/Ah+cNR9uG8iEYArAZ71LcWotfee2Gz +xdxozr9bRt0POYhO2YIsfMpBrEskPsDH2g/2nFV8l4OJgxU2qZUrF4PN5ha+Mu6u +sVtN8hjvAvnbf4Pxn0b8NN9f4PJncroUa8acv5WsV85E1RW7NYCefggU4LytYIHg +euRF9eY9gVCX5MkUgW2xODHIYJhwk/+5lJxG7qUsSahD/nPHO/yoWgdVHq2DkdTq +KYXkAxx2PJcTBOHTglhE6mgCbEKp8vcfElnBWyCT6QykclZiPXXD2JV829J/Ah0A +vYa+/G/gUZiomyejVje6UsGoCc+vInxmovOL8QKCAQEAhnKEigYPw6u8JY7v5iGo +Ylz8qiMFYmaJCwevf3KCjWeEXuNO4OrKdfzkQl1tPuGLioYFfP1A2yGosjdUdLEB +0JqnzlKxUp+G6RfBj+WYzbgc5hr7t0M+reAJh09/hDzqfxjcgiHstq7mpRXBP8Y7 +iu27s7TRYJNSAYRvWcXNSBEUym3mHBBbZn7VszYooSrn60/iZ8I+VY1UF/fgqhbj +JfaaZNQCDO9K3Vb3rsXoYd8+bOZIen9uHB+pNjMqhpl4waysqrlpGFeeqdxivH6S +vkrHLs6/eWVMnS08RdcryoCrI3Bm8mMBKQglDwKLnWLfzG565qEhslzyCd/l9k9a +cwQfAh0Ao8/g72fSFmo04FizM7DZJSIPqDLjfZu9hLvUFA== +-----END PRIVATE KEY----- diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb index 3f64a80e324814..4c93f2869d8040 100644 --- a/test/openssl/test_pkey_dsa.rb +++ b/test/openssl/test_pkey_dsa.rb @@ -31,11 +31,6 @@ def test_new_break def test_generate # DSA.generate used to call DSA_generate_parameters_ex(), which adjusts the # size of q according to the size of p - key1024 = OpenSSL::PKey::DSA.generate(1024) - assert_predicate key1024, :private? - assert_equal 1024, key1024.p.num_bits - assert_equal 160, key1024.q.num_bits - key2048 = OpenSSL::PKey::DSA.generate(2048) assert_equal 2048, key2048.p.num_bits assert_equal 256, key2048.q.num_bits @@ -47,28 +42,41 @@ def test_generate end end + def test_generate_on_non_fips + # DSA with 1024 bits is invalid on FIPS 186-4. + # https://github.com/openssl/openssl/commit/49ed5ba8f62875074f04417189147fd3dda072ab + omit_on_fips + + key1024 = OpenSSL::PKey::DSA.generate(1024) + assert_predicate key1024, :private? + assert_equal 1024, key1024.p.num_bits + assert_equal 160, key1024.q.num_bits + end + def test_sign_verify - dsa512 = Fixtures.pkey("dsa512") + # The DSA valid size is 2048 or 3072 on FIPS. + # https://github.com/openssl/openssl/blob/7649b5548e5c0352b91d9d3ed695e42a2ac1e99c/providers/common/securitycheck.c#L185-L188 + dsa = Fixtures.pkey("dsa2048") data = "Sign me!" if defined?(OpenSSL::Digest::DSS1) - signature = dsa512.sign(OpenSSL::Digest.new('DSS1'), data) - assert_equal true, dsa512.verify(OpenSSL::Digest.new('DSS1'), signature, data) + signature = dsa.sign(OpenSSL::Digest.new('DSS1'), data) + assert_equal true, dsa.verify(OpenSSL::Digest.new('DSS1'), signature, data) end - signature = dsa512.sign("SHA256", data) - assert_equal true, dsa512.verify("SHA256", signature, data) + signature = dsa.sign("SHA256", data) + assert_equal true, dsa.verify("SHA256", signature, data) signature0 = (<<~'end;').unpack1("m") - MCwCFH5h40plgU5Fh0Z4wvEEpz0eE9SnAhRPbkRB8ggsN/vsSEYMXvJwjGg/ - 6g== + MD4CHQC0zmRkVOAHJTm28fS5PVUv+4LtBeNaKqr/yfmVAh0AsTcLqofWHoW8X5oWu8AOvngOcFVZ + cLTvhY3XNw== end; - assert_equal true, dsa512.verify("SHA256", signature0, data) + assert_equal true, dsa.verify("SHA256", signature0, data) signature1 = signature0.succ - assert_equal false, dsa512.verify("SHA256", signature1, data) + assert_equal false, dsa.verify("SHA256", signature1, data) end def test_sign_verify_raw - key = Fixtures.pkey("dsa512") + key = Fixtures.pkey("dsa2048") data = 'Sign me!' digest = OpenSSL::Digest.digest('SHA1', data) @@ -127,6 +135,8 @@ def test_DSAPrivateKey end def test_DSAPrivateKey_encrypted + omit_on_fips + # key = abcdef dsa512 = Fixtures.pkey("dsa512") pem = <<~EOF diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index dcb7757add02be..66d63a981d08c2 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -691,7 +691,7 @@ def test_verify_wildcard assert_equal(true, OpenSSL::SSL.verify_wildcard("xn--qdk4b9b", "xn--qdk4b9b")) end - # Comments in this test is excerpted from http://tools.ietf.org/html/rfc6125#page-27 + # Comments in this test is excerpted from https://www.rfc-editor.org/rfc/rfc6125#page-27 def test_post_connection_check_wildcard_san # case-insensitive ASCII comparison # RFC 6125, section 6.4.1 diff --git a/test/prism/command_line_test.rb b/test/prism/command_line_test.rb index 57ab0dee45e944..4b04c36f3aa5e2 100644 --- a/test/prism/command_line_test.rb +++ b/test/prism/command_line_test.rb @@ -65,5 +65,47 @@ def test_command_line_e result = Prism.parse("1 if 2..3", command_line: "e") assert_equal 0, result.warnings.length end + + def test_command_line_x_implicit + result = Prism.parse(<<~RUBY) + #!/bin/bash + exit 1 + + #!/usr/bin/env ruby + 1 + RUBY + + assert_kind_of IntegerNode, result.value.statements.body.first + end + + def test_command_line_x_explicit + result = Prism.parse(<<~RUBY, command_line: "x") + exit 1 + + #!/usr/bin/env ruby + 1 + RUBY + + assert_kind_of IntegerNode, result.value.statements.body.first + end + + def test_command_line_x_implicit_fail + result = Prism.parse(<<~RUBY) + #!/bin/bash + exit 1 + RUBY + + assert_equal 1, result.errors.length + assert_equal :load, result.errors.first.level + end + + def test_command_line_x_explicit_fail + result = Prism.parse(<<~RUBY, command_line: "x") + exit 1 + RUBY + + assert_equal 1, result.errors.length + assert_equal :load, result.errors.first.level + end end end diff --git a/test/prism/encoding_test.rb b/test/prism/encoding_test.rb index 649d05b874164b..2aee473ddf9901 100644 --- a/test/prism/encoding_test.rb +++ b/test/prism/encoding_test.rb @@ -344,7 +344,7 @@ def assert_encoding(encoding, name, range) next if ["/", "{"].include?(character) source = "# encoding: #{name}\n/(?##{character})/\n" - assert Prism.parse(source).success? + assert Prism.parse(source).success?, "Expected #{source.inspect} to parse successfully." end rescue RangeError source = "# encoding: #{name}\n\\x#{codepoint.to_s(16)}" diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index ccf7485c7bdb26..dbc4a8924d8565 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -26,7 +26,7 @@ def test_module_name_recoverable ) assert_errors expected, "module Parent module end", [ - ["expected a constant name after `module`", 14..20], + ["unexpected constant path after `module`; class/module name must be CONSTANT", 14..20], ["unexpected 'end', assuming it is closing the parent module definition", 21..24] ] end @@ -177,7 +177,7 @@ def test_unterminated_empty_string def test_incomplete_instance_var_string assert_errors expression('%@#@@#'), '%@#@@#', [ - ["'@#' is not allowed as an instance variable name", 4..5], + ["'@' without identifiers is not allowed as an instance variable name", 4..5], ["unexpected instance variable, expecting end-of-input", 4..5] ] end @@ -325,7 +325,7 @@ def test_aliasing_non_global_variable_with_global_variable def test_aliasing_global_variable_with_global_number_variable assert_errors expression("alias $a $1"), "alias $a $1", [ - ["invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", 9..11] + ["invalid argument being passed to `alias`; can't make alias for the number variables", 9..11] ] end @@ -452,7 +452,7 @@ def test_module_definition_in_method_body ) assert_errors expected, "def foo;module A;end;end", [ - ["unexpected module definition in a method definition", 8..14] + ["unexpected module definition in method body", 8..14] ] end @@ -490,7 +490,7 @@ def test_module_definition_in_method_body_within_block Location() ) - assert_errors expected, <<~RUBY, [["unexpected module definition in a method definition", 21..27]] + assert_errors expected, <<~RUBY, [["unexpected module definition in method body", 21..27]] def foo bar do module Foo;end @@ -505,7 +505,7 @@ def foo(bar = module A;end);end def foo;rescue;module A;end;end def foo;ensure;module A;end;end RUBY - message = "unexpected module definition in a method definition" + message = "unexpected module definition in method body" assert_errors expression(source), source, [ [message, 14..20], [message, 47..53], @@ -541,7 +541,7 @@ def test_class_definition_in_method_body ) assert_errors expected, "def foo;class A;end;end", [ - ["unexpected class definition in a method definition", 8..13] + ["unexpected class definition in method body", 8..13] ] end @@ -551,7 +551,7 @@ def foo(bar = class A;end);end def foo;rescue;class A;end;end def foo;ensure;class A;end;end RUBY - message = "unexpected class definition in a method definition" + message = "unexpected class definition in method body" assert_errors expression(source), source, [ [message, 14..19], [message, 46..51], @@ -1091,7 +1091,7 @@ def test_dont_allow_return_inside_class_body ) assert_errors expected, "class A; return; end", [ - ["invalid `return` in a class or module body", 15..16] + ["invalid `return` in a class or module body", 9..15] ] end @@ -1106,7 +1106,7 @@ def test_dont_allow_return_inside_module_body ) assert_errors expected, "module A; return; end", [ - ["invalid `return` in a class or module body", 16..17] + ["invalid `return` in a class or module body", 10..16] ] end @@ -1130,28 +1130,24 @@ def test_dont_allow_setting_to_back_and_nth_reference end def test_duplicated_parameter_names - # For some reason, Ripper reports no error for Ruby 3.0 when you have - # duplicated parameter names for positional parameters. - unless RUBY_VERSION < "3.1.0" - expected = DefNode( - :foo, - Location(), - nil, - ParametersNode([RequiredParameterNode(0, :a), RequiredParameterNode(0, :b), RequiredParameterNode(ParameterFlags::REPEATED_PARAMETER, :a)], [], nil, [], [], nil, nil), - nil, - [:a, :b], - Location(), - nil, - Location(), - Location(), - nil, - Location() - ) + expected = DefNode( + :foo, + Location(), + nil, + ParametersNode([RequiredParameterNode(0, :a), RequiredParameterNode(0, :b), RequiredParameterNode(ParameterFlags::REPEATED_PARAMETER, :a)], [], nil, [], [], nil, nil), + nil, + [:a, :b], + Location(), + nil, + Location(), + Location(), + nil, + Location() + ) - assert_errors expected, "def foo(a,b,a);end", [ - ["repeated parameter name", 12..13] - ] - end + assert_errors expected, "def foo(a,b,a);end", [ + ["duplicated argument name", 12..13] + ] expected = DefNode( :foo, @@ -1169,7 +1165,7 @@ def test_duplicated_parameter_names ) assert_errors expected, "def foo(a,b,*a);end", [ - ["repeated parameter name", 13..14] + ["duplicated argument name", 13..14] ] expected = DefNode( @@ -1188,7 +1184,7 @@ def test_duplicated_parameter_names ) assert_errors expected, "def foo(a,b,**a);end", [ - ["repeated parameter name", 14..15] + ["duplicated argument name", 14..15] ] expected = DefNode( @@ -1207,7 +1203,7 @@ def test_duplicated_parameter_names ) assert_errors expected, "def foo(a,b,&a);end", [ - ["repeated parameter name", 13..14] + ["duplicated argument name", 13..14] ] expected = DefNode( @@ -1258,7 +1254,7 @@ def test_invalid_operator_write_dot def test_unterminated_global_variable assert_errors expression("$"), "$", [ - ["'$' is not allowed as a global variable name", 0..1] + ["'$' without identifiers is not allowed as a global variable name", 0..1] ] end @@ -1370,7 +1366,7 @@ def test_double_scope_numbered_parameters source = "-> { _1 + -> { _2 } }" errors = [["numbered parameter is already used in outer scope", 15..17]] - assert_errors expression(source), source, errors, compare_ripper: false + assert_errors expression(source), source, errors end def test_invalid_number_underscores @@ -1436,13 +1432,13 @@ def test_parameter_name_ending_with_bang_or_question_mark ["unexpected name for a parameter", 8..10], ["unexpected name for a parameter", 11..13] ] - assert_errors expression(source), source, errors, compare_ripper: false + assert_errors expression(source), source, errors end def test_class_name source = "class 0.X end" assert_errors expression(source), source, [ - ["expected a constant name after `class`", 6..9], + ["unexpected constant path after `class`; class/module name must be CONSTANT", 6..9], ] end @@ -1482,17 +1478,16 @@ def test_shadow_args_in_lambda def test_shadow_args_in_block source = "tap{|a;a|}" assert_errors expression(source), source, [ - ["repeated parameter name", 7..8], + ["duplicated argument name", 7..8], ] end def test_repeated_parameter_name_in_destructured_params source = "def f(a, (b, (a))); end" - # In Ruby 3.0.x, `Ripper.sexp_raw` does not return `nil` for this case. - compare_ripper = RUBY_ENGINE == "ruby" && (RUBY_VERSION.split('.').map { |x| x.to_i } <=> [3, 1]) >= 1 + assert_errors expression(source), source, [ - ["repeated parameter name", 14..15], - ], compare_ripper: compare_ripper + ["duplicated argument name", 14..15], + ] end def test_assign_to_numbered_parameter @@ -1574,6 +1569,7 @@ def test_check_value_expression 1 => ^(if 1; (return) else (return) end) 1 => ^(unless 1; (return) else (return) end) RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 7..13], @@ -1584,7 +1580,7 @@ def test_check_value_expression [message, 97..103], [message, 123..129], [message, 168..174], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_statement @@ -1607,6 +1603,7 @@ class << (return) for x in (return) end RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 4..10], @@ -1617,7 +1614,7 @@ class << (return) [message, 110..116], [message, 132..138], [message, 154..160], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_def @@ -1629,12 +1626,13 @@ def x(a = return) def x(a: return) end RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 5..11], [message, 29..35], [message, 50..56], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_assignment @@ -1644,13 +1642,14 @@ def test_void_value_expression_in_assignment a, b = return, 1 a, b = 1, *return RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 4..10], [message, 18..24], [message, 32..38], [message, 53..59], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_modifier @@ -1662,6 +1661,7 @@ def test_void_value_expression_in_modifier (return) => a (return) in a RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 6..12], @@ -1670,7 +1670,7 @@ def test_void_value_expression_in_modifier [message, 58..64], [message, 67..73], [message, 81..87] - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_expression @@ -1685,6 +1685,7 @@ def test_void_value_expression_in_expression ((return)..) ((return)...) RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 1..7], @@ -1696,7 +1697,7 @@ def test_void_value_expression_in_expression [message, 85..91], [message, 96..102], [message, 109..115] - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_array @@ -1709,6 +1710,7 @@ def test_void_value_expression_in_array [ *return ] [ **return ] RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 1..7], @@ -1718,7 +1720,7 @@ def test_void_value_expression_in_array [message, 58..64], [message, 70..76], [message, 83..89], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_hash @@ -1728,13 +1730,14 @@ def test_void_value_expression_in_hash { a: return } { **return } RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 2..8], [message, 23..29], [message, 37..43], [message, 50..56], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_call @@ -1745,6 +1748,7 @@ def test_void_value_expression_in_call (return)[1] = 2 (return)::foo RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 1..7], @@ -1752,7 +1756,7 @@ def test_void_value_expression_in_call [message, 27..33], [message, 39..45], [message, 55..61], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_constant_path @@ -1760,11 +1764,12 @@ def test_void_value_expression_in_constant_path (return)::A class (return)::A; end RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 1..7], [message, 19..25], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_arguments @@ -1778,6 +1783,7 @@ def test_void_value_expression_in_arguments foo(:a => return) foo(a: return) RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 4..10], @@ -1788,7 +1794,7 @@ def test_void_value_expression_in_arguments [message, 71..77], [message, 94..100], [message, 109..115], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_unary_call @@ -1796,11 +1802,12 @@ def test_void_value_expression_in_unary_call +(return) not return RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 2..8], [message, 14..20], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_void_value_expression_in_binary_call @@ -1812,13 +1819,14 @@ def test_void_value_expression_in_binary_call 1 or (return) (return) or 1 RUBY + message = 'unexpected void value expression' assert_errors expression(source), source, [ [message, 5..11], [message, 14..20], [message, 42..48], [message, 71..77], - ], compare_ripper: false # Ripper does not check 'void value expression'. + ] end def test_trailing_comma_in_calls @@ -1934,13 +1942,14 @@ def foo(bar: bar) = 42 proc { |foo = foo| } proc { |foo: foo| } RUBY + message = 'parameter default value references itself' assert_errors expression(source), source, [ [message, 14..17], [message, 37..40], [message, 61..64], [message, 81..84], - ], compare_ripper: false # Ripper does not check 'circular reference'. + ] end def test_command_calls @@ -1976,8 +1985,9 @@ def a = b rescue c d begin; rescue a b; end begin; rescue a b => c; end RUBY + sources.each do |source| - assert_nil Ripper.sexp_raw(source) + refute_valid_syntax(source) assert_false(Prism.parse(source).success?) end end @@ -1993,8 +2003,9 @@ def test_range_and_bin_op 1.. % 2 1.. ** 2 RUBY + sources.each do |source| - assert_nil Ripper.sexp_raw(source) + refute_valid_syntax(source) assert_false(Prism.parse(source).success?) end end @@ -2049,22 +2060,36 @@ def test_non_assoc_equality def test_block_arg_and_block source = 'foo(&1) { }' assert_errors expression(source), source, [ - ['multiple block arguments; only one block is allowed', 8..11] - ], compare_ripper: false # Ripper does not check 'both block arg and actual block given'. + ["both block arg and actual block given; only one block is allowed", 8..11] + ] end def test_forwarding_arg_and_block source = 'def foo(...) = foo(...) { }' assert_errors expression(source), source, [ ['both a block argument and a forwarding argument; only one block is allowed', 24..27] - ], compare_ripper: false # Ripper does not check 'both block arg and actual block given'. + ] end def test_it_with_ordinary_parameter source = "proc { || it }" errors = [["`it` is not allowed when an ordinary parameter is defined", 10..12]] - assert_errors expression(source), source, errors, compare_ripper: false + assert_errors expression(source), source, errors, check_valid_syntax: RUBY_VERSION >= "3.4.0" + end + + def test_regular_expression_with_unknown_regexp_options + source = "/foo/AZaz" + errors = [["unknown regexp options: AZaz", 4..9]] + + assert_errors expression(source), source, errors + end + + def test_interpolated_regular_expression_with_unknown_regexp_options + source = "/\#{foo}/AZaz" + errors = [["unknown regexp options: AZaz", 7..12]] + + assert_errors expression(source), source, errors end def test_singleton_method_for_literals @@ -2110,7 +2135,7 @@ def ([1]).foo; end ["cannot define singleton method for literals", 380..388], ["cannot define singleton method for literals", 404..407] ] - assert_errors expression(source), source, errors, compare_ripper: false + assert_errors expression(source), source, errors end def test_assignment_to_literal_in_conditionals @@ -2143,11 +2168,58 @@ def test_assignment_to_literal_in_conditionals ] * source.lines.count end + def test_duplicate_pattern_capture + source = <<~RUBY + case (); in [a, a]; end + case (); in [a, *a]; end + case (); in {a: a, b: a}; end + case (); in {a: a, **a}; end + case (); in [a, {a:}]; end + case (); in [a, {a: {a: {a: [a]}}}]; end + case (); in a => a; end + case (); in [A => a, {a: b => a}]; end + RUBY + + assert_error_messages source, Array.new(source.lines.length, "duplicated variable name") + refute_error_messages "case (); in [_a, _a]; end" + end + + def test_duplicate_pattern_hash_key + assert_error_messages "case (); in {a:, a:}; end", ["duplicated key name", "duplicated variable name"] + assert_error_messages "case (); in {a:1, a:2}; end", ["duplicated key name"] + refute_error_messages "case (); in [{a:1}, {a:2}]; end" + end + private - def assert_errors(expected, source, errors, compare_ripper: RUBY_ENGINE == "ruby") - # Ripper behaves differently on JRuby/TruffleRuby, so only check this on CRuby - assert_nil Ripper.sexp_raw(source) if compare_ripper + if RUBY_ENGINE == "ruby" + def check_syntax(source) + $VERBOSE, previous = nil, $VERBOSE + + begin + RubyVM::InstructionSequence.compile(source) + ensure + $VERBOSE = previous + end + end + + def assert_valid_syntax(source) + check_syntax(source) + end + + def refute_valid_syntax(source) + assert_raise(SyntaxError) { check_syntax(source) } + end + else + def assert_valid_syntax(source) + end + + def refute_valid_syntax(source) + end + end + + def assert_errors(expected, source, errors, check_valid_syntax: true) + refute_valid_syntax(source) if check_valid_syntax result = Prism.parse(source) node = result.value.statements.body.last @@ -2156,12 +2228,17 @@ def assert_errors(expected, source, errors, compare_ripper: RUBY_ENGINE == "ruby assert_equal(errors, result.errors.map { |e| [e.message, e.location.start_offset..e.location.end_offset] }) end - def assert_error_messages(source, errors, compare_ripper: RUBY_ENGINE == "ruby") - assert_nil Ripper.sexp_raw(source) if compare_ripper + def assert_error_messages(source, errors) + refute_valid_syntax(source) result = Prism.parse(source) assert_equal(errors, result.errors.map(&:message)) end + def refute_error_messages(source) + assert_valid_syntax(source) + assert Prism.parse_success?(source) + end + def assert_warning_messages(source, warnings) result = Prism.parse(source) assert_equal(warnings, result.warnings.map(&:message)) diff --git a/test/prism/fixtures/regex.txt b/test/prism/fixtures/regex.txt index ef2f6d45a32d8d..18200e5cbd230a 100644 --- a/test/prism/fixtures/regex.txt +++ b/test/prism/fixtures/regex.txt @@ -38,3 +38,9 @@ b>)/ =~ ""; ab a = 1 tap { /(?)/ =~ to_s } + +/(?)/ =~ "" +/(?)/ =~ "" + +/(?)/ =~ "" +def foo(nil:) = /(?)/ =~ "" diff --git a/test/prism/location_test.rb b/test/prism/location_test.rb index c7ce248b569f00..b7b9a754cae781 100644 --- a/test/prism/location_test.rb +++ b/test/prism/location_test.rb @@ -527,6 +527,7 @@ def test_InterpolatedMatchLastLineNode def test_InterpolatedRegularExpressionNode assert_location(InterpolatedRegularExpressionNode, "/\#{foo}/") + assert_location(InterpolatedRegularExpressionNode, "/\#{foo}/io") end def test_InterpolatedStringNode @@ -730,6 +731,7 @@ def test_RedoNode def test_RegularExpressionNode assert_location(RegularExpressionNode, "/foo/") + assert_location(RegularExpressionNode, "/foo/io") end def test_RequiredKeywordParameterNode diff --git a/test/prism/parameters_signature_test.rb b/test/prism/parameters_signature_test.rb index 788ce7b90712e4..0eed8d993d52c9 100644 --- a/test/prism/parameters_signature_test.rb +++ b/test/prism/parameters_signature_test.rb @@ -2,7 +2,7 @@ require_relative "test_helper" -return if RUBY_VERSION < "3.2" || RUBY_ENGINE == "truffleruby" +return if RUBY_VERSION < "3.2" module Prism class ParametersSignatureTest < TestCase @@ -55,6 +55,8 @@ def test_keyrest_anonymous end def test_key_ordering + omit("TruffleRuby returns keys in order they were declared") if RUBY_ENGINE == "truffleruby" + assert_parameters([[:keyreq, :a], [:keyreq, :b], [:key, :c], [:key, :d]], "a:, c: 1, b:, d: 2") end diff --git a/test/prism/parse_test.rb b/test/prism/parse_test.rb index db66b431ba7ad0..4255553f516afd 100644 --- a/test/prism/parse_test.rb +++ b/test/prism/parse_test.rb @@ -204,7 +204,7 @@ def test_parse_file_comments # Additionally, Ripper cannot parse the %w[] fixture in this file, so set ripper_should_parse to false. ripper_should_parse = false if relative == "spanning_heredoc.txt" - # Ruby < 3.3.0 cannot parse heredocs where there are leading whitespace charactes in the heredoc start. + # Ruby < 3.3.0 cannot parse heredocs where there are leading whitespace characters in the heredoc start. # Example: <<~' EOF' or <<-' EOF' # https://bugs.ruby-lang.org/issues/19539 ripper_should_parse = false if relative == "heredocs_leading_whitespace.txt" && RUBY_VERSION < "3.3.0" diff --git a/test/prism/ruby_api_test.rb b/test/prism/ruby_api_test.rb index 4153a69ad75644..49296117bf5488 100644 --- a/test/prism/ruby_api_test.rb +++ b/test/prism/ruby_api_test.rb @@ -233,21 +233,6 @@ def test_integer_base_flags assert_equal 16, base[parse_expression("0x1")] end - def test_offset - source = <<~RUBY - #!/bin/sh - - echo "foo" - exit 0 - - #!/usr/bin/env ruby - - puts "bar" - RUBY - - assert Prism.parse_success?(source, offset: 30) - end - private def parse_expression(source) diff --git a/test/prism/ruby_parser_test.rb b/test/prism/ruby_parser_test.rb index 1d22f0e7b8729f..c9883574a68b31 100644 --- a/test/prism/ruby_parser_test.rb +++ b/test/prism/ruby_parser_test.rb @@ -18,7 +18,7 @@ Sexp.prepend( Module.new do def ==(other) - super && line == other.line && max_line == other.max_line && file == other.file + super && line == other.line && line_max == other.line_max && file == other.file end end ) @@ -52,25 +52,10 @@ class RubyParserTest < TestCase whitequark/string_concat.txt ] - # These files contain CRLF line endings, which ruby_parser translates into - # LF before it gets back to the node. This means the node actually has the - # wrong contents. - crlf = %w[ - dos_endings.txt - heredoc_with_comment.txt - seattlerb/heredoc__backslash_dos_format.txt - seattlerb/heredoc_with_carriage_return_escapes_windows.txt - seattlerb/heredoc_with_extra_carriage_horrible_mix.txt - seattlerb/heredoc_with_extra_carriage_returns_windows.txt - seattlerb/heredoc_with_extra_carriage_returns.txt - seattlerb/heredoc_with_interpolation_and_carriage_return_escapes_windows.txt - seattlerb/heredoc_with_only_carriage_returns_windows.txt - seattlerb/heredoc_with_only_carriage_returns.txt - ] - # https://github.com/seattlerb/ruby_parser/issues/344 - failures = crlf | %w[ + failures = %w[ alias.txt + dos_endings.txt heredocs_with_ignored_newlines.txt method_calls.txt methods.txt @@ -79,8 +64,13 @@ class RubyParserTest < TestCase patterns.txt regex.txt seattlerb/and_multi.txt + seattlerb/heredoc__backslash_dos_format.txt seattlerb/heredoc_bad_hex_escape.txt seattlerb/heredoc_bad_oct_escape.txt + seattlerb/heredoc_with_extra_carriage_horrible_mix.txt + seattlerb/heredoc_with_extra_carriage_returns_windows.txt + seattlerb/heredoc_with_only_carriage_returns_windows.txt + seattlerb/heredoc_with_only_carriage_returns.txt spanning_heredoc_newlines.txt spanning_heredoc.txt tilde_heredocs.txt diff --git a/test/prism/snapshots/alias.txt b/test/prism/snapshots/alias.txt index 687082d85e2e31..a952e96f67721f 100644 --- a/test/prism/snapshots/alias.txt +++ b/test/prism/snapshots/alias.txt @@ -57,7 +57,7 @@ │ │ ├── opening_loc: (7,6)-(7,8) = ":\"" │ │ ├── parts: (length: 2) │ │ │ ├── @ StringNode (location: (7,8)-(7,11)) - │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── flags: frozen │ │ │ │ ├── opening_loc: ∅ │ │ │ │ ├── content_loc: (7,8)-(7,11) = "abc" │ │ │ │ ├── closing_loc: ∅ diff --git a/test/prism/snapshots/dash_heredocs.txt b/test/prism/snapshots/dash_heredocs.txt index 9af3acf9c2e6e8..bd2b05ea0d0717 100644 --- a/test/prism/snapshots/dash_heredocs.txt +++ b/test/prism/snapshots/dash_heredocs.txt @@ -79,10 +79,11 @@ │ ├── closing_loc: (23,0)-(24,0) = " EOF\n" │ └── unescaped: " a\n b\n" ├── @ InterpolatedStringNode (location: (25,0)-(25,8)) + │ ├── flags: ∅ │ ├── opening_loc: (25,0)-(25,8) = "<<-\"EOF\"" │ ├── parts: (length: 3) │ │ ├── @ StringNode (location: (26,0)-(27,0)) - │ │ │ ├── flags: ∅ + │ │ │ ├── flags: frozen │ │ │ ├── opening_loc: ∅ │ │ │ ├── content_loc: (26,0)-(27,0) = " a\n" │ │ │ ├── closing_loc: ∅ @@ -104,17 +105,18 @@ │ │ │ │ └── block: ∅ │ │ │ └── closing_loc: (27,3)-(27,4) = "}" │ │ └── @ StringNode (location: (27,4)-(28,0)) - │ │ ├── flags: ∅ + │ │ ├── flags: frozen │ │ ├── opening_loc: ∅ │ │ ├── content_loc: (27,4)-(28,0) = "\n" │ │ ├── closing_loc: ∅ │ │ └── unescaped: "\n" │ └── closing_loc: (28,0)-(29,0) = "EOF\n" ├── @ InterpolatedStringNode (location: (30,0)-(30,6)) + │ ├── flags: ∅ │ ├── opening_loc: (30,0)-(30,6) = "<<-EOF" │ ├── parts: (length: 3) │ │ ├── @ StringNode (location: (31,0)-(32,0)) - │ │ │ ├── flags: ∅ + │ │ │ ├── flags: frozen │ │ │ ├── opening_loc: ∅ │ │ │ ├── content_loc: (31,0)-(32,0) = " a\n" │ │ │ ├── closing_loc: ∅ @@ -136,7 +138,7 @@ │ │ │ │ └── block: ∅ │ │ │ └── closing_loc: (32,3)-(32,4) = "}" │ │ └── @ StringNode (location: (32,4)-(33,0)) - │ │ ├── flags: ∅ + │ │ ├── flags: frozen │ │ ├── opening_loc: ∅ │ │ ├── content_loc: (32,4)-(33,0) = "\n" │ │ ├── closing_loc: ∅ @@ -184,10 +186,11 @@ │ │ ├── flags: ∅ │ │ └── arguments: (length: 1) │ │ └── @ InterpolatedStringNode (location: (49,7)-(49,11)) + │ │ ├── flags: ∅ │ │ ├── opening_loc: (49,7)-(49,11) = "<<-B" │ │ ├── parts: (length: 3) │ │ │ ├── @ StringNode (location: (52,0)-(53,2)) - │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── flags: frozen │ │ │ │ ├── opening_loc: ∅ │ │ │ │ ├── content_loc: (52,0)-(53,2) = " b\n " │ │ │ │ ├── closing_loc: ∅ @@ -202,7 +205,7 @@ │ │ │ │ │ └── value: 2 │ │ │ │ └── closing_loc: (54,2)-(54,3) = "}" │ │ │ └── @ StringNode (location: (54,3)-(55,0)) - │ │ │ ├── flags: ∅ + │ │ │ ├── flags: frozen │ │ │ ├── opening_loc: ∅ │ │ │ ├── content_loc: (54,3)-(55,0) = "\n" │ │ │ ├── closing_loc: ∅ @@ -228,10 +231,11 @@ │ ├── flags: ∅ │ └── arguments: (length: 1) │ └── @ InterpolatedStringNode (location: (57,7)-(57,11)) + │ ├── flags: ∅ │ ├── opening_loc: (57,7)-(57,11) = "<<-B" │ ├── parts: (length: 3) │ │ ├── @ StringNode (location: (60,0)-(61,2)) - │ │ │ ├── flags: ∅ + │ │ │ ├── flags: frozen │ │ │ ├── opening_loc: ∅ │ │ │ ├── content_loc: (60,0)-(61,2) = " b\n " │ │ │ ├── closing_loc: ∅ @@ -246,7 +250,7 @@ │ │ │ │ └── value: 2 │ │ │ └── closing_loc: (62,3)-(62,4) = "}" │ │ └── @ StringNode (location: (62,4)-(63,0)) - │ │ ├── flags: ∅ + │ │ ├── flags: frozen │ │ ├── opening_loc: ∅ │ │ ├── content_loc: (62,4)-(63,0) = "\n" │ │ ├── closing_loc: ∅ diff --git a/test/prism/snapshots/dos_endings.txt b/test/prism/snapshots/dos_endings.txt index ed75b8a52fdaa7..1ae15e1e87c081 100644 --- a/test/prism/snapshots/dos_endings.txt +++ b/test/prism/snapshots/dos_endings.txt @@ -15,16 +15,17 @@ │ │ ├── flags: ∅ │ │ └── arguments: (length: 1) │ │ └── @ InterpolatedStringNode (location: (1,5)-(2,12)) + │ │ ├── flags: ∅ │ │ ├── opening_loc: ∅ │ │ ├── parts: (length: 2) │ │ │ ├── @ StringNode (location: (1,5)-(1,9)) - │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── flags: frozen │ │ │ │ ├── opening_loc: (1,5)-(1,6) = "\"" │ │ │ │ ├── content_loc: (1,6)-(1,8) = "hi" │ │ │ │ ├── closing_loc: (1,8)-(1,9) = "\"" │ │ │ │ └── unescaped: "hi" │ │ │ └── @ StringNode (location: (2,5)-(2,12)) - │ │ │ ├── flags: ∅ + │ │ │ ├── flags: frozen │ │ │ ├── opening_loc: (2,5)-(2,6) = "\"" │ │ │ ├── content_loc: (2,6)-(2,11) = "there" │ │ │ ├── closing_loc: (2,11)-(2,12) = "\"" @@ -48,7 +49,7 @@ │ ├── opening_loc: (7,0)-(7,4) = "<<-E" │ ├── content_loc: (8,0)-(11,0) = " 1 \\\r\n 2\r\n 3\r\n" │ ├── closing_loc: (11,0)-(12,0) = "E\r\n" - │ └── unescaped: " 1 2\r\n 3\r\n" + │ └── unescaped: " 1 2\n 3\n" ├── @ LocalVariableWriteNode (location: (13,0)-(15,0)) │ ├── name: :x │ ├── depth: 0 @@ -81,20 +82,21 @@ │ │ ├── flags: ∅ │ │ ├── receiver: │ │ │ @ InterpolatedStringNode (location: (17,8)-(17,14)) + │ │ │ ├── flags: ∅ │ │ │ ├── opening_loc: (17,8)-(17,14) = "<<~EOF" │ │ │ ├── parts: (length: 2) │ │ │ │ ├── @ StringNode (location: (18,0)-(19,0)) - │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── flags: frozen │ │ │ │ │ ├── opening_loc: ∅ │ │ │ │ │ ├── content_loc: (18,0)-(19,0) = "\r\n" │ │ │ │ │ ├── closing_loc: ∅ │ │ │ │ │ └── unescaped: "\n" │ │ │ │ └── @ StringNode (location: (19,0)-(20,0)) - │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── flags: frozen │ │ │ │ ├── opening_loc: ∅ │ │ │ │ ├── content_loc: (19,0)-(20,0) = " baz\r\n" │ │ │ │ ├── closing_loc: ∅ - │ │ │ │ └── unescaped: "baz\r\n" + │ │ │ │ └── unescaped: "baz\n" │ │ │ └── closing_loc: (20,0)-(21,0) = " EOF\r\n" │ │ ├── call_operator_loc: (17,14)-(17,15) = "." │ │ ├── name: :chop diff --git a/test/prism/snapshots/dstring.txt b/test/prism/snapshots/dstring.txt index ad395f8a8eb94b..3978a2f0879e50 100644 --- a/test/prism/snapshots/dstring.txt +++ b/test/prism/snapshots/dstring.txt @@ -10,10 +10,11 @@ │ ├── closing_loc: (2,5)-(2,6) = "\"" │ └── unescaped: "foo\n bar" ├── @ InterpolatedStringNode (location: (4,0)-(5,9)) + │ ├── flags: ∅ │ ├── opening_loc: (4,0)-(4,1) = "\"" │ ├── parts: (length: 2) │ │ ├── @ StringNode (location: (4,1)-(5,2)) - │ │ │ ├── flags: ∅ + │ │ │ ├── flags: frozen │ │ │ ├── opening_loc: ∅ │ │ │ ├── content_loc: (4,1)-(5,2) = "foo\n " │ │ │ ├── closing_loc: ∅ @@ -36,16 +37,17 @@ │ │ └── closing_loc: (5,7)-(5,8) = "}" │ └── closing_loc: (5,8)-(5,9) = "\"" ├── @ InterpolatedStringNode (location: (7,0)-(9,2)) + │ ├── flags: ∅ │ ├── opening_loc: ∅ │ ├── parts: (length: 2) │ │ ├── @ StringNode (location: (7,0)-(8,2)) - │ │ │ ├── flags: ∅ + │ │ │ ├── flags: frozen │ │ │ ├── opening_loc: (7,0)-(7,1) = "\"" │ │ │ ├── content_loc: (7,1)-(8,1) = "fo\no" │ │ │ ├── closing_loc: (8,1)-(8,2) = "\"" │ │ │ └── unescaped: "fo\no" │ │ └── @ StringNode (location: (8,3)-(9,2)) - │ │ ├── flags: ∅ + │ │ ├── flags: frozen │ │ ├── opening_loc: (8,3)-(8,4) = "\"" │ │ ├── content_loc: (8,4)-(9,1) = "ba\nr" │ │ ├── closing_loc: (9,1)-(9,2) = "\"" diff --git a/test/prism/snapshots/heredoc_with_comment.txt b/test/prism/snapshots/heredoc_with_comment.txt index 117fdc117a3782..f2225ca981373d 100644 --- a/test/prism/snapshots/heredoc_with_comment.txt +++ b/test/prism/snapshots/heredoc_with_comment.txt @@ -11,7 +11,7 @@ │ ├── opening_loc: (1,0)-(1,9) = "<<-TARGET" │ ├── content_loc: (2,0)-(3,0) = " content makes for an obvious error\r\n" │ ├── closing_loc: (3,0)-(3,6) = "TARGET" - │ └── unescaped: " content makes for an obvious error\r\n" + │ └── unescaped: " content makes for an obvious error\n" ├── call_operator_loc: (1,9)-(1,10) = "." ├── name: :chomp ├── message_loc: (1,10)-(1,15) = "chomp" diff --git a/test/prism/snapshots/heredocs_leading_whitespace.txt b/test/prism/snapshots/heredocs_leading_whitespace.txt index 332dfa29862d0d..dbcb0d5a19aed9 100644 --- a/test/prism/snapshots/heredocs_leading_whitespace.txt +++ b/test/prism/snapshots/heredocs_leading_whitespace.txt @@ -28,32 +28,34 @@ │ ├── closing_loc: (19,0)-(20,0) = " FOO\n" │ └── unescaped: "a\nb\n" ├── @ InterpolatedStringNode (location: (21,0)-(21,10)) + │ ├── flags: ∅ │ ├── opening_loc: (21,0)-(21,10) = "<<~' FOO'" │ ├── parts: (length: 2) │ │ ├── @ StringNode (location: (22,0)-(23,0)) - │ │ │ ├── flags: ∅ + │ │ │ ├── flags: frozen │ │ │ ├── opening_loc: ∅ │ │ │ ├── content_loc: (22,0)-(23,0) = "a\n" │ │ │ ├── closing_loc: ∅ │ │ │ └── unescaped: "a\n" │ │ └── @ StringNode (location: (23,0)-(24,0)) - │ │ ├── flags: ∅ + │ │ ├── flags: frozen │ │ ├── opening_loc: ∅ │ │ ├── content_loc: (23,0)-(24,0) = "b\n" │ │ ├── closing_loc: ∅ │ │ └── unescaped: "b\n" │ └── closing_loc: (24,0)-(25,0) = " FOO\n" └── @ InterpolatedStringNode (location: (26,0)-(26,10)) + ├── flags: ∅ ├── opening_loc: (26,0)-(26,10) = "<<~' FOO'" ├── parts: (length: 2) │ ├── @ StringNode (location: (27,0)-(28,0)) - │ │ ├── flags: ∅ + │ │ ├── flags: frozen │ │ ├── opening_loc: ∅ │ │ ├── content_loc: (27,0)-(28,0) = "a\n" │ │ ├── closing_loc: ∅ │ │ └── unescaped: "a\n" │ └── @ StringNode (location: (28,0)-(29,0)) - │ ├── flags: ∅ + │ ├── flags: frozen │ ├── opening_loc: ∅ │ ├── content_loc: (28,0)-(29,0) = "b\n" │ ├── closing_loc: ∅ diff --git a/test/prism/snapshots/heredocs_nested.txt b/test/prism/snapshots/heredocs_nested.txt index 1717aadbbce9c8..f830b028c78622 100644 --- a/test/prism/snapshots/heredocs_nested.txt +++ b/test/prism/snapshots/heredocs_nested.txt @@ -4,10 +4,11 @@ @ StatementsNode (location: (1,0)-(12,4)) └── body: (length: 2) ├── @ InterpolatedStringNode (location: (1,0)-(1,7)) + │ ├── flags: ∅ │ ├── opening_loc: (1,0)-(1,7) = "<<~RUBY" │ ├── parts: (length: 4) │ │ ├── @ StringNode (location: (2,0)-(3,0)) - │ │ │ ├── flags: ∅ + │ │ │ ├── flags: frozen │ │ │ ├── opening_loc: ∅ │ │ │ ├── content_loc: (2,0)-(3,0) = "pre\n" │ │ │ ├── closing_loc: ∅ @@ -25,19 +26,20 @@ │ │ │ │ └── unescaped: " hello\n" │ │ │ └── closing_loc: (7,0)-(7,1) = "}" │ │ ├── @ StringNode (location: (7,1)-(8,0)) - │ │ │ ├── flags: ∅ + │ │ │ ├── flags: frozen │ │ │ ├── opening_loc: ∅ │ │ │ ├── content_loc: (7,1)-(8,0) = "\n" │ │ │ ├── closing_loc: ∅ │ │ │ └── unescaped: "\n" │ │ └── @ StringNode (location: (8,0)-(9,0)) - │ │ ├── flags: ∅ + │ │ ├── flags: frozen │ │ ├── opening_loc: ∅ │ │ ├── content_loc: (8,0)-(9,0) = "post\n" │ │ ├── closing_loc: ∅ │ │ └── unescaped: "post\n" │ └── closing_loc: (9,0)-(10,0) = "RUBY\n" └── @ InterpolatedStringNode (location: (12,0)-(12,4)) + ├── flags: ∅ ├── opening_loc: (12,0)-(12,4) = "<<-A" ├── parts: (length: 2) │ ├── @ EmbeddedStatementsNode (location: (13,0)-(21,1)) @@ -46,6 +48,7 @@ │ │ │ @ StatementsNode (location: (14,0)-(14,4)) │ │ │ └── body: (length: 1) │ │ │ └── @ InterpolatedStringNode (location: (14,0)-(14,4)) + │ │ │ ├── flags: ∅ │ │ │ ├── opening_loc: (14,0)-(14,4) = "<<-B" │ │ │ ├── parts: (length: 2) │ │ │ │ ├── @ EmbeddedStatementsNode (location: (15,0)-(19,1)) @@ -54,6 +57,7 @@ │ │ │ │ │ │ @ StatementsNode (location: (16,0)-(16,4)) │ │ │ │ │ │ └── body: (length: 1) │ │ │ │ │ │ └── @ InterpolatedStringNode (location: (16,0)-(16,4)) + │ │ │ │ │ │ ├── flags: ∅ │ │ │ │ │ │ ├── opening_loc: (16,0)-(16,4) = "<<-C" │ │ │ │ │ │ ├── parts: (length: 2) │ │ │ │ │ │ │ ├── @ EmbeddedStatementsNode (location: (17,0)-(17,4)) @@ -66,7 +70,7 @@ │ │ │ │ │ │ │ │ │ └── value: 3 │ │ │ │ │ │ │ │ └── closing_loc: (17,3)-(17,4) = "}" │ │ │ │ │ │ │ └── @ StringNode (location: (17,4)-(18,0)) - │ │ │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ │ │ ├── flags: frozen │ │ │ │ │ │ │ ├── opening_loc: ∅ │ │ │ │ │ │ │ ├── content_loc: (17,4)-(18,0) = "\n" │ │ │ │ │ │ │ ├── closing_loc: ∅ @@ -74,7 +78,7 @@ │ │ │ │ │ │ └── closing_loc: (18,0)-(19,0) = "C\n" │ │ │ │ │ └── closing_loc: (19,0)-(19,1) = "}" │ │ │ │ └── @ StringNode (location: (19,1)-(20,0)) - │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── flags: frozen │ │ │ │ ├── opening_loc: ∅ │ │ │ │ ├── content_loc: (19,1)-(20,0) = "\n" │ │ │ │ ├── closing_loc: ∅ @@ -82,7 +86,7 @@ │ │ │ └── closing_loc: (20,0)-(21,0) = "B\n" │ │ └── closing_loc: (21,0)-(21,1) = "}" │ └── @ StringNode (location: (21,1)-(22,0)) - │ ├── flags: ∅ + │ ├── flags: frozen │ ├── opening_loc: ∅ │ ├── content_loc: (21,1)-(22,0) = "\n" │ ├── closing_loc: ∅ diff --git a/test/prism/snapshots/heredocs_with_ignored_newlines.txt b/test/prism/snapshots/heredocs_with_ignored_newlines.txt index cdc0b4faab9279..0f964ec294185b 100644 --- a/test/prism/snapshots/heredocs_with_ignored_newlines.txt +++ b/test/prism/snapshots/heredocs_with_ignored_newlines.txt @@ -10,58 +10,59 @@ │ ├── closing_loc: (2,0)-(3,0) = "HERE\n" │ └── unescaped: "" └── @ InterpolatedStringNode (location: (4,0)-(4,8)) + ├── flags: ∅ ├── opening_loc: (4,0)-(4,8) = "<<~THERE" ├── parts: (length: 9) │ ├── @ StringNode (location: (5,0)-(6,0)) - │ │ ├── flags: ∅ + │ │ ├── flags: frozen │ │ ├── opening_loc: ∅ │ │ ├── content_loc: (5,0)-(6,0) = " way over\n" │ │ ├── closing_loc: ∅ │ │ └── unescaped: "way over\n" │ ├── @ StringNode (location: (6,0)-(7,0)) - │ │ ├── flags: ∅ + │ │ ├── flags: frozen │ │ ├── opening_loc: ∅ │ │ ├── content_loc: (6,0)-(7,0) = " <)" + │ │ │ ├── call_operator_loc: ∅ + │ │ │ ├── name: :=~ + │ │ │ ├── message_loc: (40,15)-(40,17) = "=~" + │ │ │ ├── opening_loc: ∅ + │ │ │ ├── arguments: + │ │ │ │ @ ArgumentsNode (location: (40,18)-(40,22)) + │ │ │ │ ├── flags: ∅ + │ │ │ │ └── arguments: (length: 1) + │ │ │ │ └── @ CallNode (location: (40,18)-(40,22)) + │ │ │ │ ├── flags: variable_call, ignore_visibility + │ │ │ │ ├── receiver: ∅ + │ │ │ │ ├── call_operator_loc: ∅ + │ │ │ │ ├── name: :to_s + │ │ │ │ ├── message_loc: (40,18)-(40,22) = "to_s" + │ │ │ │ ├── opening_loc: ∅ + │ │ │ │ ├── arguments: ∅ + │ │ │ │ ├── closing_loc: ∅ + │ │ │ │ └── block: ∅ + │ │ │ ├── closing_loc: ∅ + │ │ │ └── block: ∅ + │ │ └── targets: (length: 1) + │ │ └── @ LocalVariableTargetNode (location: (40,10)-(40,11)) + │ │ ├── name: :a + │ │ └── depth: 1 + │ ├── opening_loc: (40,4)-(40,5) = "{" + │ └── closing_loc: (40,23)-(40,24) = "}" + ├── @ MatchWriteNode (location: (42,0)-(42,16)) + │ ├── call: + │ │ @ CallNode (location: (42,0)-(42,16)) + │ │ ├── flags: ∅ + │ │ ├── receiver: + │ │ │ @ RegularExpressionNode (location: (42,0)-(42,10)) + │ │ │ ├── flags: forced_us_ascii_encoding + │ │ │ ├── opening_loc: (42,0)-(42,1) = "/" + │ │ │ ├── content_loc: (42,1)-(42,9) = "(?)" + │ │ │ ├── closing_loc: (42,9)-(42,10) = "/" + │ │ │ └── unescaped: "(?)" + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :=~ + │ │ ├── message_loc: (42,11)-(42,13) = "=~" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (42,14)-(42,16)) + │ │ │ ├── flags: ∅ + │ │ │ └── arguments: (length: 1) + │ │ │ └── @ StringNode (location: (42,14)-(42,16)) + │ │ │ ├── flags: ∅ + │ │ │ ├── opening_loc: (42,14)-(42,15) = "\"" + │ │ │ ├── content_loc: (42,15)-(42,15) = "" + │ │ │ ├── closing_loc: (42,15)-(42,16) = "\"" + │ │ │ └── unescaped: "" + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ └── targets: (length: 1) + │ └── @ LocalVariableTargetNode (location: (42,4)-(42,7)) + │ ├── name: :foo + │ └── depth: 0 + ├── @ CallNode (location: (43,0)-(43,16)) + │ ├── flags: ∅ + │ ├── receiver: + │ │ @ RegularExpressionNode (location: (43,0)-(43,10)) + │ │ ├── flags: forced_us_ascii_encoding + │ │ ├── opening_loc: (43,0)-(43,1) = "/" + │ │ ├── content_loc: (43,1)-(43,9) = "(?)" + │ │ ├── closing_loc: (43,9)-(43,10) = "/" + │ │ └── unescaped: "(?)" + │ ├── call_operator_loc: ∅ + │ ├── name: :=~ + │ ├── message_loc: (43,11)-(43,13) = "=~" + │ ├── opening_loc: ∅ + │ ├── arguments: + │ │ @ ArgumentsNode (location: (43,14)-(43,16)) + │ │ ├── flags: ∅ + │ │ └── arguments: (length: 1) + │ │ └── @ StringNode (location: (43,14)-(43,16)) + │ │ ├── flags: ∅ + │ │ ├── opening_loc: (43,14)-(43,15) = "\"" + │ │ ├── content_loc: (43,15)-(43,15) = "" + │ │ ├── closing_loc: (43,15)-(43,16) = "\"" + │ │ └── unescaped: "" + │ ├── closing_loc: ∅ + │ └── block: ∅ + ├── @ CallNode (location: (45,0)-(45,16)) + │ ├── flags: ∅ + │ ├── receiver: + │ │ @ RegularExpressionNode (location: (45,0)-(45,10)) + │ │ ├── flags: forced_us_ascii_encoding + │ │ ├── opening_loc: (45,0)-(45,1) = "/" + │ │ ├── content_loc: (45,1)-(45,9) = "(?)" + │ │ ├── closing_loc: (45,9)-(45,10) = "/" + │ │ └── unescaped: "(?)" + │ ├── call_operator_loc: ∅ + │ ├── name: :=~ + │ ├── message_loc: (45,11)-(45,13) = "=~" + │ ├── opening_loc: ∅ + │ ├── arguments: + │ │ @ ArgumentsNode (location: (45,14)-(45,16)) + │ │ ├── flags: ∅ + │ │ └── arguments: (length: 1) + │ │ └── @ StringNode (location: (45,14)-(45,16)) + │ │ ├── flags: ∅ + │ │ ├── opening_loc: (45,14)-(45,15) = "\"" + │ │ ├── content_loc: (45,15)-(45,15) = "" + │ │ ├── closing_loc: (45,15)-(45,16) = "\"" + │ │ └── unescaped: "" + │ ├── closing_loc: ∅ + │ └── block: ∅ + └── @ DefNode (location: (46,0)-(46,32)) + ├── name: :foo + ├── name_loc: (46,4)-(46,7) = "foo" ├── receiver: ∅ - ├── call_operator_loc: ∅ - ├── name: :tap - ├── message_loc: (40,0)-(40,3) = "tap" - ├── opening_loc: ∅ - ├── arguments: ∅ - ├── closing_loc: ∅ - └── block: - @ BlockNode (location: (40,4)-(40,24)) - ├── locals: [] - ├── parameters: ∅ - ├── body: - │ @ StatementsNode (location: (40,6)-(40,22)) - │ └── body: (length: 1) - │ └── @ MatchWriteNode (location: (40,6)-(40,22)) - │ ├── call: - │ │ @ CallNode (location: (40,6)-(40,22)) - │ │ ├── flags: ∅ - │ │ ├── receiver: - │ │ │ @ RegularExpressionNode (location: (40,6)-(40,14)) - │ │ │ ├── flags: forced_us_ascii_encoding - │ │ │ ├── opening_loc: (40,6)-(40,7) = "/" - │ │ │ ├── content_loc: (40,7)-(40,13) = "(?)" - │ │ │ ├── closing_loc: (40,13)-(40,14) = "/" - │ │ │ └── unescaped: "(?)" - │ │ ├── call_operator_loc: ∅ - │ │ ├── name: :=~ - │ │ ├── message_loc: (40,15)-(40,17) = "=~" - │ │ ├── opening_loc: ∅ - │ │ ├── arguments: - │ │ │ @ ArgumentsNode (location: (40,18)-(40,22)) - │ │ │ ├── flags: ∅ - │ │ │ └── arguments: (length: 1) - │ │ │ └── @ CallNode (location: (40,18)-(40,22)) - │ │ │ ├── flags: variable_call, ignore_visibility - │ │ │ ├── receiver: ∅ - │ │ │ ├── call_operator_loc: ∅ - │ │ │ ├── name: :to_s - │ │ │ ├── message_loc: (40,18)-(40,22) = "to_s" - │ │ │ ├── opening_loc: ∅ - │ │ │ ├── arguments: ∅ - │ │ │ ├── closing_loc: ∅ - │ │ │ └── block: ∅ - │ │ ├── closing_loc: ∅ - │ │ └── block: ∅ - │ └── targets: (length: 1) - │ └── @ LocalVariableTargetNode (location: (40,10)-(40,11)) - │ ├── name: :a - │ └── depth: 1 - ├── opening_loc: (40,4)-(40,5) = "{" - └── closing_loc: (40,23)-(40,24) = "}" + ├── parameters: + │ @ ParametersNode (location: (46,8)-(46,12)) + │ ├── requireds: (length: 0) + │ ├── optionals: (length: 0) + │ ├── rest: ∅ + │ ├── posts: (length: 0) + │ ├── keywords: (length: 1) + │ │ └── @ RequiredKeywordParameterNode (location: (46,8)-(46,12)) + │ │ ├── flags: ∅ + │ │ ├── name: :nil + │ │ └── name_loc: (46,8)-(46,12) = "nil:" + │ ├── keyword_rest: ∅ + │ └── block: ∅ + ├── body: + │ @ StatementsNode (location: (46,16)-(46,32)) + │ └── body: (length: 1) + │ └── @ MatchWriteNode (location: (46,16)-(46,32)) + │ ├── call: + │ │ @ CallNode (location: (46,16)-(46,32)) + │ │ ├── flags: ∅ + │ │ ├── receiver: + │ │ │ @ RegularExpressionNode (location: (46,16)-(46,26)) + │ │ │ ├── flags: forced_us_ascii_encoding + │ │ │ ├── opening_loc: (46,16)-(46,17) = "/" + │ │ │ ├── content_loc: (46,17)-(46,25) = "(?)" + │ │ │ ├── closing_loc: (46,25)-(46,26) = "/" + │ │ │ └── unescaped: "(?)" + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :=~ + │ │ ├── message_loc: (46,27)-(46,29) = "=~" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (46,30)-(46,32)) + │ │ │ ├── flags: ∅ + │ │ │ └── arguments: (length: 1) + │ │ │ └── @ StringNode (location: (46,30)-(46,32)) + │ │ │ ├── flags: ∅ + │ │ │ ├── opening_loc: (46,30)-(46,31) = "\"" + │ │ │ ├── content_loc: (46,31)-(46,31) = "" + │ │ │ ├── closing_loc: (46,31)-(46,32) = "\"" + │ │ │ └── unescaped: "" + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ + │ └── targets: (length: 1) + │ └── @ LocalVariableTargetNode (location: (46,20)-(46,23)) + │ ├── name: :nil + │ └── depth: 0 + ├── locals: [:nil] + ├── def_keyword_loc: (46,0)-(46,3) = "def" + ├── operator_loc: ∅ + ├── lparen_loc: (46,7)-(46,8) = "(" + ├── rparen_loc: (46,12)-(46,13) = ")" + ├── equal_loc: (46,14)-(46,15) = "=" + └── end_keyword_loc: ∅ diff --git a/test/prism/snapshots/seattlerb/difficult0_.txt b/test/prism/snapshots/seattlerb/difficult0_.txt index 251a80125dee68..8ba30ccf85d247 100644 --- a/test/prism/snapshots/seattlerb/difficult0_.txt +++ b/test/prism/snapshots/seattlerb/difficult0_.txt @@ -35,16 +35,17 @@ │ │ │ ├── flags: ∅ │ │ │ └── arguments: (length: 1) │ │ │ └── @ InterpolatedStringNode (location: (1,9)-(4,4)) + │ │ │ ├── flags: ∅ │ │ │ ├── opening_loc: (1,9)-(1,10) = "'" │ │ │ ├── parts: (length: 2) │ │ │ │ ├── @ StringNode (location: (1,10)-(2,0)) - │ │ │ │ │ ├── flags: ∅ + │ │ │ │ │ ├── flags: frozen │ │ │ │ │ ├── opening_loc: ∅ │ │ │ │ │ ├── content_loc: (1,10)-(2,0) = "b\n" │ │ │ │ │ ├── closing_loc: ∅ │ │ │ │ │ └── unescaped: "b\n" │ │ │ │ └── @ StringNode (location: (4,0)-(4,3)) - │ │ │ │ ├── flags: ∅ + │ │ │ │ ├── flags: frozen │ │ │ │ ├── opening_loc: ∅ │ │ │ │ ├── content_loc: (4,0)-(4,3) = " c" │ │ │ │ ├── closing_loc: ∅ diff --git a/test/prism/snapshots/seattlerb/dstr_evstr.txt b/test/prism/snapshots/seattlerb/dstr_evstr.txt index ce692238cdf190..8d771e88c2d029 100644 --- a/test/prism/snapshots/seattlerb/dstr_evstr.txt +++ b/test/prism/snapshots/seattlerb/dstr_evstr.txt @@ -4,6 +4,7 @@ @ StatementsNode (location: (1,0)-(1,12)) └── body: (length: 1) └── @ InterpolatedStringNode (location: (1,0)-(1,12)) + ├── flags: ∅ ├── opening_loc: (1,0)-(1,1) = "\"" ├── parts: (length: 2) │ ├── @ EmbeddedStatementsNode (location: (1,1)-(1,7)) diff --git a/test/prism/snapshots/seattlerb/dstr_lex_state.txt b/test/prism/snapshots/seattlerb/dstr_lex_state.txt index 0e7f8ff83d99e3..c4c0ef0437d2fd 100644 --- a/test/prism/snapshots/seattlerb/dstr_lex_state.txt +++ b/test/prism/snapshots/seattlerb/dstr_lex_state.txt @@ -4,6 +4,7 @@ @ StatementsNode (location: (1,0)-(1,8)) └── body: (length: 1) └── @ InterpolatedStringNode (location: (1,0)-(1,8)) + ├── flags: ∅ ├── opening_loc: (1,0)-(1,1) = "\"" ├── parts: (length: 1) │ └── @ EmbeddedStatementsNode (location: (1,1)-(1,7)) diff --git a/test/prism/snapshots/seattlerb/dstr_str.txt b/test/prism/snapshots/seattlerb/dstr_str.txt index 42bd37a1ac9527..70b5752ce375d4 100644 --- a/test/prism/snapshots/seattlerb/dstr_str.txt +++ b/test/prism/snapshots/seattlerb/dstr_str.txt @@ -4,6 +4,7 @@ @ StatementsNode (location: (1,0)-(1,10)) └── body: (length: 1) └── @ InterpolatedStringNode (location: (1,0)-(1,10)) + ├── flags: ∅ ├── opening_loc: (1,0)-(1,1) = "\"" ├── parts: (length: 2) │ ├── @ EmbeddedStatementsNode (location: (1,1)-(1,7)) @@ -19,7 +20,7 @@ │ │ │ └── unescaped: "a" │ │ └── closing_loc: (1,6)-(1,7) = "}" │ └── @ StringNode (location: (1,7)-(1,9)) - │ ├── flags: ∅ + │ ├── flags: frozen │ ├── opening_loc: ∅ │ ├── content_loc: (1,7)-(1,9) = " b" │ ├── closing_loc: ∅ diff --git a/test/prism/snapshots/seattlerb/evstr_evstr.txt b/test/prism/snapshots/seattlerb/evstr_evstr.txt index ef7545c2f2573f..9c801299f812e4 100644 --- a/test/prism/snapshots/seattlerb/evstr_evstr.txt +++ b/test/prism/snapshots/seattlerb/evstr_evstr.txt @@ -4,6 +4,7 @@ @ StatementsNode (location: (1,0)-(1,10)) └── body: (length: 1) └── @ InterpolatedStringNode (location: (1,0)-(1,10)) + ├── flags: ∅ ├── opening_loc: (1,0)-(1,1) = "\"" ├── parts: (length: 2) │ ├── @ EmbeddedStatementsNode (location: (1,1)-(1,5)) diff --git a/test/prism/snapshots/seattlerb/evstr_str.txt b/test/prism/snapshots/seattlerb/evstr_str.txt index 214491d6cf0b6e..54319e613c5e97 100644 --- a/test/prism/snapshots/seattlerb/evstr_str.txt +++ b/test/prism/snapshots/seattlerb/evstr_str.txt @@ -4,6 +4,7 @@ @ StatementsNode (location: (1,0)-(1,8)) └── body: (length: 1) └── @ InterpolatedStringNode (location: (1,0)-(1,8)) + ├── flags: ∅ ├── opening_loc: (1,0)-(1,1) = "\"" ├── parts: (length: 2) │ ├── @ EmbeddedStatementsNode (location: (1,1)-(1,5)) @@ -23,7 +24,7 @@ │ │ │ └── block: ∅ │ │ └── closing_loc: (1,4)-(1,5) = "}" │ └── @ StringNode (location: (1,5)-(1,7)) - │ ├── flags: ∅ + │ ├── flags: frozen │ ├── opening_loc: ∅ │ ├── content_loc: (1,5)-(1,7) = " b" │ ├── closing_loc: ∅ diff --git a/test/prism/snapshots/seattlerb/heredoc__backslash_dos_format.txt b/test/prism/snapshots/seattlerb/heredoc__backslash_dos_format.txt index 6ba437e36a907b..353e4c6964ceec 100644 --- a/test/prism/snapshots/seattlerb/heredoc__backslash_dos_format.txt +++ b/test/prism/snapshots/seattlerb/heredoc__backslash_dos_format.txt @@ -13,5 +13,5 @@ │ ├── opening_loc: (1,6)-(1,12) = "<<-XXX" │ ├── content_loc: (2,0)-(4,0) = "before\\\r\nafter\r\n" │ ├── closing_loc: (4,0)-(5,0) = "XXX\r\n" - │ └── unescaped: "beforeafter\r\n" + │ └── unescaped: "beforeafter\n" └── operator_loc: (1,4)-(1,5) = "=" diff --git a/test/prism/snapshots/seattlerb/heredoc_nested.txt b/test/prism/snapshots/seattlerb/heredoc_nested.txt index 4734068ba01cca..26d533a33df07b 100644 --- a/test/prism/snapshots/seattlerb/heredoc_nested.txt +++ b/test/prism/snapshots/seattlerb/heredoc_nested.txt @@ -7,6 +7,7 @@ ├── flags: ∅ ├── elements: (length: 2) │ ├── @ InterpolatedStringNode (location: (1,1)-(1,4)) + │ │ ├── flags: ∅ │ │ ├── opening_loc: (1,1)-(1,4) = "< 1, #{right} => 2 } RUBY - warnings << Prism.parse(<<~RUBY, filepath: __FILE__).warnings.first + warnings << (Prism.parse(<<~RUBY, filepath: __FILE__).warnings.first || NullWarning.new) case foo when #{left} when #{right} @@ -79,7 +86,7 @@ def assert_warning(left, right = left, message = left) end def refute_warning(left, right) - assert_empty parse_warnings(left, right).compact + assert_empty parse_warnings(left, right).grep_v(NullWarning) end end end diff --git a/test/reline/helper.rb b/test/reline/helper.rb index 2180fdf5969abe..f2f3421ded0bf4 100644 --- a/test/reline/helper.rb +++ b/test/reline/helper.rb @@ -78,14 +78,6 @@ def test_rubybin end end -def start_pasting - Reline::GeneralIO.start_pasting -end - -def finish_pasting - Reline::GeneralIO.finish_pasting -end - class Reline::TestCase < Test::Unit::TestCase private def convert_str(input, options = {}, normalized = nil) return nil if input.nil? @@ -136,9 +128,14 @@ def input_raw_keys(input, convert = true) end end - def assert_line(expected) - expected = convert_str(expected) - assert_equal(expected, @line_editor.line) + def assert_line_around_cursor(before, after) + before = convert_str(before) + after = convert_str(after) + line = @line_editor.line + byte_pointer = @line_editor.instance_variable_get(:@byte_pointer) + actual_before = line.byteslice(0, byte_pointer) + actual_after = line.byteslice(byte_pointer..) + assert_equal([before, after], [actual_before, actual_after]) end def assert_byte_pointer_size(expected) @@ -153,25 +150,6 @@ def assert_byte_pointer_size(expected) EOM end - def assert_cursor(expected) - # This test satisfies nothing because there is no `@cursor` anymore - # Test editor_cursor_position instead - cursor_x = @line_editor.instance_eval do - line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer) - Reline::Unicode.calculate_width(line_before_cursor) - end - assert_equal(expected, cursor_x) - end - - def assert_cursor_max(expected) - # This test satisfies nothing because there is no `@cursor_max` anymore - cursor_max = @line_editor.instance_eval do - line = whole_lines[@line_index] - Reline::Unicode.calculate_width(line) - end - assert_equal(expected, cursor_max) - end - def assert_line_index(expected) assert_equal(expected, @line_editor.instance_variable_get(:@line_index)) end diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index d5ddd40f8526cf..2311af0f5aad26 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -19,418 +19,238 @@ def teardown def test_ed_insert_one input_keys('a') - assert_line('a') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(1) + assert_line_around_cursor('a', '') end def test_ed_insert_two input_keys('ab') - assert_line('ab') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('ab', '') end def test_ed_insert_mbchar_one input_keys('か') - assert_line('か') - assert_byte_pointer_size('か') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('か', '') end def test_ed_insert_mbchar_two input_keys('かき') - assert_line('かき') - assert_byte_pointer_size('かき') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('かき', '') end def test_ed_insert_for_mbchar_by_plural_code_points input_keys("か\u3099") - assert_line("か\u3099") - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor("か\u3099", '') end def test_ed_insert_for_plural_mbchar_by_plural_code_points input_keys("か\u3099き\u3099") - assert_line("か\u3099き\u3099") - assert_byte_pointer_size("か\u3099き\u3099") - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor("か\u3099き\u3099", '') end def test_move_next_and_prev input_keys('abd') - assert_byte_pointer_size('abd') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('abd', '') input_keys("\C-b", false) - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(3) + assert_line_around_cursor('ab', 'd') input_keys("\C-b", false) - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(3) + assert_line_around_cursor('a', 'bd') input_keys("\C-f", false) - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(3) + assert_line_around_cursor('ab', 'd') input_keys('c') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(4) - assert_line('abcd') + assert_line_around_cursor('abc', 'd') end def test_move_next_and_prev_for_mbchar input_keys('かきけ') - assert_byte_pointer_size('かきけ') - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor('かきけ', '') input_keys("\C-b", false) - assert_byte_pointer_size('かき') - assert_cursor(4) - assert_cursor_max(6) + assert_line_around_cursor('かき', 'け') input_keys("\C-b", false) - assert_byte_pointer_size('か') - assert_cursor(2) - assert_cursor_max(6) + assert_line_around_cursor('か', 'きけ') input_keys("\C-f", false) - assert_byte_pointer_size('かき') - assert_cursor(4) - assert_cursor_max(6) + assert_line_around_cursor('かき', 'け') input_keys('く') - assert_byte_pointer_size('かきく') - assert_cursor(6) - assert_cursor_max(8) - assert_line('かきくけ') + assert_line_around_cursor('かきく', 'け') end def test_move_next_and_prev_for_mbchar_by_plural_code_points input_keys("か\u3099き\u3099け\u3099") - assert_byte_pointer_size("か\u3099き\u3099け\u3099") - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099き\u3099け\u3099", '') input_keys("\C-b", false) - assert_byte_pointer_size("か\u3099き\u3099") - assert_cursor(4) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099き\u3099", "け\u3099") input_keys("\C-b", false) - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099", "き\u3099け\u3099") input_keys("\C-f", false) - assert_byte_pointer_size("か\u3099き\u3099") - assert_cursor(4) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099き\u3099", "け\u3099") input_keys("く\u3099") - assert_byte_pointer_size("か\u3099き\u3099く\u3099") - assert_cursor(6) - assert_cursor_max(8) - assert_line("か\u3099き\u3099く\u3099け\u3099") + assert_line_around_cursor("か\u3099き\u3099く\u3099", "け\u3099") end def test_move_to_beg_end input_keys('bcd') - assert_byte_pointer_size('bcd') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('bcd', '') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) + assert_line_around_cursor('', 'bcd') input_keys('a') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(4) + assert_line_around_cursor('a', 'bcd') input_keys("\C-e", false) - assert_byte_pointer_size('abcd') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('abcd', '') input_keys('e') - assert_byte_pointer_size('abcde') - assert_cursor(5) - assert_cursor_max(5) - assert_line('abcde') + assert_line_around_cursor('abcde', '') end def test_ed_newline_with_cr input_keys('ab') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('ab', '') refute(@line_editor.finished?) input_keys("\C-m", false) - assert_line('ab') + assert_line_around_cursor('ab', '') assert(@line_editor.finished?) end def test_ed_newline_with_lf input_keys('ab') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('ab', '') refute(@line_editor.finished?) input_keys("\C-j", false) - assert_line('ab') + assert_line_around_cursor('ab', '') assert(@line_editor.finished?) end def test_em_delete_prev_char input_keys('ab') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('ab', '') input_keys("\C-h", false) - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(1) - assert_line('a') + assert_line_around_cursor('a', '') end def test_em_delete_prev_char_for_mbchar input_keys('かき') - assert_byte_pointer_size('かき') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('かき', '') input_keys("\C-h", false) - assert_byte_pointer_size('か') - assert_cursor(2) - assert_cursor_max(2) - assert_line('か') + assert_line_around_cursor('か', '') end def test_em_delete_prev_char_for_mbchar_by_plural_code_points input_keys("か\u3099き\u3099") - assert_byte_pointer_size("か\u3099き\u3099") - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor("か\u3099き\u3099", '') input_keys("\C-h", false) - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(2) - assert_line("か\u3099") + assert_line_around_cursor("か\u3099", '') end def test_ed_quoted_insert input_keys("ab\C-v\C-acd") - assert_line("ab\C-acd") - assert_byte_pointer_size("ab\C-acd") - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor("ab\C-acd", '') input_keys("\C-q\C-b") - assert_line("ab\C-acd\C-b") - assert_byte_pointer_size("ab\C-acd\C-b") - assert_cursor(8) - assert_cursor_max(8) + assert_line_around_cursor("ab\C-acd\C-b", '') end def test_ed_kill_line input_keys("\C-k", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys('abc') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('abc', '') input_keys("\C-k", false) - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(3) - assert_line('abc') + assert_line_around_cursor('abc', '') input_keys("\C-b\C-k", false) - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) - assert_line('ab') + assert_line_around_cursor('ab', '') end def test_em_kill_line @line_editor.input_key(Reline::Key.new(:em_kill_line, :em_kill_line, false)) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys('abc') @line_editor.input_key(Reline::Key.new(:em_kill_line, :em_kill_line, false)) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys('abc') input_keys("\C-b", false) @line_editor.input_key(Reline::Key.new(:em_kill_line, :em_kill_line, false)) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys('abc') input_keys("\C-a", false) @line_editor.input_key(Reline::Key.new(:em_kill_line, :em_kill_line, false)) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_ed_move_to_beg input_keys('abd') - assert_byte_pointer_size('abd') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('abd', '') input_keys("\C-b", false) - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(3) + assert_line_around_cursor('ab', 'd') input_keys('c') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(4) + assert_line_around_cursor('abc', 'd') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(4) + assert_line_around_cursor('', 'abcd') input_keys('012') - assert_byte_pointer_size('012') - assert_cursor(3) - assert_cursor_max(7) - assert_line('012abcd') + assert_line_around_cursor('012', 'abcd') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(7) + assert_line_around_cursor('', '012abcd') input_keys('ABC') - assert_byte_pointer_size('ABC') - assert_cursor(3) - assert_cursor_max(10) - assert_line('ABC012abcd') + assert_line_around_cursor('ABC', '012abcd') input_keys("\C-f" * 10 + "\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(10) + assert_line_around_cursor('', 'ABC012abcd') input_keys('a') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(11) - assert_line('aABC012abcd') + assert_line_around_cursor('a', 'ABC012abcd') end def test_ed_move_to_beg_with_blank input_keys(' abc') - assert_byte_pointer_size(' abc') - assert_cursor(5) - assert_cursor_max(5) + assert_line_around_cursor(' abc', '') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) + assert_line_around_cursor('', ' abc') end def test_ed_move_to_end input_keys('abd') - assert_byte_pointer_size('abd') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('abd', '') input_keys("\C-b", false) - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(3) + assert_line_around_cursor('ab', 'd') input_keys('c') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(4) + assert_line_around_cursor('abc', 'd') input_keys("\C-e", false) - assert_byte_pointer_size('abcd') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('abcd', '') input_keys('012') - assert_byte_pointer_size('abcd012') - assert_cursor(7) - assert_cursor_max(7) - assert_line('abcd012') + assert_line_around_cursor('abcd012', '') input_keys("\C-e", false) - assert_byte_pointer_size('abcd012') - assert_cursor(7) - assert_cursor_max(7) + assert_line_around_cursor('abcd012', '') input_keys('ABC') - assert_byte_pointer_size('abcd012ABC') - assert_cursor(10) - assert_cursor_max(10) - assert_line('abcd012ABC') + assert_line_around_cursor('abcd012ABC', '') input_keys("\C-b" * 10 + "\C-e", false) - assert_byte_pointer_size('abcd012ABC') - assert_cursor(10) - assert_cursor_max(10) + assert_line_around_cursor('abcd012ABC', '') input_keys('a') - assert_byte_pointer_size('abcd012ABCa') - assert_cursor(11) - assert_cursor_max(11) - assert_line('abcd012ABCa') + assert_line_around_cursor('abcd012ABCa', '') end def test_em_delete input_keys('ab') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('ab', '') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(2) + assert_line_around_cursor('', 'ab') input_keys("\C-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(1) - assert_line('b') + assert_line_around_cursor('', 'b') end def test_em_delete_for_mbchar input_keys('かき') - assert_byte_pointer_size('かき') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('かき', '') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(4) + assert_line_around_cursor('', 'かき') input_keys("\C-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(2) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(2) - assert_line('き') + assert_line_around_cursor('', 'き') end def test_em_delete_for_mbchar_by_plural_code_points input_keys("か\u3099き\u3099") - assert_byte_pointer_size("か\u3099き\u3099") - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor("か\u3099き\u3099", '') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(4) + assert_line_around_cursor('', "か\u3099き\u3099") input_keys("\C-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(2) - assert_line("き\u3099") + assert_line_around_cursor('', "き\u3099") end def test_em_delete_ends_editing input_keys("\C-d") # quit from inputing - assert_line(nil) + assert_nil(@line_editor.line) assert(@line_editor.finished?) end @@ -444,834 +264,454 @@ def test_ed_clear_screen_with_inputed input_keys('abc') input_keys("\C-b", false) refute(@line_editor.instance_variable_get(:@cleared)) - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(3) + assert_line_around_cursor('ab', 'c') input_keys("\C-l", false) assert(@line_editor.instance_variable_get(:@cleared)) - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(3) - assert_line('abc') + assert_line_around_cursor('ab', 'c') end def test_key_delete input_keys('abc') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('abc', '') @line_editor.input_key(Reline::Key.new(:key_delete, :key_delete, false)) - assert_cursor(3) - assert_cursor_max(3) - assert_line('abc') + assert_line_around_cursor('abc', '') end def test_key_delete_does_not_end_editing @line_editor.input_key(Reline::Key.new(:key_delete, :key_delete, false)) - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') refute(@line_editor.finished?) end def test_key_delete_preserves_cursor input_keys('abc') input_keys("\C-b", false) - assert_cursor(2) - assert_cursor_max(3) + assert_line_around_cursor('ab', 'c') @line_editor.input_key(Reline::Key.new(:key_delete, :key_delete, false)) - assert_cursor(2) - assert_cursor_max(2) - assert_line('ab') + assert_line_around_cursor('ab', '') end def test_em_next_word - assert_byte_pointer_size('') - assert_cursor(0) + assert_line_around_cursor('', '') input_keys('abc def{bbb}ccc') input_keys("\C-a\M-F", false) - assert_byte_pointer_size('abc') - assert_cursor(3) + assert_line_around_cursor('abc', ' def{bbb}ccc') input_keys("\M-F", false) - assert_byte_pointer_size('abc def') - assert_cursor(7) + assert_line_around_cursor('abc def', '{bbb}ccc') input_keys("\M-F", false) - assert_byte_pointer_size('abc def{bbb') - assert_cursor(11) + assert_line_around_cursor('abc def{bbb', '}ccc') input_keys("\M-F", false) - assert_byte_pointer_size('abc def{bbb}ccc') - assert_cursor(15) + assert_line_around_cursor('abc def{bbb}ccc', '') input_keys("\M-F", false) - assert_byte_pointer_size('abc def{bbb}ccc') - assert_cursor(15) + assert_line_around_cursor('abc def{bbb}ccc', '') end def test_em_next_word_for_mbchar - assert_cursor(0) + assert_line_around_cursor('', '') input_keys('あいう かきく{さしす}たちつ') input_keys("\C-a\M-F", false) - assert_byte_pointer_size('あいう') - assert_cursor(6) + assert_line_around_cursor('あいう', ' かきく{さしす}たちつ') input_keys("\M-F", false) - assert_byte_pointer_size('あいう かきく') - assert_cursor(13) + assert_line_around_cursor('あいう かきく', '{さしす}たちつ') input_keys("\M-F", false) - assert_byte_pointer_size('あいう かきく{さしす') - assert_cursor(20) + assert_line_around_cursor('あいう かきく{さしす', '}たちつ') input_keys("\M-F", false) - assert_byte_pointer_size('あいう かきく{さしす}たちつ') - assert_cursor(27) + assert_line_around_cursor('あいう かきく{さしす}たちつ', '') input_keys("\M-F", false) - assert_byte_pointer_size('あいう かきく{さしす}たちつ') - assert_cursor(27) + assert_line_around_cursor('あいう かきく{さしす}たちつ', '') end def test_em_next_word_for_mbchar_by_plural_code_points - assert_cursor(0) + assert_line_around_cursor("", "") input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") input_keys("\C-a\M-F", false) - assert_byte_pointer_size("あいう") - assert_cursor(6) + assert_line_around_cursor("あいう", " か\u3099き\u3099く\u3099{さしす}たちつ") input_keys("\M-F", false) - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099") - assert_cursor(13) + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099", "{さしす}たちつ") input_keys("\M-F", false) - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす") - assert_cursor(20) + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす", "}たちつ") input_keys("\M-F", false) - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_cursor(27) + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "") input_keys("\M-F", false) - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_cursor(27) + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "") end def test_em_prev_word input_keys('abc def{bbb}ccc') - assert_byte_pointer_size('abc def{bbb}ccc') - assert_cursor(15) + assert_line_around_cursor('abc def{bbb}ccc', '') input_keys("\M-B", false) - assert_byte_pointer_size('abc def{bbb}') - assert_cursor(12) + assert_line_around_cursor('abc def{bbb}', 'ccc') input_keys("\M-B", false) - assert_byte_pointer_size('abc def{') - assert_cursor(8) + assert_line_around_cursor('abc def{', 'bbb}ccc') input_keys("\M-B", false) - assert_byte_pointer_size('abc ') - assert_cursor(4) + assert_line_around_cursor('abc ', 'def{bbb}ccc') input_keys("\M-B", false) - assert_byte_pointer_size('') - assert_cursor(0) + assert_line_around_cursor('', 'abc def{bbb}ccc') input_keys("\M-B", false) - assert_byte_pointer_size('') - assert_cursor(0) + assert_line_around_cursor('', 'abc def{bbb}ccc') end def test_em_prev_word_for_mbchar input_keys('あいう かきく{さしす}たちつ') - assert_byte_pointer_size('あいう かきく{さしす}たちつ') - assert_cursor(27) + assert_line_around_cursor('あいう かきく{さしす}たちつ', '') input_keys("\M-B", false) - assert_byte_pointer_size('あいう かきく{さしす}') - assert_cursor(21) + assert_line_around_cursor('あいう かきく{さしす}', 'たちつ') input_keys("\M-B", false) - assert_byte_pointer_size('あいう かきく{') - assert_cursor(14) + assert_line_around_cursor('あいう かきく{', 'さしす}たちつ') input_keys("\M-B", false) - assert_byte_pointer_size('あいう ') - assert_cursor(7) + assert_line_around_cursor('あいう ', 'かきく{さしす}たちつ') input_keys("\M-B", false) - assert_byte_pointer_size('') - assert_cursor(0) + assert_line_around_cursor('', 'あいう かきく{さしす}たちつ') input_keys("\M-B", false) - assert_byte_pointer_size('') - assert_cursor(0) + assert_line_around_cursor('', 'あいう かきく{さしす}たちつ') end def test_em_prev_word_for_mbchar_by_plural_code_points input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_cursor(27) + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", "") input_keys("\M-B", false) - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}") - assert_cursor(21) + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", "たちつ") input_keys("\M-B", false) - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{") - assert_cursor(14) + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", "さしす}たちつ") input_keys("\M-B", false) - assert_byte_pointer_size('あいう ') - assert_cursor(7) + assert_line_around_cursor("あいう ", "か\u3099き\u3099く\u3099{さしす}たちつ") input_keys("\M-B", false) - assert_byte_pointer_size('') - assert_cursor(0) + assert_line_around_cursor("", "あいう か\u3099き\u3099く\u3099{さしす}たちつ") input_keys("\M-B", false) - assert_byte_pointer_size('') - assert_cursor(0) + assert_line_around_cursor("", "あいう か\u3099き\u3099く\u3099{さしす}たちつ") end def test_em_delete_next_word input_keys('abc def{bbb}ccc') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(15) + assert_line_around_cursor('', 'abc def{bbb}ccc') input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(12) - assert_line(' def{bbb}ccc') + assert_line_around_cursor('', ' def{bbb}ccc') input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(8) - assert_line('{bbb}ccc') + assert_line_around_cursor('', '{bbb}ccc') input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(4) - assert_line('}ccc') + assert_line_around_cursor('', '}ccc') input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_em_delete_next_word_for_mbchar input_keys('あいう かきく{さしす}たちつ') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(27) + assert_line_around_cursor('', 'あいう かきく{さしす}たちつ') input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(21) - assert_line(' かきく{さしす}たちつ') + assert_line_around_cursor('', ' かきく{さしす}たちつ') input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(14) - assert_line('{さしす}たちつ') + assert_line_around_cursor('', '{さしす}たちつ') input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(7) - assert_line('}たちつ') + assert_line_around_cursor('', '}たちつ') input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_em_delete_next_word_for_mbchar_by_plural_code_points input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(27) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(27) + assert_line_around_cursor('', "あいう か\u3099き\u3099く\u3099{さしす}たちつ") input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(21) - assert_line(" か\u3099き\u3099く\u3099{さしす}たちつ") + assert_line_around_cursor('', " か\u3099き\u3099く\u3099{さしす}たちつ") input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(14) - assert_line('{さしす}たちつ') + assert_line_around_cursor('', '{さしす}たちつ') input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(7) - assert_line('}たちつ') + assert_line_around_cursor('', '}たちつ') input_keys("\M-d", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_ed_delete_prev_word input_keys('abc def{bbb}ccc') - assert_byte_pointer_size('abc def{bbb}ccc') - assert_cursor(15) - assert_cursor_max(15) + assert_line_around_cursor('abc def{bbb}ccc', '') input_keys("\M-\C-H", false) - assert_byte_pointer_size('abc def{bbb}') - assert_cursor(12) - assert_cursor_max(12) - assert_line('abc def{bbb}') + assert_line_around_cursor('abc def{bbb}', '') input_keys("\M-\C-H", false) - assert_byte_pointer_size('abc def{') - assert_cursor(8) - assert_cursor_max(8) - assert_line('abc def{') + assert_line_around_cursor('abc def{', '') input_keys("\M-\C-H", false) - assert_byte_pointer_size('abc ') - assert_cursor(4) - assert_cursor_max(4) - assert_line('abc ') + assert_line_around_cursor('abc ', '') input_keys("\M-\C-H", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_ed_delete_prev_word_for_mbchar input_keys('あいう かきく{さしす}たちつ') - assert_byte_pointer_size('あいう かきく{さしす}たちつ') - assert_cursor(27) - assert_cursor_max(27) + assert_line_around_cursor('あいう かきく{さしす}たちつ', '') input_keys("\M-\C-H", false) - assert_byte_pointer_size('あいう かきく{さしす}') - assert_cursor(21) - assert_cursor_max(21) - assert_line('あいう かきく{さしす}') + assert_line_around_cursor('あいう かきく{さしす}', '') input_keys("\M-\C-H", false) - assert_byte_pointer_size('あいう かきく{') - assert_cursor(14) - assert_cursor_max(14) - assert_line('あいう かきく{') + assert_line_around_cursor('あいう かきく{', '') input_keys("\M-\C-H", false) - assert_byte_pointer_size('あいう ') - assert_cursor(7) - assert_cursor_max(7) - assert_line('あいう ') + assert_line_around_cursor('あいう ', '') input_keys("\M-\C-H", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_ed_delete_prev_word_for_mbchar_by_plural_code_points input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_cursor(27) - assert_cursor_max(27) + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", '') input_keys("\M-\C-H", false) - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}") - assert_cursor(21) - assert_cursor_max(21) - assert_line("あいう か\u3099き\u3099く\u3099{さしす}") + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", '') input_keys("\M-\C-H", false) - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{") - assert_cursor(14) - assert_cursor_max(14) - assert_line("あいう か\u3099き\u3099く\u3099{") + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", '') input_keys("\M-\C-H", false) - assert_byte_pointer_size("あいう ") - assert_cursor(7) - assert_cursor_max(7) - assert_line('あいう ') + assert_line_around_cursor('あいう ', '') input_keys("\M-\C-H", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_ed_transpose_chars input_keys('abc') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) + assert_line_around_cursor('', 'abc') input_keys("\C-t", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) - assert_line('abc') + assert_line_around_cursor('', 'abc') input_keys("\C-f\C-t", false) - assert_byte_pointer_size('ba') - assert_cursor(2) - assert_cursor_max(3) - assert_line('bac') + assert_line_around_cursor('ba', 'c') input_keys("\C-t", false) - assert_byte_pointer_size('bca') - assert_cursor(3) - assert_cursor_max(3) - assert_line('bca') + assert_line_around_cursor('bca', '') input_keys("\C-t", false) - assert_byte_pointer_size('bac') - assert_cursor(3) - assert_cursor_max(3) - assert_line('bac') + assert_line_around_cursor('bac', '') end def test_ed_transpose_chars_for_mbchar input_keys('あかさ') input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', 'あかさ') input_keys("\C-t", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) - assert_line('あかさ') + assert_line_around_cursor('', 'あかさ') input_keys("\C-f\C-t", false) - assert_byte_pointer_size('かあ') - assert_cursor(4) - assert_cursor_max(6) - assert_line('かあさ') + assert_line_around_cursor('かあ', 'さ') input_keys("\C-t", false) - assert_byte_pointer_size('かさあ') - assert_cursor(6) - assert_cursor_max(6) - assert_line('かさあ') + assert_line_around_cursor('かさあ', '') input_keys("\C-t", false) - assert_byte_pointer_size('かあさ') - assert_cursor(6) - assert_cursor_max(6) - assert_line('かあさ') + assert_line_around_cursor('かあさ', '') end def test_ed_transpose_chars_for_mbchar_by_plural_code_points input_keys("あか\u3099さ") input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', "あか\u3099さ") input_keys("\C-t", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) - assert_line("あか\u3099さ") + assert_line_around_cursor('', "あか\u3099さ") input_keys("\C-f\C-t", false) - assert_byte_pointer_size("か\u3099あ") - assert_cursor(4) - assert_cursor_max(6) - assert_line("か\u3099あさ") + assert_line_around_cursor("か\u3099あ", 'さ') input_keys("\C-t", false) - assert_byte_pointer_size("か\u3099さあ") - assert_cursor(6) - assert_cursor_max(6) - assert_line("か\u3099さあ") + assert_line_around_cursor("か\u3099さあ", '') input_keys("\C-t", false) - assert_byte_pointer_size("か\u3099あさ") - assert_cursor(6) - assert_cursor_max(6) - assert_line("か\u3099あさ") + assert_line_around_cursor("か\u3099あさ", '') end def test_ed_transpose_words input_keys('abc def') - assert_line('abc def') - assert_byte_pointer_size('abc def') - assert_cursor(7) - assert_cursor_max(7) + assert_line_around_cursor('abc def', '') input_keys("\M-t", false) - assert_line('def abc') - assert_byte_pointer_size('def abc') - assert_cursor(7) - assert_cursor_max(7) + assert_line_around_cursor('def abc', '') input_keys("\C-a\C-k", false) input_keys(' abc def ') input_keys("\C-b" * 4, false) - assert_line(' abc def ') - assert_byte_pointer_size(' abc de') - assert_cursor(8) - assert_cursor_max(12) + assert_line_around_cursor(' abc de', 'f ') input_keys("\M-t", false) - assert_line(' def abc ') - assert_byte_pointer_size(' def abc') - assert_cursor(9) - assert_cursor_max(12) + assert_line_around_cursor(' def abc', ' ') input_keys("\C-a\C-k", false) input_keys(' abc def ') input_keys("\C-b" * 6, false) - assert_line(' abc def ') - assert_byte_pointer_size(' abc ') - assert_cursor(6) - assert_cursor_max(12) + assert_line_around_cursor(' abc ', 'def ') input_keys("\M-t", false) - assert_line(' def abc ') - assert_byte_pointer_size(' def abc') - assert_cursor(9) - assert_cursor_max(12) + assert_line_around_cursor(' def abc', ' ') input_keys("\M-t", false) - assert_line(' abc def') - assert_byte_pointer_size(' abc def') - assert_cursor(12) - assert_cursor_max(12) + assert_line_around_cursor(' abc def', '') end def test_ed_transpose_words_for_mbchar input_keys('あいう かきく') - assert_line('あいう かきく') - assert_byte_pointer_size('あいう かきく') - assert_cursor(13) - assert_cursor_max(13) + assert_line_around_cursor('あいう かきく', '') input_keys("\M-t", false) - assert_line('かきく あいう') - assert_byte_pointer_size('かきく あいう') - assert_cursor(13) - assert_cursor_max(13) + assert_line_around_cursor('かきく あいう', '') input_keys("\C-a\C-k", false) input_keys(' あいう かきく ') input_keys("\C-b" * 4, false) - assert_line(' あいう かきく ') - assert_byte_pointer_size(' あいう かき') - assert_cursor(13) - assert_cursor_max(18) + assert_line_around_cursor(' あいう かき', 'く ') input_keys("\M-t", false) - assert_line(' かきく あいう ') - assert_byte_pointer_size(' かきく あいう') - assert_cursor(15) - assert_cursor_max(18) + assert_line_around_cursor(' かきく あいう', ' ') input_keys("\C-a\C-k", false) input_keys(' あいう かきく ') input_keys("\C-b" * 6, false) - assert_line(' あいう かきく ') - assert_byte_pointer_size(' あいう ') - assert_cursor(9) - assert_cursor_max(18) + assert_line_around_cursor(' あいう ', 'かきく ') input_keys("\M-t", false) - assert_line(' かきく あいう ') - assert_byte_pointer_size(' かきく あいう') - assert_cursor(15) - assert_cursor_max(18) + assert_line_around_cursor(' かきく あいう', ' ') input_keys("\M-t", false) - assert_line(' あいう かきく') - assert_byte_pointer_size(' あいう かきく') - assert_cursor(18) - assert_cursor_max(18) + assert_line_around_cursor(' あいう かきく', '') end def test_ed_transpose_words_with_one_word input_keys('abc ') - assert_line('abc ') - assert_byte_pointer_size('abc ') - assert_cursor(5) - assert_cursor_max(5) + assert_line_around_cursor('abc ', '') input_keys("\M-t", false) - assert_line('abc ') - assert_byte_pointer_size('abc ') - assert_cursor(5) - assert_cursor_max(5) + assert_line_around_cursor('abc ', '') input_keys("\C-b", false) - assert_line('abc ') - assert_byte_pointer_size('abc ') - assert_cursor(4) - assert_cursor_max(5) + assert_line_around_cursor('abc ', ' ') input_keys("\M-t", false) - assert_line('abc ') - assert_byte_pointer_size('abc ') - assert_cursor(4) - assert_cursor_max(5) + assert_line_around_cursor('abc ', ' ') input_keys("\C-b" * 2, false) - assert_line('abc ') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(5) + assert_line_around_cursor('ab', 'c ') input_keys("\M-t", false) - assert_line('abc ') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(5) + assert_line_around_cursor('ab', 'c ') input_keys("\M-t", false) - assert_line('abc ') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(5) + assert_line_around_cursor('ab', 'c ') end def test_ed_transpose_words_with_one_word_for_mbchar input_keys('あいう ') - assert_line('あいう ') - assert_byte_pointer_size('あいう ') - assert_cursor(8) - assert_cursor_max(8) + assert_line_around_cursor('あいう ', '') input_keys("\M-t", false) - assert_line('あいう ') - assert_byte_pointer_size('あいう ') - assert_cursor(8) - assert_cursor_max(8) + assert_line_around_cursor('あいう ', '') input_keys("\C-b", false) - assert_line('あいう ') - assert_byte_pointer_size('あいう ') - assert_cursor(7) - assert_cursor_max(8) + assert_line_around_cursor('あいう ', ' ') input_keys("\M-t", false) - assert_line('あいう ') - assert_byte_pointer_size('あいう ') - assert_cursor(7) - assert_cursor_max(8) + assert_line_around_cursor('あいう ', ' ') input_keys("\C-b" * 2, false) - assert_line('あいう ') - assert_byte_pointer_size('あい') - assert_cursor(4) - assert_cursor_max(8) + assert_line_around_cursor('あい', 'う ') input_keys("\M-t", false) - assert_line('あいう ') - assert_byte_pointer_size('あい') - assert_cursor(4) - assert_cursor_max(8) + assert_line_around_cursor('あい', 'う ') input_keys("\M-t", false) - assert_line('あいう ') - assert_byte_pointer_size('あい') - assert_cursor(4) - assert_cursor_max(8) + assert_line_around_cursor('あい', 'う ') end def test_ed_digit input_keys('0123') - assert_byte_pointer_size('0123') - assert_cursor(4) - assert_cursor_max(4) - assert_line('0123') + assert_line_around_cursor('0123', '') end def test_ed_next_and_prev_char input_keys('abc') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('abc', '') input_keys("\C-b", false) - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(3) + assert_line_around_cursor('ab', 'c') input_keys("\C-b", false) - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(3) + assert_line_around_cursor('a', 'bc') input_keys("\C-b", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) + assert_line_around_cursor('', 'abc') input_keys("\C-b", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) + assert_line_around_cursor('', 'abc') input_keys("\C-f", false) - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(3) + assert_line_around_cursor('a', 'bc') input_keys("\C-f", false) - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(3) + assert_line_around_cursor('ab', 'c') input_keys("\C-f", false) - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('abc', '') input_keys("\C-f", false) - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('abc', '') end def test_ed_next_and_prev_char_for_mbchar input_keys('あいう') - assert_byte_pointer_size('あいう') - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor('あいう', '') input_keys("\C-b", false) - assert_byte_pointer_size('あい') - assert_cursor(4) - assert_cursor_max(6) + assert_line_around_cursor('あい', 'う') input_keys("\C-b", false) - assert_byte_pointer_size('あ') - assert_cursor(2) - assert_cursor_max(6) + assert_line_around_cursor('あ', 'いう') input_keys("\C-b", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', 'あいう') input_keys("\C-b", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', 'あいう') input_keys("\C-f", false) - assert_byte_pointer_size('あ') - assert_cursor(2) - assert_cursor_max(6) + assert_line_around_cursor('あ', 'いう') input_keys("\C-f", false) - assert_byte_pointer_size('あい') - assert_cursor(4) - assert_cursor_max(6) + assert_line_around_cursor('あい', 'う') input_keys("\C-f", false) - assert_byte_pointer_size('あいう') - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor('あいう', '') input_keys("\C-f", false) - assert_byte_pointer_size('あいう') - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor('あいう', '') end def test_ed_next_and_prev_char_for_mbchar_by_plural_code_points input_keys("か\u3099き\u3099く\u3099") - assert_byte_pointer_size("か\u3099き\u3099く\u3099") - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099き\u3099く\u3099", '') input_keys("\C-b", false) - assert_byte_pointer_size("か\u3099き\u3099") - assert_cursor(4) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099き\u3099", "く\u3099") input_keys("\C-b", false) - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099", "き\u3099く\u3099") input_keys("\C-b", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', "か\u3099き\u3099く\u3099") input_keys("\C-b", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', "か\u3099き\u3099く\u3099") input_keys("\C-f", false) - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099", "き\u3099く\u3099") input_keys("\C-f", false) - assert_byte_pointer_size("か\u3099き\u3099") - assert_cursor(4) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099き\u3099", "く\u3099") input_keys("\C-f", false) - assert_byte_pointer_size("か\u3099き\u3099く\u3099") - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099き\u3099く\u3099", '') input_keys("\C-f", false) - assert_byte_pointer_size("か\u3099き\u3099く\u3099") - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor("か\u3099き\u3099く\u3099", '') end def test_em_capitol_case input_keys('abc def{bbb}ccc') input_keys("\C-a\M-c", false) - assert_byte_pointer_size('Abc') - assert_cursor(3) - assert_cursor_max(15) - assert_line('Abc def{bbb}ccc') + assert_line_around_cursor('Abc', ' def{bbb}ccc') input_keys("\M-c", false) - assert_byte_pointer_size('Abc Def') - assert_cursor(7) - assert_cursor_max(15) - assert_line('Abc Def{bbb}ccc') + assert_line_around_cursor('Abc Def', '{bbb}ccc') input_keys("\M-c", false) - assert_byte_pointer_size('Abc Def{Bbb') - assert_cursor(11) - assert_cursor_max(15) - assert_line('Abc Def{Bbb}ccc') + assert_line_around_cursor('Abc Def{Bbb', '}ccc') input_keys("\M-c", false) - assert_byte_pointer_size('Abc Def{Bbb}Ccc') - assert_cursor(15) - assert_cursor_max(15) - assert_line('Abc Def{Bbb}Ccc') + assert_line_around_cursor('Abc Def{Bbb}Ccc', '') end def test_em_capitol_case_with_complex_example input_keys('{}#* AaA!!!cCc ') input_keys("\C-a\M-c", false) - assert_byte_pointer_size('{}#* Aaa') - assert_cursor(11) - assert_cursor_max(20) - assert_line('{}#* Aaa!!!cCc ') + assert_line_around_cursor('{}#* Aaa', '!!!cCc ') input_keys("\M-c", false) - assert_byte_pointer_size('{}#* Aaa!!!Ccc') - assert_cursor(17) - assert_cursor_max(20) - assert_line('{}#* Aaa!!!Ccc ') + assert_line_around_cursor('{}#* Aaa!!!Ccc', ' ') input_keys("\M-c", false) - assert_byte_pointer_size('{}#* Aaa!!!Ccc ') - assert_cursor(20) - assert_cursor_max(20) - assert_line('{}#* Aaa!!!Ccc ') + assert_line_around_cursor('{}#* Aaa!!!Ccc ', '') end def test_em_lower_case input_keys('AbC def{bBb}CCC') input_keys("\C-a\M-l", false) - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(15) - assert_line('abc def{bBb}CCC') + assert_line_around_cursor('abc', ' def{bBb}CCC') input_keys("\M-l", false) - assert_byte_pointer_size('abc def') - assert_cursor(7) - assert_cursor_max(15) - assert_line('abc def{bBb}CCC') + assert_line_around_cursor('abc def', '{bBb}CCC') input_keys("\M-l", false) - assert_byte_pointer_size('abc def{bbb') - assert_cursor(11) - assert_cursor_max(15) - assert_line('abc def{bbb}CCC') + assert_line_around_cursor('abc def{bbb', '}CCC') input_keys("\M-l", false) - assert_byte_pointer_size('abc def{bbb}ccc') - assert_cursor(15) - assert_cursor_max(15) - assert_line('abc def{bbb}ccc') + assert_line_around_cursor('abc def{bbb}ccc', '') end def test_em_lower_case_with_complex_example input_keys('{}#* AaA!!!cCc ') input_keys("\C-a\M-l", false) - assert_byte_pointer_size('{}#* aaa') - assert_cursor(11) - assert_cursor_max(20) - assert_line('{}#* aaa!!!cCc ') + assert_line_around_cursor('{}#* aaa', '!!!cCc ') input_keys("\M-l", false) - assert_byte_pointer_size('{}#* aaa!!!ccc') - assert_cursor(17) - assert_cursor_max(20) - assert_line('{}#* aaa!!!ccc ') + assert_line_around_cursor('{}#* aaa!!!ccc', ' ') input_keys("\M-l", false) - assert_byte_pointer_size('{}#* aaa!!!ccc ') - assert_cursor(20) - assert_cursor_max(20) - assert_line('{}#* aaa!!!ccc ') + assert_line_around_cursor('{}#* aaa!!!ccc ', '') end def test_em_upper_case input_keys('AbC def{bBb}CCC') input_keys("\C-a\M-u", false) - assert_byte_pointer_size('ABC') - assert_cursor(3) - assert_cursor_max(15) - assert_line('ABC def{bBb}CCC') + assert_line_around_cursor('ABC', ' def{bBb}CCC') input_keys("\M-u", false) - assert_byte_pointer_size('ABC DEF') - assert_cursor(7) - assert_cursor_max(15) - assert_line('ABC DEF{bBb}CCC') + assert_line_around_cursor('ABC DEF', '{bBb}CCC') input_keys("\M-u", false) - assert_byte_pointer_size('ABC DEF{BBB') - assert_cursor(11) - assert_cursor_max(15) - assert_line('ABC DEF{BBB}CCC') + assert_line_around_cursor('ABC DEF{BBB', '}CCC') input_keys("\M-u", false) - assert_byte_pointer_size('ABC DEF{BBB}CCC') - assert_cursor(15) - assert_cursor_max(15) - assert_line('ABC DEF{BBB}CCC') + assert_line_around_cursor('ABC DEF{BBB}CCC', '') end def test_em_upper_case_with_complex_example input_keys('{}#* AaA!!!cCc ') input_keys("\C-a\M-u", false) - assert_byte_pointer_size('{}#* AAA') - assert_cursor(11) - assert_cursor_max(20) - assert_line('{}#* AAA!!!cCc ') + assert_line_around_cursor('{}#* AAA', '!!!cCc ') input_keys("\M-u", false) - assert_byte_pointer_size('{}#* AAA!!!CCC') - assert_cursor(17) - assert_cursor_max(20) - assert_line('{}#* AAA!!!CCC ') + assert_line_around_cursor('{}#* AAA!!!CCC', ' ') input_keys("\M-u", false) - assert_byte_pointer_size('{}#* AAA!!!CCC ') - assert_cursor(20) - assert_cursor_max(20) - assert_line('{}#* AAA!!!CCC ') + assert_line_around_cursor('{}#* AAA!!!CCC ', '') end def test_em_delete_or_list @@ -1286,28 +726,16 @@ def test_em_delete_or_list } } input_keys('fooo') - assert_byte_pointer_size('fooo') - assert_cursor(4) - assert_cursor_max(4) - assert_line('fooo') + assert_line_around_cursor('fooo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-b", false) - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(4) - assert_line('fooo') + assert_line_around_cursor('foo', 'o') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) @line_editor.input_key(Reline::Key.new(:em_delete_or_list, :em_delete_or_list, false)) - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) @line_editor.input_key(Reline::Key.new(:em_delete_or_list, :em_delete_or_list, false)) - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) end @@ -1322,22 +750,13 @@ def test_completion_duplicated_list } } input_keys('foo_') - assert_byte_pointer_size('foo_') - assert_cursor(4) - assert_cursor_max(4) - assert_line('foo_') + assert_line_around_cursor('foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size('foo_') - assert_cursor(4) - assert_cursor_max(4) - assert_line('foo_') + assert_line_around_cursor('foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size('foo_') - assert_cursor(4) - assert_cursor_max(4) - assert_line('foo_') + assert_line_around_cursor('foo_', '') assert_equal(%w{foo_foo foo_bar}, @line_editor.instance_variable_get(:@menu_info).list) end @@ -1353,36 +772,21 @@ def test_completion } } input_keys('fo') - assert_byte_pointer_size('fo') - assert_cursor(2) - assert_cursor_max(2) - assert_line('fo') + assert_line_around_cursor('fo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size('foo_') - assert_cursor(4) - assert_cursor_max(4) - assert_line('foo_') + assert_line_around_cursor('foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size('foo_') - assert_cursor(4) - assert_cursor_max(4) - assert_line('foo_') + assert_line_around_cursor('foo_', '') assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) input_keys('a') input_keys("\C-i", false) - assert_byte_pointer_size('foo_a') - assert_cursor(5) - assert_cursor_max(5) - assert_line('foo_a') + assert_line_around_cursor('foo_a', '') input_keys("\C-h", false) input_keys('b') input_keys("\C-i", false) - assert_byte_pointer_size('foo_ba') - assert_cursor(6) - assert_cursor_max(6) - assert_line('foo_ba') + assert_line_around_cursor('foo_ba', '') end def test_completion_with_indent @@ -1397,22 +801,13 @@ def test_completion_with_indent } } input_keys(' fo') - assert_byte_pointer_size(' fo') - assert_cursor(4) - assert_cursor_max(4) - assert_line(' fo') + assert_line_around_cursor(' fo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size(' foo_') - assert_cursor(6) - assert_cursor_max(6) - assert_line(' foo_') + assert_line_around_cursor(' foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size(' foo_') - assert_cursor(6) - assert_cursor_max(6) - assert_line(' foo_') + assert_line_around_cursor(' foo_', '') assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) end @@ -1428,22 +823,13 @@ def test_completion_with_indent_and_completer_quote_characters } } input_keys(' "".fo') - assert_byte_pointer_size(' "".fo') - assert_cursor(7) - assert_cursor_max(7) - assert_line(' "".fo') + assert_line_around_cursor(' "".fo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size(' "".foo_') - assert_cursor(9) - assert_cursor_max(9) - assert_line(' "".foo_') + assert_line_around_cursor(' "".foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size(' "".foo_') - assert_cursor(9) - assert_cursor_max(9) - assert_line(' "".foo_') + assert_line_around_cursor(' "".foo_', '') assert_equal(%w{"".foo_foo "".foo_bar "".foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) end @@ -1461,54 +847,33 @@ def test_completion_with_perfect_match matched = m } input_keys('fo') - assert_byte_pointer_size('fo') - assert_cursor(2) - assert_cursor_max(2) - assert_line('fo') + assert_line_around_cursor('fo', '') assert_equal(Reline::LineEditor::CompletionState::NORMAL, @line_editor.instance_variable_get(:@completion_state)) assert_equal(nil, matched) input_keys("\C-i", false) - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') assert_equal(Reline::LineEditor::CompletionState::MENU_WITH_PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal(nil, matched) input_keys("\C-i", false) - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal(nil, matched) input_keys("\C-i", false) - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal('foo', matched) matched = nil input_keys('_') input_keys("\C-i", false) - assert_byte_pointer_size('foo_bar') - assert_cursor(7) - assert_cursor_max(7) - assert_line('foo_bar') + assert_line_around_cursor('foo_bar', '') assert_equal(Reline::LineEditor::CompletionState::MENU_WITH_PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal(nil, matched) input_keys("\C-i", false) - assert_byte_pointer_size('foo_bar') - assert_cursor(7) - assert_cursor_max(7) - assert_line('foo_bar') + assert_line_around_cursor('foo_bar', '') assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal(nil, matched) input_keys("\C-i", false) - assert_byte_pointer_size('foo_bar') - assert_cursor(7) - assert_cursor_max(7) - assert_line('foo_bar') + assert_line_around_cursor('foo_bar', '') assert_equal(Reline::LineEditor::CompletionState::PERFECT_MATCH, @line_editor.instance_variable_get(:@completion_state)) assert_equal('foo_bar', matched) end @@ -1525,43 +890,25 @@ def test_completion_with_completion_ignore_case } } input_keys('fo') - assert_byte_pointer_size('fo') - assert_cursor(2) - assert_cursor_max(2) - assert_line('fo') + assert_line_around_cursor('fo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size('foo_') - assert_cursor(4) - assert_cursor_max(4) - assert_line('foo_') + assert_line_around_cursor('foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size('foo_') - assert_cursor(4) - assert_cursor_max(4) - assert_line('foo_') + assert_line_around_cursor('foo_', '') assert_equal(%w{foo_foo foo_bar}, @line_editor.instance_variable_get(:@menu_info).list) @config.completion_ignore_case = true input_keys("\C-i", false) - assert_byte_pointer_size('foo_') - assert_cursor(4) - assert_cursor_max(4) - assert_line('foo_') + assert_line_around_cursor('foo_', '') assert_equal(%w{foo_foo foo_bar Foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) input_keys('a') input_keys("\C-i", false) - assert_byte_pointer_size('foo_a') - assert_cursor(5) - assert_cursor_max(5) - assert_line('foo_a') + assert_line_around_cursor('foo_a', '') input_keys("\C-h", false) input_keys('b') input_keys("\C-i", false) - assert_byte_pointer_size('foo_ba') - assert_cursor(6) - assert_cursor_max(6) - assert_line('foo_ba') + assert_line_around_cursor('foo_ba', '') end def test_completion_in_middle_of_line @@ -1576,17 +923,11 @@ def test_completion_in_middle_of_line } } input_keys('abcde fo ABCDE') - assert_line('abcde fo ABCDE') + assert_line_around_cursor('abcde fo ABCDE', '') input_keys("\C-b" * 6 + "\C-i", false) - assert_byte_pointer_size('abcde foo_') - assert_cursor(10) - assert_cursor_max(16) - assert_line('abcde foo_ ABCDE') + assert_line_around_cursor('abcde foo_', ' ABCDE') input_keys("\C-b" * 2 + "\C-i", false) - assert_byte_pointer_size('abcde foo_') - assert_cursor(10) - assert_cursor_max(18) - assert_line('abcde foo_o_ ABCDE') + assert_line_around_cursor('abcde foo_', 'o_ ABCDE') end def test_completion_with_nil_value @@ -1602,125 +943,65 @@ def test_completion_with_nil_value } @config.completion_ignore_case = true input_keys('fo') - assert_byte_pointer_size('fo') - assert_cursor(2) - assert_cursor_max(2) - assert_line('fo') + assert_line_around_cursor('fo', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size('foo_') - assert_cursor(4) - assert_cursor_max(4) - assert_line('foo_') + assert_line_around_cursor('foo_', '') assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) input_keys("\C-i", false) - assert_byte_pointer_size('foo_') - assert_cursor(4) - assert_cursor_max(4) - assert_line('foo_') + assert_line_around_cursor('foo_', '') assert_equal(%w{foo_foo foo_bar Foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) input_keys('a') input_keys("\C-i", false) - assert_byte_pointer_size('foo_a') - assert_cursor(5) - assert_cursor_max(5) - assert_line('foo_a') + assert_line_around_cursor('foo_a', '') input_keys("\C-h", false) input_keys('b') input_keys("\C-i", false) - assert_byte_pointer_size('foo_ba') - assert_cursor(6) - assert_cursor_max(6) - assert_line('foo_ba') + assert_line_around_cursor('foo_ba', '') end def test_em_kill_region input_keys('abc def{bbb}ccc ddd ') - assert_byte_pointer_size('abc def{bbb}ccc ddd ') - assert_cursor(26) - assert_cursor_max(26) - assert_line('abc def{bbb}ccc ddd ') + assert_line_around_cursor('abc def{bbb}ccc ddd ', '') input_keys("\C-w", false) - assert_byte_pointer_size('abc def{bbb}ccc ') - assert_cursor(20) - assert_cursor_max(20) - assert_line('abc def{bbb}ccc ') + assert_line_around_cursor('abc def{bbb}ccc ', '') input_keys("\C-w", false) - assert_byte_pointer_size('abc ') - assert_cursor(6) - assert_cursor_max(6) - assert_line('abc ') + assert_line_around_cursor('abc ', '') input_keys("\C-w", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys("\C-w", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_em_kill_region_mbchar input_keys('あ い う{う}う ') - assert_byte_pointer_size('あ い う{う}う ') - assert_cursor(21) - assert_cursor_max(21) - assert_line('あ い う{う}う ') + assert_line_around_cursor('あ い う{う}う ', '') input_keys("\C-w", false) - assert_byte_pointer_size('あ い ') - assert_cursor(10) - assert_cursor_max(10) - assert_line('あ い ') + assert_line_around_cursor('あ い ', '') input_keys("\C-w", false) - assert_byte_pointer_size('あ ') - assert_cursor(5) - assert_cursor_max(5) - assert_line('あ ') + assert_line_around_cursor('あ ', '') input_keys("\C-w", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_vi_search_prev Reline::HISTORY.concat(%w{abc 123 AAA}) - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-ra\C-j") - assert_line('abc') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) + assert_line_around_cursor('', 'abc') end def test_larger_histories_than_history_size history_size = @config.history_size @config.history_size = 2 Reline::HISTORY.concat(%w{abc 123 AAA}) - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-p") - assert_line('AAA') - assert_byte_pointer_size('AAA') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('AAA', '') input_keys("\C-p") - assert_line('123') - assert_byte_pointer_size('123') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('123', '') input_keys("\C-p") - assert_line('123') - assert_byte_pointer_size('123') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('123', '') ensure @config.history_size = history_size end @@ -1731,25 +1012,13 @@ def test_search_history_to_back '12aa', '1234' # new ]) - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-r123") - assert_line('1234') - assert_byte_pointer_size('1234') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1234', '') input_keys("\C-ha") - assert_line('12aa') - assert_byte_pointer_size('12aa') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('12aa', '') input_keys("\C-h3") - assert_line('1235') - assert_byte_pointer_size('1235') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1235', '') end def test_search_history_to_front @@ -1758,25 +1027,13 @@ def test_search_history_to_front '12aa', '1234' # new ]) - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-s123") - assert_line('1235') - assert_byte_pointer_size('1235') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1235', '') input_keys("\C-ha") - assert_line('12aa') - assert_byte_pointer_size('12aa') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('12aa', '') input_keys("\C-h3") - assert_line('1234') - assert_byte_pointer_size('1234') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1234', '') end def test_search_history_front_and_back @@ -1785,30 +1042,15 @@ def test_search_history_front_and_back '12aa', '1234' # new ]) - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-s12") - assert_line('1235') - assert_byte_pointer_size('1235') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1235', '') input_keys("\C-s") - assert_line('12aa') - assert_byte_pointer_size('12aa') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('12aa', '') input_keys("\C-r") - assert_line('12aa') - assert_byte_pointer_size('12aa') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('12aa', '') input_keys("\C-r") - assert_line('1235') - assert_byte_pointer_size('1235') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1235', '') end def test_search_history_back_and_front @@ -1817,30 +1059,15 @@ def test_search_history_back_and_front '12aa', '1234' # new ]) - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-r12") - assert_line('1234') - assert_byte_pointer_size('1234') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1234', '') input_keys("\C-r") - assert_line('12aa') - assert_byte_pointer_size('12aa') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('12aa', '') input_keys("\C-s") - assert_line('12aa') - assert_byte_pointer_size('12aa') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('12aa', '') input_keys("\C-s") - assert_line('1234') - assert_byte_pointer_size('1234') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1234', '') end def test_search_history_to_back_in_the_middle_of_histories @@ -1849,20 +1076,11 @@ def test_search_history_to_back_in_the_middle_of_histories '12aa', '1234' # new ]) - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-p\C-p") - assert_line('12aa') - assert_byte_pointer_size('12aa') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('12aa', '') input_keys("\C-r123") - assert_line('1235') - assert_byte_pointer_size('1235') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1235', '') end def test_search_history_twice @@ -1871,20 +1089,11 @@ def test_search_history_twice '12aa', '1234' # new ]) - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-r123") - assert_line('1234') - assert_byte_pointer_size('1234') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1234', '') input_keys("\C-r") - assert_line('1235') - assert_byte_pointer_size('1235') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1235', '') end def test_search_history_by_last_determined @@ -1893,35 +1102,17 @@ def test_search_history_by_last_determined '12aa', '1234' # new ]) - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-r123") - assert_line('1234') - assert_byte_pointer_size('1234') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1234', '') input_keys("\C-j") - assert_line('1234') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(4) + assert_line_around_cursor('', '1234') input_keys("\C-k") # delete - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-r") - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-r") - assert_line('1235') - assert_byte_pointer_size('1235') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('1235', '') end def test_search_history_with_isearch_terminator @@ -1933,76 +1124,40 @@ def test_search_history_with_isearch_terminator '12aa', '1234' # new ]) - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys("\C-r12a") - assert_line('12aa') - assert_byte_pointer_size('12aa') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('12aa', '') input_keys('Y') - assert_line('12aa') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(4) + assert_line_around_cursor('', '12aa') input_keys('x') - assert_line('x12aa') - assert_byte_pointer_size('x') - assert_cursor(1) - assert_cursor_max(5) + assert_line_around_cursor('x', '12aa') end def test_em_set_mark_and_em_exchange_mark input_keys('aaa bbb ccc ddd') - assert_byte_pointer_size('aaa bbb ccc ddd') - assert_cursor(15) - assert_cursor_max(15) - assert_line('aaa bbb ccc ddd') + assert_line_around_cursor('aaa bbb ccc ddd', '') input_keys("\C-a\M-F\M-F", false) - assert_byte_pointer_size('aaa bbb') - assert_cursor(7) - assert_cursor_max(15) - assert_line('aaa bbb ccc ddd') + assert_line_around_cursor('aaa bbb', ' ccc ddd') assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer)) input_keys("\x00", false) # C-Space - assert_byte_pointer_size('aaa bbb') - assert_cursor(7) - assert_cursor_max(15) - assert_line('aaa bbb ccc ddd') + assert_line_around_cursor('aaa bbb', ' ccc ddd') assert_equal([7, 0], @line_editor.instance_variable_get(:@mark_pointer)) input_keys("\C-a", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(15) - assert_line('aaa bbb ccc ddd') + assert_line_around_cursor('', 'aaa bbb ccc ddd') assert_equal([7, 0], @line_editor.instance_variable_get(:@mark_pointer)) input_key_by_symbol(:em_exchange_mark) - assert_byte_pointer_size('aaa bbb') - assert_cursor(7) - assert_cursor_max(15) - assert_line('aaa bbb ccc ddd') + assert_line_around_cursor('aaa bbb', ' ccc ddd') assert_equal([0, 0], @line_editor.instance_variable_get(:@mark_pointer)) end def test_em_exchange_mark_without_mark input_keys('aaa bbb ccc ddd') - assert_byte_pointer_size('aaa bbb ccc ddd') - assert_cursor(15) - assert_cursor_max(15) - assert_line('aaa bbb ccc ddd') + assert_line_around_cursor('aaa bbb ccc ddd', '') input_keys("\C-a\M-f", false) - assert_byte_pointer_size('aaa') - assert_cursor(3) - assert_cursor_max(15) - assert_line('aaa bbb ccc ddd') + assert_line_around_cursor('aaa', ' bbb ccc ddd') assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer)) input_key_by_symbol(:em_exchange_mark) - assert_byte_pointer_size('aaa') - assert_cursor(3) - assert_cursor_max(15) - assert_line('aaa bbb ccc ddd') + assert_line_around_cursor('aaa', ' bbb ccc ddd') assert_equal(nil, @line_editor.instance_variable_get(:@mark_pointer)) end @@ -2031,20 +1186,11 @@ def test_ed_search_prev_history input_keys('123') # The ed_search_prev_history doesn't have default binding @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('123') - assert_cursor(3) - assert_cursor_max(5) - assert_line('12345') + assert_line_around_cursor('123', '45') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('123') - assert_cursor(3) - assert_cursor_max(5) - assert_line('12356') + assert_line_around_cursor('123', '56') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('123') - assert_cursor(3) - assert_cursor_max(5) - assert_line('12356') + assert_line_around_cursor('123', '56') end def test_ed_search_prev_history_with_empty @@ -2055,25 +1201,13 @@ def test_ed_search_prev_history_with_empty ]) # The ed_search_prev_history doesn't have default binding @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) - assert_line('12345') + assert_line_around_cursor('', '12345') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) - assert_line('12aaa') + assert_line_around_cursor('', '12aaa') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) - assert_line('12356') + assert_line_around_cursor('', '12356') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) - assert_line('12356') + assert_line_around_cursor('', '12356') end def test_ed_search_prev_history_without_match @@ -2085,10 +1219,7 @@ def test_ed_search_prev_history_without_match input_keys('ABC') # The ed_search_prev_history doesn't have default binding @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('ABC') - assert_cursor(3) - assert_cursor_max(3) - assert_line('ABC') + assert_line_around_cursor('ABC', '') end def test_ed_search_next_history @@ -2100,30 +1231,15 @@ def test_ed_search_next_history input_keys('123') # The ed_search_prev_history and ed_search_next_history doesn't have default binding @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('123') - assert_cursor(3) - assert_cursor_max(5) - assert_line('12345') + assert_line_around_cursor('123', '45') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('123') - assert_cursor(3) - assert_cursor_max(5) - assert_line('12356') + assert_line_around_cursor('123', '56') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('123') - assert_cursor(3) - assert_cursor_max(5) - assert_line('12356') + assert_line_around_cursor('123', '56') @line_editor.__send__(:ed_search_next_history, "\C-n".ord) - assert_byte_pointer_size('123') - assert_cursor(3) - assert_cursor_max(5) - assert_line('12345') + assert_line_around_cursor('123', '45') @line_editor.__send__(:ed_search_next_history, "\C-n".ord) - assert_byte_pointer_size('123') - assert_cursor(3) - assert_cursor_max(5) - assert_line('12345') + assert_line_around_cursor('123', '45') end def test_ed_search_next_history_with_empty @@ -2134,35 +1250,17 @@ def test_ed_search_next_history_with_empty ]) # The ed_search_prev_history and ed_search_next_history doesn't have default binding @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) - assert_line('12345') + assert_line_around_cursor('', '12345') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) - assert_line('12aaa') + assert_line_around_cursor('', '12aaa') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) - assert_line('12356') + assert_line_around_cursor('', '12356') @line_editor.__send__(:ed_search_next_history, "\C-n".ord) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) - assert_line('12aaa') + assert_line_around_cursor('', '12aaa') @line_editor.__send__(:ed_search_next_history, "\C-n".ord) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) - assert_line('12345') + assert_line_around_cursor('', '12345') @line_editor.__send__(:ed_search_next_history, "\C-n".ord) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end # Unicode emoji test @@ -2170,97 +1268,49 @@ def test_ed_insert_for_include_zwj_emoji omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8 # U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466 is family: man, woman, girl, boy "👨‍👩‍👧‍👦" input_keys("\u{1F468}") # U+1F468 is man "👨" - assert_line("\u{1F468}") - assert_byte_pointer_size("\u{1F468}") - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('👨', '') input_keys("\u200D") # U+200D is ZERO WIDTH JOINER - assert_line("\u{1F468 200D}") - assert_byte_pointer_size("\u{1F468 200D}") - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('👨‍', '') input_keys("\u{1F469}") # U+1F469 is woman "👩" - assert_line("\u{1F468 200D 1F469}") - assert_byte_pointer_size("\u{1F468 200D 1F469}") - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('👨‍👩', '') input_keys("\u200D") # U+200D is ZERO WIDTH JOINER - assert_line("\u{1F468 200D 1F469 200D}") - assert_byte_pointer_size("\u{1F468 200D 1F469 200D}") - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('👨‍👩‍', '') input_keys("\u{1F467}") # U+1F467 is girl "👧" - assert_line("\u{1F468 200D 1F469 200D 1F467}") - assert_byte_pointer_size("\u{1F468 200D 1F469 200D 1F467}") - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('👨‍👩‍👧', '') input_keys("\u200D") # U+200D is ZERO WIDTH JOINER - assert_line("\u{1F468 200D 1F469 200D 1F467 200D}") - assert_byte_pointer_size("\u{1F468 200D 1F469 200D 1F467 200D}") - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('👨‍👩‍👧‍', '') input_keys("\u{1F466}") # U+1F466 is boy "👦" - assert_line("\u{1F468 200D 1F469 200D 1F467 200D 1F466}") - assert_byte_pointer_size("\u{1F468 200D 1F469 200D 1F467 200D 1F466}") - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('👨‍👩‍👧‍👦', '') # U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466 is family: man, woman, girl, boy "👨‍👩‍👧‍👦" input_keys("\u{1F468 200D 1F469 200D 1F467 200D 1F466}") - assert_line("\u{1F468 200D 1F469 200D 1F467 200D 1F466 1F468 200D 1F469 200D 1F467 200D 1F466}") - assert_byte_pointer_size("\u{1F468 200D 1F469 200D 1F467 200D 1F466 1F468 200D 1F469 200D 1F467 200D 1F466}") - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('👨‍👩‍👧‍👦👨‍👩‍👧‍👦', '') end def test_ed_insert_for_include_valiation_selector omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8 # U+0030 U+FE00 is DIGIT ZERO + VARIATION SELECTOR-1 "0︀" input_keys("\u0030") # U+0030 is DIGIT ZERO - assert_line("\u0030") - assert_byte_pointer_size("\u0030") - assert_cursor(1) - assert_cursor_max(1) + assert_line_around_cursor('0', '') input_keys("\uFE00") # U+FE00 is VARIATION SELECTOR-1 - assert_line("\u{0030 FE00}") - assert_byte_pointer_size("\u{0030 FE00}") - assert_cursor(1) - assert_cursor_max(1) + assert_line_around_cursor('0︀', '') end def test_em_yank_pop input_keys("def hoge\C-w\C-b\C-f\C-w", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys("\C-y", false) - assert_byte_pointer_size('def ') - assert_cursor(4) - assert_cursor_max(4) - assert_line('def ') + assert_line_around_cursor('def ', '') input_keys("\M-\C-y", false) - assert_byte_pointer_size('hoge') - assert_cursor(4) - assert_cursor_max(4) - assert_line('hoge') + assert_line_around_cursor('hoge', '') end def test_em_kill_region_with_kill_ring input_keys("def hoge\C-b\C-b\C-b\C-b", false) - assert_byte_pointer_size('def ') - assert_cursor(4) - assert_cursor_max(8) - assert_line('def hoge') + assert_line_around_cursor('def ', 'hoge') input_keys("\C-k\C-w", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys("\C-y", false) - assert_byte_pointer_size('def hoge') - assert_cursor(8) - assert_cursor_max(8) - assert_line('def hoge') + assert_line_around_cursor('def hoge', '') end def test_ed_search_prev_next_history_in_multibyte @@ -2276,104 +1326,60 @@ def test_ed_search_prev_next_history_in_multibyte assert_whole_lines(['def foo', ' 12345', 'end']) assert_line_index(1) assert_whole_lines(['def foo', ' 12345', 'end']) - assert_byte_pointer_size(' 123') - assert_cursor(5) - assert_cursor_max(7) - assert_line(' 12345') + assert_line_around_cursor(' 123', '45') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) assert_line_index(2) assert_whole_lines(['def hoge', ' 67890', ' 12345', 'end']) - assert_byte_pointer_size(' 123') - assert_cursor(5) - assert_cursor_max(7) - assert_line(' 12345') + assert_line_around_cursor(' 123', '45') @line_editor.__send__(:ed_search_prev_history, "\C-p".ord) assert_line_index(2) assert_whole_lines(['def hoge', ' 67890', ' 12345', 'end']) - assert_byte_pointer_size(' 123') - assert_cursor(5) - assert_cursor_max(7) - assert_line(' 12345') + assert_line_around_cursor(' 123', '45') @line_editor.__send__(:ed_search_next_history, "\C-n".ord) assert_line_index(1) assert_whole_lines(['def foo', ' 12345', 'end']) - assert_byte_pointer_size(' 123') - assert_cursor(5) - assert_cursor_max(7) - assert_line(' 12345') + assert_line_around_cursor(' 123', '45') @line_editor.__send__(:ed_search_next_history, "\C-n".ord) assert_line_index(1) assert_whole_lines(['def foo', ' 12345', 'end']) - assert_byte_pointer_size(' 123') - assert_cursor(5) - assert_cursor_max(7) - assert_line(' 12345') + assert_line_around_cursor(' 123', '45') end def test_ignore_NUL_by_ed_quoted_insert input_keys(%Q{"\C-v\C-@"}, false) - assert_byte_pointer_size('""') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('""', '') end def test_ed_argument_digit_by_meta_num input_keys('abcdef') - assert_byte_pointer_size('abcdef') - assert_cursor(6) - assert_cursor_max(6) - assert_line('abcdef') + assert_line_around_cursor('abcdef', '') input_keys("\M-2", false) input_keys("\C-h", false) - assert_byte_pointer_size('abcd') - assert_cursor(4) - assert_cursor_max(4) - assert_line('abcd') + assert_line_around_cursor('abcd', '') end def test_halfwidth_kana_width_dakuten input_raw_keys('ガギゲゴ') - assert_byte_pointer_size('ガギゲゴ') - assert_cursor(8) - assert_cursor_max(8) + assert_line_around_cursor('ガギゲゴ', '') input_keys("\C-b\C-b", false) - assert_byte_pointer_size('ガギ') - assert_cursor(4) - assert_cursor_max(8) + assert_line_around_cursor('ガギ', 'ゲゴ') input_raw_keys('グ', false) - assert_byte_pointer_size('ガギグ') - assert_cursor(6) - assert_cursor_max(10) - assert_line('ガギグゲゴ') + assert_line_around_cursor('ガギグ', 'ゲゴ') end def test_input_unknown_char input_keys('͸') # U+0378 (unassigned) - assert_line('͸') - assert_byte_pointer_size('͸') - assert_cursor(1) - assert_cursor_max(1) + assert_line_around_cursor('͸', '') end def test_unix_line_discard input_keys("\C-u", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys('abc') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('abc', '') input_keys("\C-b\C-u", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(1) - assert_line('c') + assert_line_around_cursor('', 'c') input_keys("\C-f\C-u", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end end diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb index 7c924d19633bf8..91cbd49d74bfd5 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -25,950 +25,513 @@ def test_vi_command_mode def test_vi_command_mode_with_input input_keys("abc\C-[") assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode) - assert_line('abc') + assert_line_around_cursor('ab', 'c') end def test_vi_insert assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) input_keys('i') - assert_line('i') - assert_cursor(1) + assert_line_around_cursor('i', '') assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) input_keys("\C-[") - assert_line('i') - assert_cursor(0) + assert_line_around_cursor('', 'i') assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode) input_keys('i') - assert_line('i') - assert_cursor(0) + assert_line_around_cursor('', 'i') assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) end def test_vi_add assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) input_keys('a') - assert_line('a') - assert_cursor(1) + assert_line_around_cursor('a', '') assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) input_keys("\C-[") - assert_line('a') - assert_cursor(0) + assert_line_around_cursor('', 'a') assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode) input_keys('a') - assert_line('a') - assert_cursor(1) + assert_line_around_cursor('a', '') assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) end def test_vi_insert_at_bol input_keys('I') - assert_line('I') + assert_line_around_cursor('I', '') assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) input_keys("12345\C-[hh") - assert_line('I12345') - assert_byte_pointer_size('I12') - assert_cursor(3) - assert_cursor_max(6) + assert_line_around_cursor('I12', '345') assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode) input_keys('I') - assert_line('I12345') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', 'I12345') assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) end def test_vi_add_at_eol input_keys('A') - assert_line('A') + assert_line_around_cursor('A', '') assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) input_keys("12345\C-[hh") - assert_line('A12345') - assert_byte_pointer_size('A12') - assert_cursor(3) - assert_cursor_max(6) + assert_line_around_cursor('A12', '345') assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode) input_keys('A') - assert_line('A12345') - assert_byte_pointer_size('A12345') - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor('A12345', '') assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) end def test_ed_insert_one input_keys('a') - assert_line('a') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(1) + assert_line_around_cursor('a', '') end def test_ed_insert_two input_keys('ab') - assert_line('ab') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('ab', '') end def test_ed_insert_mbchar_one input_keys('か') - assert_line('か') - assert_byte_pointer_size('か') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('か', '') end def test_ed_insert_mbchar_two input_keys('かき') - assert_line('かき') - assert_byte_pointer_size('かき') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('かき', '') end def test_ed_insert_for_mbchar_by_plural_code_points input_keys("か\u3099") - assert_line("か\u3099") - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor("か\u3099", '') end def test_ed_insert_for_plural_mbchar_by_plural_code_points input_keys("か\u3099き\u3099") - assert_line("か\u3099き\u3099") - assert_byte_pointer_size("か\u3099き\u3099") - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor("か\u3099き\u3099", '') end def test_ed_next_char input_keys("abcdef\C-[0") - assert_line('abcdef') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', 'abcdef') input_keys('l') - assert_line('abcdef') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(6) + assert_line_around_cursor('a', 'bcdef') input_keys('2l') - assert_line('abcdef') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(6) + assert_line_around_cursor('abc', 'def') end def test_ed_prev_char input_keys("abcdef\C-[") - assert_line('abcdef') - assert_byte_pointer_size('abcde') - assert_cursor(5) - assert_cursor_max(6) + assert_line_around_cursor('abcde', 'f') input_keys('h') - assert_line('abcdef') - assert_byte_pointer_size('abcd') - assert_cursor(4) - assert_cursor_max(6) + assert_line_around_cursor('abcd', 'ef') input_keys('2h') - assert_line('abcdef') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(6) + assert_line_around_cursor('ab', 'cdef') end def test_history Reline::HISTORY.concat(%w{abc 123 AAA}) input_keys("\C-[") - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys('k') - assert_line('AAA') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) + assert_line_around_cursor('', 'AAA') input_keys('2k') - assert_line('abc') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) + assert_line_around_cursor('', 'abc') input_keys('j') - assert_line('123') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) + assert_line_around_cursor('', '123') input_keys('2j') - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') end def test_vi_paste_prev input_keys("abcde\C-[3h") - assert_line('abcde') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(5) + assert_line_around_cursor('a', 'bcde') input_keys('P') - assert_line('abcde') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(5) + assert_line_around_cursor('a', 'bcde') input_keys('d$') - assert_line('a') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(1) + assert_line_around_cursor('', 'a') input_keys('P') - assert_line('bcdea') - assert_byte_pointer_size('bcd') - assert_cursor(3) - assert_cursor_max(5) + assert_line_around_cursor('bcd', 'ea') input_keys('2P') - assert_line('bcdbcdbcdeeea') - assert_byte_pointer_size('bcdbcdbcd') - assert_cursor(9) - assert_cursor_max(13) + assert_line_around_cursor('bcdbcdbcd', 'eeea') end def test_vi_paste_next input_keys("abcde\C-[3h") - assert_line('abcde') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(5) + assert_line_around_cursor('a', 'bcde') input_keys('p') - assert_line('abcde') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(5) + assert_line_around_cursor('a', 'bcde') input_keys('d$') - assert_line('a') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(1) + assert_line_around_cursor('', 'a') input_keys('p') - assert_line('abcde') - assert_byte_pointer_size('abcd') - assert_cursor(4) - assert_cursor_max(5) + assert_line_around_cursor('abcd', 'e') input_keys('2p') - assert_line('abcdebcdebcde') - assert_byte_pointer_size('abcdebcdebcd') - assert_cursor(12) - assert_cursor_max(13) + assert_line_around_cursor('abcdebcdebcd', 'e') end def test_vi_paste_prev_for_mbchar input_keys("あいうえお\C-[3h") - assert_line('あいうえお') - assert_byte_pointer_size('あ') - assert_cursor(2) - assert_cursor_max(10) + assert_line_around_cursor('あ', 'いうえお') input_keys('P') - assert_line('あいうえお') - assert_byte_pointer_size('あ') - assert_cursor(2) - assert_cursor_max(10) + assert_line_around_cursor('あ', 'いうえお') input_keys('d$') - assert_line('あ') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(2) + assert_line_around_cursor('', 'あ') input_keys('P') - assert_line('いうえおあ') - assert_byte_pointer_size('いうえ') - assert_cursor(6) - assert_cursor_max(10) + assert_line_around_cursor('いうえ', 'おあ') input_keys('2P') - assert_line('いうえいうえいうえおおおあ') - assert_byte_pointer_size('いうえいうえいうえ') - assert_cursor(18) - assert_cursor_max(26) + assert_line_around_cursor('いうえいうえいうえ', 'おおおあ') end def test_vi_paste_next_for_mbchar input_keys("あいうえお\C-[3h") - assert_line('あいうえお') - assert_byte_pointer_size('あ') - assert_cursor(2) - assert_cursor_max(10) + assert_line_around_cursor('あ', 'いうえお') input_keys('p') - assert_line('あいうえお') - assert_byte_pointer_size('あ') - assert_cursor(2) - assert_cursor_max(10) + assert_line_around_cursor('あ', 'いうえお') input_keys('d$') - assert_line('あ') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(2) + assert_line_around_cursor('', 'あ') input_keys('p') - assert_line('あいうえお') - assert_byte_pointer_size('あいうえ') - assert_cursor(8) - assert_cursor_max(10) + assert_line_around_cursor('あいうえ', 'お') input_keys('2p') - assert_line('あいうえおいうえおいうえお') - assert_byte_pointer_size('あいうえおいうえおいうえ') - assert_cursor(24) - assert_cursor_max(26) + assert_line_around_cursor('あいうえおいうえおいうえ', 'お') end def test_vi_paste_prev_for_mbchar_by_plural_code_points input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h") - assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099") - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(10) + assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099") input_keys('P') - assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099") - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(10) + assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099") input_keys('d$') - assert_line("か\u3099") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(2) + assert_line_around_cursor('', "か\u3099") input_keys('P') - assert_line("き\u3099く\u3099け\u3099こ\u3099か\u3099") - assert_byte_pointer_size("き\u3099く\u3099け\u3099") - assert_cursor(6) - assert_cursor_max(10) + assert_line_around_cursor("き\u3099く\u3099け\u3099", "こ\u3099か\u3099") input_keys('2P') - assert_line("き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099こ\u3099こ\u3099こ\u3099か\u3099") - assert_byte_pointer_size("き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099") - assert_cursor(18) - assert_cursor_max(26) + assert_line_around_cursor("き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099", "こ\u3099こ\u3099こ\u3099か\u3099") end def test_vi_paste_next_for_mbchar_by_plural_code_points input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h") - assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099") - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(10) + assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099") input_keys('p') - assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099") - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(10) + assert_line_around_cursor("か\u3099", "き\u3099く\u3099け\u3099こ\u3099") input_keys('d$') - assert_line("か\u3099") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(2) + assert_line_around_cursor('', "か\u3099") input_keys('p') - assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099") - assert_byte_pointer_size("か\u3099き\u3099く\u3099け\u3099") - assert_cursor(8) - assert_cursor_max(10) + assert_line_around_cursor("か\u3099き\u3099く\u3099け\u3099", "こ\u3099") input_keys('2p') - assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099") - assert_byte_pointer_size("か\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099") - assert_cursor(24) - assert_cursor_max(26) + assert_line_around_cursor("か\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099", "こ\u3099") end def test_vi_prev_next_word input_keys("aaa b{b}b ccc\C-[0") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(13) + assert_line_around_cursor('', 'aaa b{b}b ccc') input_keys('w') - assert_byte_pointer_size('aaa ') - assert_cursor(4) - assert_cursor_max(13) + assert_line_around_cursor('aaa ', 'b{b}b ccc') input_keys('w') - assert_byte_pointer_size('aaa b') - assert_cursor(5) - assert_cursor_max(13) + assert_line_around_cursor('aaa b', '{b}b ccc') input_keys('w') - assert_byte_pointer_size('aaa b{') - assert_cursor(6) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{', 'b}b ccc') input_keys('w') - assert_byte_pointer_size('aaa b{b') - assert_cursor(7) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b', '}b ccc') input_keys('w') - assert_byte_pointer_size('aaa b{b}') - assert_cursor(8) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}', 'b ccc') input_keys('w') - assert_byte_pointer_size('aaa b{b}b ') - assert_cursor(10) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}b ', 'ccc') input_keys('w') - assert_byte_pointer_size('aaa b{b}b cc') - assert_cursor(12) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}b cc', 'c') input_keys('b') - assert_byte_pointer_size('aaa b{b}b ') - assert_cursor(10) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}b ', 'ccc') input_keys('b') - assert_byte_pointer_size('aaa b{b}') - assert_cursor(8) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}', 'b ccc') input_keys('b') - assert_byte_pointer_size('aaa b{b') - assert_cursor(7) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b', '}b ccc') input_keys('b') - assert_byte_pointer_size('aaa b{') - assert_cursor(6) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{', 'b}b ccc') input_keys('b') - assert_byte_pointer_size('aaa b') - assert_cursor(5) - assert_cursor_max(13) + assert_line_around_cursor('aaa b', '{b}b ccc') input_keys('b') - assert_byte_pointer_size('aaa ') - assert_cursor(4) - assert_cursor_max(13) + assert_line_around_cursor('aaa ', 'b{b}b ccc') input_keys('b') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(13) + assert_line_around_cursor('', 'aaa b{b}b ccc') input_keys('3w') - assert_byte_pointer_size('aaa b{') - assert_cursor(6) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{', 'b}b ccc') input_keys('3w') - assert_byte_pointer_size('aaa b{b}b ') - assert_cursor(10) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}b ', 'ccc') input_keys('3w') - assert_byte_pointer_size('aaa b{b}b cc') - assert_cursor(12) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}b cc', 'c') input_keys('3b') - assert_byte_pointer_size('aaa b{b') - assert_cursor(7) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b', '}b ccc') input_keys('3b') - assert_byte_pointer_size('aaa ') - assert_cursor(4) - assert_cursor_max(13) + assert_line_around_cursor('aaa ', 'b{b}b ccc') input_keys('3b') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(13) + assert_line_around_cursor('', 'aaa b{b}b ccc') end def test_vi_end_word input_keys("aaa b{b}}}b ccc\C-[0") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(19) + assert_line_around_cursor('', 'aaa b{b}}}b ccc') input_keys('e') - assert_byte_pointer_size('aa') - assert_cursor(2) - assert_cursor_max(19) + assert_line_around_cursor('aa', 'a b{b}}}b ccc') input_keys('e') - assert_byte_pointer_size('aaa ') - assert_cursor(6) - assert_cursor_max(19) + assert_line_around_cursor('aaa ', 'b{b}}}b ccc') input_keys('e') - assert_byte_pointer_size('aaa b') - assert_cursor(7) - assert_cursor_max(19) + assert_line_around_cursor('aaa b', '{b}}}b ccc') input_keys('e') - assert_byte_pointer_size('aaa b{') - assert_cursor(8) - assert_cursor_max(19) + assert_line_around_cursor('aaa b{', 'b}}}b ccc') input_keys('e') - assert_byte_pointer_size('aaa b{b}}') - assert_cursor(11) - assert_cursor_max(19) + assert_line_around_cursor('aaa b{b}}', '}b ccc') input_keys('e') - assert_byte_pointer_size('aaa b{b}}}') - assert_cursor(12) - assert_cursor_max(19) + assert_line_around_cursor('aaa b{b}}}', 'b ccc') input_keys('e') - assert_byte_pointer_size('aaa b{b}}}b cc') - assert_cursor(18) - assert_cursor_max(19) + assert_line_around_cursor('aaa b{b}}}b cc', 'c') input_keys('e') - assert_byte_pointer_size('aaa b{b}}}b cc') - assert_cursor(18) - assert_cursor_max(19) + assert_line_around_cursor('aaa b{b}}}b cc', 'c') input_keys('03e') - assert_byte_pointer_size('aaa b') - assert_cursor(7) - assert_cursor_max(19) + assert_line_around_cursor('aaa b', '{b}}}b ccc') input_keys('3e') - assert_byte_pointer_size('aaa b{b}}}') - assert_cursor(12) - assert_cursor_max(19) + assert_line_around_cursor('aaa b{b}}}', 'b ccc') input_keys('3e') - assert_byte_pointer_size('aaa b{b}}}b cc') - assert_cursor(18) - assert_cursor_max(19) + assert_line_around_cursor('aaa b{b}}}b cc', 'c') end def test_vi_prev_next_big_word input_keys("aaa b{b}b ccc\C-[0") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(13) + assert_line_around_cursor('', 'aaa b{b}b ccc') input_keys('W') - assert_byte_pointer_size('aaa ') - assert_cursor(4) - assert_cursor_max(13) + assert_line_around_cursor('aaa ', 'b{b}b ccc') input_keys('W') - assert_byte_pointer_size('aaa b{b}b ') - assert_cursor(10) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}b ', 'ccc') input_keys('W') - assert_byte_pointer_size('aaa b{b}b cc') - assert_cursor(12) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}b cc', 'c') input_keys('B') - assert_byte_pointer_size('aaa b{b}b ') - assert_cursor(10) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}b ', 'ccc') input_keys('B') - assert_byte_pointer_size('aaa ') - assert_cursor(4) - assert_cursor_max(13) + assert_line_around_cursor('aaa ', 'b{b}b ccc') input_keys('B') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(13) + assert_line_around_cursor('', 'aaa b{b}b ccc') input_keys('2W') - assert_byte_pointer_size('aaa b{b}b ') - assert_cursor(10) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}b ', 'ccc') input_keys('2W') - assert_byte_pointer_size('aaa b{b}b cc') - assert_cursor(12) - assert_cursor_max(13) + assert_line_around_cursor('aaa b{b}b cc', 'c') input_keys('2B') - assert_byte_pointer_size('aaa ') - assert_cursor(4) - assert_cursor_max(13) + assert_line_around_cursor('aaa ', 'b{b}b ccc') input_keys('2B') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(13) + assert_line_around_cursor('', 'aaa b{b}b ccc') end def test_vi_end_big_word input_keys("aaa b{b}}}b ccc\C-[0") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(19) + assert_line_around_cursor('', 'aaa b{b}}}b ccc') input_keys('E') - assert_byte_pointer_size('aa') - assert_cursor(2) - assert_cursor_max(19) + assert_line_around_cursor('aa', 'a b{b}}}b ccc') input_keys('E') - assert_byte_pointer_size('aaa b{b}}}') - assert_cursor(12) - assert_cursor_max(19) + assert_line_around_cursor('aaa b{b}}}', 'b ccc') input_keys('E') - assert_byte_pointer_size('aaa b{b}}}b cc') - assert_cursor(18) - assert_cursor_max(19) + assert_line_around_cursor('aaa b{b}}}b cc', 'c') input_keys('E') - assert_byte_pointer_size('aaa b{b}}}b cc') - assert_cursor(18) - assert_cursor_max(19) + assert_line_around_cursor('aaa b{b}}}b cc', 'c') end def test_ed_quoted_insert input_keys("ab\C-v\C-acd") - assert_line("ab\C-acd") - assert_byte_pointer_size("ab\C-acd") - assert_cursor(6) - assert_cursor_max(6) + assert_line_around_cursor("ab\C-acd", '') end def test_ed_quoted_insert_with_vi_arg input_keys("ab\C-[3\C-v\C-aacd") - assert_line("a\C-a\C-a\C-abcd") - assert_byte_pointer_size("a\C-a\C-a\C-abcd") - assert_cursor(10) - assert_cursor_max(10) + assert_line_around_cursor("a\C-a\C-a\C-abcd", '') end def test_vi_replace_char input_keys("abcdef\C-[03l") - assert_line('abcdef') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(6) + assert_line_around_cursor('abc', 'def') input_keys('rz') - assert_line('abczef') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(6) + assert_line_around_cursor('abc', 'zef') input_keys('2rx') - assert_line('abcxxf') - assert_byte_pointer_size('abcxx') - assert_cursor(5) - assert_cursor_max(6) + assert_line_around_cursor('abcxx', 'f') end def test_vi_replace_char_with_mbchar input_keys("あいうえお\C-[0l") - assert_line('あいうえお') - assert_byte_pointer_size('あ') - assert_cursor(2) - assert_cursor_max(10) + assert_line_around_cursor('あ', 'いうえお') input_keys('rx') - assert_line('あxうえお') - assert_byte_pointer_size('あ') - assert_cursor(2) - assert_cursor_max(9) + assert_line_around_cursor('あ', 'xうえお') input_keys('l2ry') - assert_line('あxyyお') - assert_byte_pointer_size('あxyy') - assert_cursor(5) - assert_cursor_max(7) + assert_line_around_cursor('あxyy', 'お') end def test_vi_next_char input_keys("abcdef\C-[0") - assert_line('abcdef') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', 'abcdef') input_keys('fz') - assert_line('abcdef') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', 'abcdef') input_keys('fe') - assert_line('abcdef') - assert_byte_pointer_size('abcd') - assert_cursor(4) - assert_cursor_max(6) + assert_line_around_cursor('abcd', 'ef') end def test_vi_to_next_char input_keys("abcdef\C-[0") - assert_line('abcdef') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', 'abcdef') input_keys('tz') - assert_line('abcdef') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', 'abcdef') input_keys('te') - assert_line('abcdef') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(6) + assert_line_around_cursor('abc', 'def') end def test_vi_prev_char input_keys("abcdef\C-[") - assert_line('abcdef') - assert_byte_pointer_size('abcde') - assert_cursor(5) - assert_cursor_max(6) + assert_line_around_cursor('abcde', 'f') input_keys('Fz') - assert_line('abcdef') - assert_byte_pointer_size('abcde') - assert_cursor(5) - assert_cursor_max(6) + assert_line_around_cursor('abcde', 'f') input_keys('Fa') - assert_line('abcdef') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', 'abcdef') end def test_vi_to_prev_char input_keys("abcdef\C-[") - assert_line('abcdef') - assert_byte_pointer_size('abcde') - assert_cursor(5) - assert_cursor_max(6) + assert_line_around_cursor('abcde', 'f') input_keys('Tz') - assert_line('abcdef') - assert_byte_pointer_size('abcde') - assert_cursor(5) - assert_cursor_max(6) + assert_line_around_cursor('abcde', 'f') input_keys('Ta') - assert_line('abcdef') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(6) + assert_line_around_cursor('a', 'bcdef') end def test_vi_delete_next_char input_keys("abc\C-[h") - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(3) - assert_line('abc') + assert_line_around_cursor('a', 'bc') input_keys('x') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(2) - assert_line('ac') + assert_line_around_cursor('a', 'c') input_keys('x') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(1) - assert_line('a') + assert_line_around_cursor('', 'a') input_keys('x') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys('x') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_vi_delete_next_char_for_mbchar input_keys("あいう\C-[h") - assert_byte_pointer_size('あ') - assert_cursor(2) - assert_cursor_max(6) - assert_line('あいう') + assert_line_around_cursor('あ', 'いう') input_keys('x') - assert_byte_pointer_size('あ') - assert_cursor(2) - assert_cursor_max(4) - assert_line('あう') + assert_line_around_cursor('あ', 'う') input_keys('x') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(2) - assert_line('あ') + assert_line_around_cursor('', 'あ') input_keys('x') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys('x') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_vi_delete_next_char_for_mbchar_by_plural_code_points input_keys("か\u3099き\u3099く\u3099\C-[h") - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(6) - assert_line("か\u3099き\u3099く\u3099") + assert_line_around_cursor("か\u3099", "き\u3099く\u3099") input_keys('x') - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(4) - assert_line("か\u3099く\u3099") + assert_line_around_cursor("か\u3099", "く\u3099") input_keys('x') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(2) - assert_line("か\u3099") + assert_line_around_cursor('', "か\u3099") input_keys('x') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys('x') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_vi_delete_prev_char input_keys('ab') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('ab', '') input_keys("\C-h") - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(1) - assert_line('a') + assert_line_around_cursor('a', '') end def test_vi_delete_prev_char_for_mbchar input_keys('かき') - assert_byte_pointer_size('かき') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('かき', '') input_keys("\C-h") - assert_byte_pointer_size('か') - assert_cursor(2) - assert_cursor_max(2) - assert_line('か') + assert_line_around_cursor('か', '') end def test_vi_delete_prev_char_for_mbchar_by_plural_code_points input_keys("か\u3099き\u3099") - assert_byte_pointer_size("か\u3099き\u3099") - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor("か\u3099き\u3099", '') input_keys("\C-h") - assert_byte_pointer_size("か\u3099") - assert_cursor(2) - assert_cursor_max(2) - assert_line("か\u3099") + assert_line_around_cursor("か\u3099", '') end def test_ed_delete_prev_char input_keys("abcdefg\C-[h") - assert_byte_pointer_size('abcde') - assert_cursor(5) - assert_cursor_max(7) - assert_line('abcdefg') + assert_line_around_cursor('abcde', 'fg') input_keys('X') - assert_byte_pointer_size('abcd') - assert_cursor(4) - assert_cursor_max(6) - assert_line('abcdfg') + assert_line_around_cursor('abcd', 'fg') input_keys('3X') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(3) - assert_line('afg') + assert_line_around_cursor('a', 'fg') input_keys('p') - assert_byte_pointer_size('abcd') - assert_cursor(4) - assert_cursor_max(6) - assert_line('afbcdg') + assert_line_around_cursor('afbc', 'dg') end def test_ed_delete_prev_word input_keys('abc def{bbb}ccc') - assert_byte_pointer_size('abc def{bbb}ccc') - assert_cursor(15) - assert_cursor_max(15) + assert_line_around_cursor('abc def{bbb}ccc', '') input_keys("\C-w") - assert_byte_pointer_size('abc def{bbb}') - assert_cursor(12) - assert_cursor_max(12) - assert_line('abc def{bbb}') + assert_line_around_cursor('abc def{bbb}', '') input_keys("\C-w") - assert_byte_pointer_size('abc def{') - assert_cursor(8) - assert_cursor_max(8) - assert_line('abc def{') + assert_line_around_cursor('abc def{', '') input_keys("\C-w") - assert_byte_pointer_size('abc ') - assert_cursor(4) - assert_cursor_max(4) - assert_line('abc ') + assert_line_around_cursor('abc ', '') input_keys("\C-w") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_ed_delete_prev_word_for_mbchar input_keys('あいう かきく{さしす}たちつ') - assert_byte_pointer_size('あいう かきく{さしす}たちつ') - assert_cursor(27) - assert_cursor_max(27) + assert_line_around_cursor('あいう かきく{さしす}たちつ', '') input_keys("\C-w") - assert_byte_pointer_size('あいう かきく{さしす}') - assert_cursor(21) - assert_cursor_max(21) - assert_line('あいう かきく{さしす}') + assert_line_around_cursor('あいう かきく{さしす}', '') input_keys("\C-w") - assert_byte_pointer_size('あいう かきく{') - assert_cursor(14) - assert_cursor_max(14) - assert_line('あいう かきく{') + assert_line_around_cursor('あいう かきく{', '') input_keys("\C-w") - assert_byte_pointer_size('あいう ') - assert_cursor(7) - assert_cursor_max(7) - assert_line('あいう ') + assert_line_around_cursor('あいう ', '') input_keys("\C-w") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_ed_delete_prev_word_for_mbchar_by_plural_code_points input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ") - assert_cursor(27) - assert_cursor_max(27) + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}たちつ", '') input_keys("\C-w") - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}") - assert_cursor(21) - assert_cursor_max(21) - assert_line("あいう か\u3099き\u3099く\u3099{さしす}") + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{さしす}", '') input_keys("\C-w") - assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{") - assert_cursor(14) - assert_cursor_max(14) - assert_line("あいう か\u3099き\u3099く\u3099{") + assert_line_around_cursor("あいう か\u3099き\u3099く\u3099{", '') input_keys("\C-w") - assert_byte_pointer_size('あいう ') - assert_cursor(7) - assert_cursor_max(7) - assert_line('あいう ') + assert_line_around_cursor('あいう ', '') input_keys("\C-w") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_ed_newline_with_cr input_keys('ab') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('ab', '') refute(@line_editor.finished?) input_keys("\C-m") - assert_line('ab') + assert_line_around_cursor('ab', '') assert(@line_editor.finished?) end def test_ed_newline_with_lf input_keys('ab') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('ab', '') refute(@line_editor.finished?) input_keys("\C-j") - assert_line('ab') + assert_line_around_cursor('ab', '') assert(@line_editor.finished?) end def test_vi_list_or_eof input_keys("\C-d") # quit from inputing - assert_line(nil) + assert_nil(@line_editor.line) assert(@line_editor.finished?) end def test_vi_list_or_eof_with_non_empty_line input_keys('ab') - assert_byte_pointer_size('ab') - assert_cursor(2) - assert_cursor_max(2) + assert_line_around_cursor('ab', '') refute(@line_editor.finished?) input_keys("\C-d") - assert_line('ab') + assert_line_around_cursor('ab', '') assert(@line_editor.finished?) end @@ -982,40 +545,19 @@ def test_completion_journey } } input_keys('foo') - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') input_keys("\C-n") - assert_byte_pointer_size('foo_bar') - assert_cursor(7) - assert_cursor_max(7) - assert_line('foo_bar') + assert_line_around_cursor('foo_bar', '') input_keys("\C-n") - assert_byte_pointer_size('foo_bar_baz') - assert_cursor(11) - assert_cursor_max(11) - assert_line('foo_bar_baz') + assert_line_around_cursor('foo_bar_baz', '') input_keys("\C-n") - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') input_keys("\C-n") - assert_byte_pointer_size('foo_bar') - assert_cursor(7) - assert_cursor_max(7) - assert_line('foo_bar') + assert_line_around_cursor('foo_bar', '') input_keys("_\C-n") - assert_byte_pointer_size('foo_bar_baz') - assert_cursor(11) - assert_cursor_max(11) - assert_line('foo_bar_baz') + assert_line_around_cursor('foo_bar_baz', '') input_keys("\C-n") - assert_byte_pointer_size('foo_bar_') - assert_cursor(8) - assert_cursor_max(8) - assert_line('foo_bar_') + assert_line_around_cursor('foo_bar_', '') end def test_completion_journey_reverse @@ -1028,40 +570,19 @@ def test_completion_journey_reverse } } input_keys('foo') - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') input_keys("\C-p") - assert_byte_pointer_size('foo_bar_baz') - assert_cursor(11) - assert_cursor_max(11) - assert_line('foo_bar_baz') + assert_line_around_cursor('foo_bar_baz', '') input_keys("\C-p") - assert_byte_pointer_size('foo_bar') - assert_cursor(7) - assert_cursor_max(7) - assert_line('foo_bar') + assert_line_around_cursor('foo_bar', '') input_keys("\C-p") - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') input_keys("\C-p") - assert_byte_pointer_size('foo_bar_baz') - assert_cursor(11) - assert_cursor_max(11) - assert_line('foo_bar_baz') + assert_line_around_cursor('foo_bar_baz', '') input_keys("\C-h\C-p") - assert_byte_pointer_size('foo_bar_baz') - assert_cursor(11) - assert_cursor_max(11) - assert_line('foo_bar_baz') + assert_line_around_cursor('foo_bar_baz', '') input_keys("\C-p") - assert_byte_pointer_size('foo_bar_ba') - assert_cursor(10) - assert_cursor_max(10) - assert_line('foo_bar_ba') + assert_line_around_cursor('foo_bar_ba', '') end def test_completion_journey_in_middle_of_line @@ -1074,42 +595,21 @@ def test_completion_journey_in_middle_of_line } } input_keys('abcde fo ABCDE') - assert_line('abcde fo ABCDE') + assert_line_around_cursor('abcde fo ABCDE', '') input_keys("\C-[" + 'h' * 5 + "i\C-n") - assert_byte_pointer_size('abcde foo_bar') - assert_cursor(13) - assert_cursor_max(19) - assert_line('abcde foo_bar ABCDE') + assert_line_around_cursor('abcde foo_bar', ' ABCDE') input_keys("\C-n") - assert_byte_pointer_size('abcde foo_bar_baz') - assert_cursor(17) - assert_cursor_max(23) - assert_line('abcde foo_bar_baz ABCDE') + assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE') input_keys("\C-n") - assert_byte_pointer_size('abcde fo') - assert_cursor(8) - assert_cursor_max(14) - assert_line('abcde fo ABCDE') + assert_line_around_cursor('abcde fo', ' ABCDE') input_keys("\C-n") - assert_byte_pointer_size('abcde foo_bar') - assert_cursor(13) - assert_cursor_max(19) - assert_line('abcde foo_bar ABCDE') + assert_line_around_cursor('abcde foo_bar', ' ABCDE') input_keys("_\C-n") - assert_byte_pointer_size('abcde foo_bar_baz') - assert_cursor(17) - assert_cursor_max(23) - assert_line('abcde foo_bar_baz ABCDE') + assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE') input_keys("\C-n") - assert_byte_pointer_size('abcde foo_bar_') - assert_cursor(14) - assert_cursor_max(20) - assert_line('abcde foo_bar_ ABCDE') + assert_line_around_cursor('abcde foo_bar_', ' ABCDE') input_keys("\C-n") - assert_byte_pointer_size('abcde foo_bar_baz') - assert_cursor(17) - assert_cursor_max(23) - assert_line('abcde foo_bar_baz ABCDE') + assert_line_around_cursor('abcde foo_bar_baz', ' ABCDE') end def test_completion @@ -1122,15 +622,9 @@ def test_completion } } input_keys('foo') - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') input_keys("\C-i") - assert_byte_pointer_size('foo_bar') - assert_cursor(7) - assert_cursor_max(7) - assert_line('foo_bar') + assert_line_around_cursor('foo_bar', '') end def test_completion_with_disable_completion @@ -1144,324 +638,160 @@ def test_completion_with_disable_completion } } input_keys('foo') - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') input_keys("\C-i") - assert_byte_pointer_size('foo') - assert_cursor(3) - assert_cursor_max(3) - assert_line('foo') + assert_line_around_cursor('foo', '') end def test_vi_first_print input_keys("abcde\C-[^") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) + assert_line_around_cursor('', 'abcde') input_keys("0\C-ki") input_keys(" abcde\C-[^") - assert_byte_pointer_size(' ') - assert_cursor(1) - assert_cursor_max(6) + assert_line_around_cursor(' ', 'abcde') input_keys("0\C-ki") input_keys(" abcde ABCDE \C-[^") - assert_byte_pointer_size(' ') - assert_cursor(3) - assert_cursor_max(17) + assert_line_around_cursor(' ', 'abcde ABCDE ') end def test_ed_move_to_beg input_keys("abcde\C-[0") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(5) + assert_line_around_cursor('', 'abcde') input_keys("0\C-ki") input_keys(" abcde\C-[0") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(6) + assert_line_around_cursor('', ' abcde') input_keys("0\C-ki") input_keys(" abcde ABCDE \C-[0") - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(17) + assert_line_around_cursor('', ' abcde ABCDE ') end def test_vi_delete_meta input_keys("aaa bbb ccc ddd eee\C-[02w") - assert_byte_pointer_size('aaa bbb ') - assert_cursor(8) - assert_cursor_max(19) - assert_line('aaa bbb ccc ddd eee') + assert_line_around_cursor('aaa bbb ', 'ccc ddd eee') input_keys('dw') - assert_byte_pointer_size('aaa bbb ') - assert_cursor(8) - assert_cursor_max(15) - assert_line('aaa bbb ddd eee') + assert_line_around_cursor('aaa bbb ', 'ddd eee') input_keys('db') - assert_byte_pointer_size('aaa ') - assert_cursor(4) - assert_cursor_max(11) - assert_line('aaa ddd eee') + assert_line_around_cursor('aaa ', 'ddd eee') end def test_vi_delete_meta_with_vi_next_word_at_eol input_keys("foo bar\C-[0w") - assert_byte_pointer_size('foo ') - assert_cursor(4) - assert_cursor_max(7) - assert_line('foo bar') + assert_line_around_cursor('foo ', 'bar') input_keys('w') - assert_byte_pointer_size('foo ba') - assert_cursor(6) - assert_cursor_max(7) - assert_line('foo bar') + assert_line_around_cursor('foo ba', 'r') input_keys('0dw') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) - assert_line('bar') + assert_line_around_cursor('', 'bar') input_keys('dw') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') end def test_vi_delete_meta_with_vi_next_char input_keys("aaa bbb ccc ___ ddd\C-[02w") - assert_byte_pointer_size('aaa bbb ') - assert_cursor(8) - assert_cursor_max(19) - assert_line('aaa bbb ccc ___ ddd') + assert_line_around_cursor('aaa bbb ', 'ccc ___ ddd') input_keys('df_') - assert_byte_pointer_size('aaa bbb ') - assert_cursor(8) - assert_cursor_max(14) - assert_line('aaa bbb __ ddd') + assert_line_around_cursor('aaa bbb ', '__ ddd') end def test_vi_delete_meta_with_arg input_keys("aaa bbb ccc\C-[02w") - assert_byte_pointer_size('aaa bbb ') - assert_cursor(8) - assert_cursor_max(11) - assert_line('aaa bbb ccc') + assert_line_around_cursor('aaa bbb ', 'ccc') input_keys('2dl') - assert_byte_pointer_size('aaa bbb ') - assert_cursor(8) - assert_cursor_max(9) - assert_line('aaa bbb c') + assert_line_around_cursor('aaa bbb ', 'c') end def test_vi_change_meta input_keys("aaa bbb ccc ddd eee\C-[02w") - assert_byte_pointer_size('aaa bbb ') - assert_cursor(8) - assert_cursor_max(19) - assert_line('aaa bbb ccc ddd eee') + assert_line_around_cursor('aaa bbb ', 'ccc ddd eee') input_keys('cwaiueo') - assert_byte_pointer_size('aaa bbb aiueo') - assert_cursor(13) - assert_cursor_max(21) - assert_line('aaa bbb aiueo ddd eee') + assert_line_around_cursor('aaa bbb aiueo', ' ddd eee') input_keys("\C-[") - assert_byte_pointer_size('aaa bbb aiue') - assert_cursor(12) - assert_cursor_max(21) - assert_line('aaa bbb aiueo ddd eee') + assert_line_around_cursor('aaa bbb aiue', 'o ddd eee') input_keys('cb') - assert_byte_pointer_size('aaa bbb ') - assert_cursor(8) - assert_cursor_max(17) - assert_line('aaa bbb o ddd eee') + assert_line_around_cursor('aaa bbb ', 'o ddd eee') end def test_vi_change_meta_with_vi_next_word input_keys("foo bar baz\C-[0w") - assert_byte_pointer_size('foo ') - assert_cursor(5) - assert_cursor_max(13) - assert_line('foo bar baz') + assert_line_around_cursor('foo ', 'bar baz') input_keys('cwhoge') - assert_byte_pointer_size('foo hoge') - assert_cursor(9) - assert_cursor_max(14) - assert_line('foo hoge baz') + assert_line_around_cursor('foo hoge', ' baz') input_keys("\C-[") - assert_byte_pointer_size('foo hog') - assert_cursor(8) - assert_cursor_max(14) - assert_line('foo hoge baz') + assert_line_around_cursor('foo hog', 'e baz') end def test_unimplemented_vi_command_should_be_no_op input_keys("abc\C-[h") - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(3) - assert_line('abc') + assert_line_around_cursor('a', 'bc') input_keys('@') - assert_byte_pointer_size('a') - assert_cursor(1) - assert_cursor_max(3) - assert_line('abc') + assert_line_around_cursor('a', 'bc') end def test_vi_yank input_keys("foo bar\C-[0") - assert_line('foo bar') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(7) + assert_line_around_cursor('', 'foo bar') input_keys('y3l') - assert_line('foo bar') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(7) + assert_line_around_cursor('', 'foo bar') input_keys('P') - assert_line('foofoo bar') - assert_byte_pointer_size('fo') - assert_cursor(2) - assert_cursor_max(10) + assert_line_around_cursor('fo', 'ofoo bar') end def test_vi_end_word_with_operator input_keys("foo bar\C-[0") - assert_line('foo bar') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(7) + assert_line_around_cursor('', 'foo bar') input_keys('de') - assert_line(' bar') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(4) + assert_line_around_cursor('', ' bar') input_keys('de') - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys('de') - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') end def test_vi_end_big_word_with_operator input_keys("aaa b{b}}}b\C-[0") - assert_line('aaa b{b}}}b') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(13) + assert_line_around_cursor('', 'aaa b{b}}}b') input_keys('dE') - assert_line(' b{b}}}b') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(10) + assert_line_around_cursor('', ' b{b}}}b') input_keys('dE') - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') input_keys('dE') - assert_line('') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') end def test_vi_next_char_with_operator input_keys("foo bar\C-[0") - assert_line('foo bar') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(7) + assert_line_around_cursor('', 'foo bar') input_keys('df ') - assert_line('bar') - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(3) - end - - def test_pasting - start_pasting - input_keys('ab') - finish_pasting - input_keys('c') - assert_line('abc') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(3) - end - - def test_pasting_fullwidth - start_pasting - input_keys('あ') - finish_pasting - input_keys('い') - assert_line('あい') - assert_byte_pointer_size('あい') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('', 'bar') end def test_ed_delete_next_char_at_eol input_keys('"あ"') - assert_line('"あ"') - assert_byte_pointer_size('"あ"') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('"あ"', '') input_keys("\C-[") - assert_line('"あ"') - assert_byte_pointer_size('"あ') - assert_cursor(3) - assert_cursor_max(4) + assert_line_around_cursor('"あ', '"') input_keys('xa"') - assert_line('"あ"') - assert_byte_pointer_size('"あ"') - assert_cursor(4) - assert_cursor_max(4) + assert_line_around_cursor('"あ"', '') end def test_vi_kill_line_prev input_keys("\C-u", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys('abc') - assert_byte_pointer_size('abc') - assert_cursor(3) - assert_cursor_max(3) + assert_line_around_cursor('abc', '') input_keys("\C-u", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(0) - assert_line('') + assert_line_around_cursor('', '') input_keys('abc') input_keys("\C-[\C-u", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(1) - assert_line('c') + assert_line_around_cursor('', 'c') input_keys("\C-u", false) - assert_byte_pointer_size('') - assert_cursor(0) - assert_cursor_max(1) - assert_line('c') + assert_line_around_cursor('', 'c') end def test_vi_change_to_eol input_keys("abcdef\C-[2hC") - assert_line("abc") + assert_line_around_cursor('abc', '') input_keys("\C-[0C") - assert_line("") - assert_cursor(0) - assert_cursor_max(0) + assert_line_around_cursor('', '') assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode) end diff --git a/test/reline/test_line_editor.rb b/test/reline/test_line_editor.rb index 0963d2c8fe835c..bf688ac3c6f193 100644 --- a/test/reline/test_line_editor.rb +++ b/test/reline/test_line_editor.rb @@ -127,4 +127,29 @@ def test_complicated end end end + + def test_menu_info_format + list = %w[aa b c d e f g hhh i j k] + col3 = [ + 'aa e i', + 'b f j', + 'c g k', + 'd hhh' + ] + col2 = [ + 'aa g', + 'b hhh', + 'c i', + 'd j', + 'e k', + 'f' + ] + assert_equal(col3, Reline::LineEditor::MenuInfo.new(list).lines(19)) + assert_equal(col3, Reline::LineEditor::MenuInfo.new(list).lines(15)) + assert_equal(col2, Reline::LineEditor::MenuInfo.new(list).lines(14)) + assert_equal(col2, Reline::LineEditor::MenuInfo.new(list).lines(10)) + assert_equal(list, Reline::LineEditor::MenuInfo.new(list).lines(9)) + assert_equal(list, Reline::LineEditor::MenuInfo.new(list).lines(0)) + assert_equal([], Reline::LineEditor::MenuInfo.new([]).lines(10)) + end end diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb index 527da519c1c78c..53c842a04a41c7 100644 --- a/test/reline/yamatanooroti/test_rendering.rb +++ b/test/reline/yamatanooroti/test_rendering.rb @@ -197,9 +197,12 @@ def test_mode_string_vi LINES start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl}, startup_message: 'Multiline REPL.') write(":a\n\C-[k") + write("i\n:a") + write("\C-[h") close assert_screen(<<~EOC) - Multiline REPL. + (ins)prompt> :a + => :a (ins)prompt> :a => :a (cmd)prompt> :a @@ -1105,6 +1108,19 @@ def test_autocomplete_target_is_wrapped EOC end + def test_force_insert_before_autocomplete + start_terminal(20, 20, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.') + write('Sy') + write(";St\t\t") + close + assert_screen(<<~'EOC') + Multiline REPL. + prompt> Sy;Struct + String + Struct + EOC + end + def test_simple_dialog_with_scroll_key start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog long,scrollkey}, startup_message: 'Multiline REPL.') write('a') diff --git a/test/resolv/test_dns.rb b/test/resolv/test_dns.rb index 20c3408cd6b9ca..40c5406db83525 100644 --- a/test/resolv/test_dns.rb +++ b/test/resolv/test_dns.rb @@ -64,6 +64,50 @@ def with_udp(host, port) end end + def with_udp_and_tcp(host, port) + if port == 0 + # Automatic port; we might need to retry until we find a port which is free on both UDP _and_ TCP. + retries_remaining = 5 + t = nil + u = nil + begin + begin + u = UDPSocket.new + u.bind(host, 0) + _, udp_port, _, _ = u.addr + t = TCPServer.new(host, udp_port) + t.listen(1) + rescue Errno::EADDRINUSE, Errno::EACCES + # ADDRINUSE is what should get thrown if we try and bind a port which is already bound on UNIXen, + # but windows can sometimes throw EACCESS. + # See: https://stackoverflow.com/questions/48478869/cannot-bind-to-some-ports-due-to-permission-denied + retries_remaining -= 1 + if retries_remaining > 0 + t&.close + t = nil + u&.close + u = nil + retry + end + raise + end + + # If we get to this point, we have a valid t & u socket + yield u, t + ensure + t&.close + u&.close + end + else + # Explicitly specified port, don't retry the bind. + with_udp(host, port) do |u| + with_tcp(host, port) do |t| + yield u, t + end + end + end + end + # [ruby-core:65836] def test_resolve_with_2_ndots conf = Resolv::DNS::Config.new :nameserver => ['127.0.0.1'], :ndots => 2 @@ -176,156 +220,154 @@ def test_query_ipv4_address_truncated_tcp_fallback num_records = 50 - with_udp('127.0.0.1', 0) {|u| + with_udp_and_tcp('127.0.0.1', 0) {|u, t| _, server_port, _, server_address = u.addr - with_tcp('127.0.0.1', server_port) {|t| - client_thread = Thread.new { - Resolv::DNS.open(:nameserver_port => [[server_address, server_port]]) {|dns| - dns.getresources("foo.example.org", Resolv::DNS::Resource::IN::A) - } - } - udp_server_thread = Thread.new { - msg, (_, client_port, _, client_address) = Timeout.timeout(5) {u.recvfrom(4096)} - id, word2, qdcount, ancount, nscount, arcount = msg.unpack("nnnnnn") - qr = (word2 & 0x8000) >> 15 - opcode = (word2 & 0x7800) >> 11 - aa = (word2 & 0x0400) >> 10 - tc = (word2 & 0x0200) >> 9 - rd = (word2 & 0x0100) >> 8 - ra = (word2 & 0x0080) >> 7 - z = (word2 & 0x0070) >> 4 - rcode = word2 & 0x000f - rest = msg[12..-1] - assert_equal(0, qr) # 0:query 1:response - assert_equal(0, opcode) # 0:QUERY 1:IQUERY 2:STATUS - assert_equal(0, aa) # Authoritative Answer - assert_equal(0, tc) # TrunCation - assert_equal(1, rd) # Recursion Desired - assert_equal(0, ra) # Recursion Available - assert_equal(0, z) # Reserved for future use - assert_equal(0, rcode) # 0:No-error 1:Format-error 2:Server-failure 3:Name-Error 4:Not-Implemented 5:Refused - assert_equal(1, qdcount) # number of entries in the question section. - assert_equal(0, ancount) # number of entries in the answer section. - assert_equal(0, nscount) # number of entries in the authority records section. - assert_equal(0, arcount) # number of entries in the additional records section. - name = [3, "foo", 7, "example", 3, "org", 0].pack("Ca*Ca*Ca*C") - assert_operator(rest, :start_with?, name) - rest = rest[name.length..-1] - assert_equal(4, rest.length) - qtype, _ = rest.unpack("nn") - assert_equal(1, qtype) # A - assert_equal(1, qtype) # IN - id = id - qr = 1 - opcode = opcode - aa = 0 - tc = 1 - rd = rd - ra = 1 - z = 0 - rcode = 0 - qdcount = 0 - ancount = num_records - nscount = 0 - arcount = 0 - word2 = (qr << 15) | - (opcode << 11) | - (aa << 10) | - (tc << 9) | - (rd << 8) | - (ra << 7) | - (z << 4) | - rcode - msg = [id, word2, qdcount, ancount, nscount, arcount].pack("nnnnnn") - type = 1 - klass = 1 - ttl = 3600 - rdlength = 4 - num_records.times do |i| - rdata = [192,0,2,i].pack("CCCC") # 192.0.2.x (TEST-NET address) RFC 3330 - rr = [name, type, klass, ttl, rdlength, rdata].pack("a*nnNna*") - msg << rr - end - u.send(msg[0...512], 0, client_address, client_port) - } - tcp_server_thread = Thread.new { - ct = t.accept - msg = ct.recv(512) - msg.slice!(0..1) # Size (only for TCP) - id, word2, qdcount, ancount, nscount, arcount = msg.unpack("nnnnnn") - qr = (word2 & 0x8000) >> 15 - opcode = (word2 & 0x7800) >> 11 - aa = (word2 & 0x0400) >> 10 - tc = (word2 & 0x0200) >> 9 - rd = (word2 & 0x0100) >> 8 - ra = (word2 & 0x0080) >> 7 - z = (word2 & 0x0070) >> 4 - rcode = word2 & 0x000f - rest = msg[12..-1] - assert_equal(0, qr) # 0:query 1:response - assert_equal(0, opcode) # 0:QUERY 1:IQUERY 2:STATUS - assert_equal(0, aa) # Authoritative Answer - assert_equal(0, tc) # TrunCation - assert_equal(1, rd) # Recursion Desired - assert_equal(0, ra) # Recursion Available - assert_equal(0, z) # Reserved for future use - assert_equal(0, rcode) # 0:No-error 1:Format-error 2:Server-failure 3:Name-Error 4:Not-Implemented 5:Refused - assert_equal(1, qdcount) # number of entries in the question section. - assert_equal(0, ancount) # number of entries in the answer section. - assert_equal(0, nscount) # number of entries in the authority records section. - assert_equal(0, arcount) # number of entries in the additional records section. - name = [3, "foo", 7, "example", 3, "org", 0].pack("Ca*Ca*Ca*C") - assert_operator(rest, :start_with?, name) - rest = rest[name.length..-1] - assert_equal(4, rest.length) - qtype, _ = rest.unpack("nn") - assert_equal(1, qtype) # A - assert_equal(1, qtype) # IN - id = id - qr = 1 - opcode = opcode - aa = 0 - tc = 0 - rd = rd - ra = 1 - z = 0 - rcode = 0 - qdcount = 0 - ancount = num_records - nscount = 0 - arcount = 0 - word2 = (qr << 15) | - (opcode << 11) | - (aa << 10) | - (tc << 9) | - (rd << 8) | - (ra << 7) | - (z << 4) | - rcode - msg = [id, word2, qdcount, ancount, nscount, arcount].pack("nnnnnn") - type = 1 - klass = 1 - ttl = 3600 - rdlength = 4 - num_records.times do |i| - rdata = [192,0,2,i].pack("CCCC") # 192.0.2.x (TEST-NET address) RFC 3330 - rr = [name, type, klass, ttl, rdlength, rdata].pack("a*nnNna*") - msg << rr - end - msg = "#{[msg.bytesize].pack("n")}#{msg}" # Prefix with size - ct.send(msg, 0) - ct.close + client_thread = Thread.new { + Resolv::DNS.open(:nameserver_port => [[server_address, server_port]]) {|dns| + dns.getresources("foo.example.org", Resolv::DNS::Resource::IN::A) } - result, _ = assert_join_threads([client_thread, udp_server_thread, tcp_server_thread]) - assert_instance_of(Array, result) - assert_equal(50, result.length) - result.each_with_index do |rr, i| - assert_instance_of(Resolv::DNS::Resource::IN::A, rr) - assert_instance_of(Resolv::IPv4, rr.address) - assert_equal("192.0.2.#{i}", rr.address.to_s) - assert_equal(3600, rr.ttl) + } + udp_server_thread = Thread.new { + msg, (_, client_port, _, client_address) = Timeout.timeout(5) {u.recvfrom(4096)} + id, word2, qdcount, ancount, nscount, arcount = msg.unpack("nnnnnn") + qr = (word2 & 0x8000) >> 15 + opcode = (word2 & 0x7800) >> 11 + aa = (word2 & 0x0400) >> 10 + tc = (word2 & 0x0200) >> 9 + rd = (word2 & 0x0100) >> 8 + ra = (word2 & 0x0080) >> 7 + z = (word2 & 0x0070) >> 4 + rcode = word2 & 0x000f + rest = msg[12..-1] + assert_equal(0, qr) # 0:query 1:response + assert_equal(0, opcode) # 0:QUERY 1:IQUERY 2:STATUS + assert_equal(0, aa) # Authoritative Answer + assert_equal(0, tc) # TrunCation + assert_equal(1, rd) # Recursion Desired + assert_equal(0, ra) # Recursion Available + assert_equal(0, z) # Reserved for future use + assert_equal(0, rcode) # 0:No-error 1:Format-error 2:Server-failure 3:Name-Error 4:Not-Implemented 5:Refused + assert_equal(1, qdcount) # number of entries in the question section. + assert_equal(0, ancount) # number of entries in the answer section. + assert_equal(0, nscount) # number of entries in the authority records section. + assert_equal(0, arcount) # number of entries in the additional records section. + name = [3, "foo", 7, "example", 3, "org", 0].pack("Ca*Ca*Ca*C") + assert_operator(rest, :start_with?, name) + rest = rest[name.length..-1] + assert_equal(4, rest.length) + qtype, _ = rest.unpack("nn") + assert_equal(1, qtype) # A + assert_equal(1, qtype) # IN + id = id + qr = 1 + opcode = opcode + aa = 0 + tc = 1 + rd = rd + ra = 1 + z = 0 + rcode = 0 + qdcount = 0 + ancount = num_records + nscount = 0 + arcount = 0 + word2 = (qr << 15) | + (opcode << 11) | + (aa << 10) | + (tc << 9) | + (rd << 8) | + (ra << 7) | + (z << 4) | + rcode + msg = [id, word2, qdcount, ancount, nscount, arcount].pack("nnnnnn") + type = 1 + klass = 1 + ttl = 3600 + rdlength = 4 + num_records.times do |i| + rdata = [192,0,2,i].pack("CCCC") # 192.0.2.x (TEST-NET address) RFC 3330 + rr = [name, type, klass, ttl, rdlength, rdata].pack("a*nnNna*") + msg << rr end + u.send(msg[0...512], 0, client_address, client_port) } + tcp_server_thread = Thread.new { + ct = t.accept + msg = ct.recv(512) + msg.slice!(0..1) # Size (only for TCP) + id, word2, qdcount, ancount, nscount, arcount = msg.unpack("nnnnnn") + qr = (word2 & 0x8000) >> 15 + opcode = (word2 & 0x7800) >> 11 + aa = (word2 & 0x0400) >> 10 + tc = (word2 & 0x0200) >> 9 + rd = (word2 & 0x0100) >> 8 + ra = (word2 & 0x0080) >> 7 + z = (word2 & 0x0070) >> 4 + rcode = word2 & 0x000f + rest = msg[12..-1] + assert_equal(0, qr) # 0:query 1:response + assert_equal(0, opcode) # 0:QUERY 1:IQUERY 2:STATUS + assert_equal(0, aa) # Authoritative Answer + assert_equal(0, tc) # TrunCation + assert_equal(1, rd) # Recursion Desired + assert_equal(0, ra) # Recursion Available + assert_equal(0, z) # Reserved for future use + assert_equal(0, rcode) # 0:No-error 1:Format-error 2:Server-failure 3:Name-Error 4:Not-Implemented 5:Refused + assert_equal(1, qdcount) # number of entries in the question section. + assert_equal(0, ancount) # number of entries in the answer section. + assert_equal(0, nscount) # number of entries in the authority records section. + assert_equal(0, arcount) # number of entries in the additional records section. + name = [3, "foo", 7, "example", 3, "org", 0].pack("Ca*Ca*Ca*C") + assert_operator(rest, :start_with?, name) + rest = rest[name.length..-1] + assert_equal(4, rest.length) + qtype, _ = rest.unpack("nn") + assert_equal(1, qtype) # A + assert_equal(1, qtype) # IN + id = id + qr = 1 + opcode = opcode + aa = 0 + tc = 0 + rd = rd + ra = 1 + z = 0 + rcode = 0 + qdcount = 0 + ancount = num_records + nscount = 0 + arcount = 0 + word2 = (qr << 15) | + (opcode << 11) | + (aa << 10) | + (tc << 9) | + (rd << 8) | + (ra << 7) | + (z << 4) | + rcode + msg = [id, word2, qdcount, ancount, nscount, arcount].pack("nnnnnn") + type = 1 + klass = 1 + ttl = 3600 + rdlength = 4 + num_records.times do |i| + rdata = [192,0,2,i].pack("CCCC") # 192.0.2.x (TEST-NET address) RFC 3330 + rr = [name, type, klass, ttl, rdlength, rdata].pack("a*nnNna*") + msg << rr + end + msg = "#{[msg.bytesize].pack("n")}#{msg}" # Prefix with size + ct.send(msg, 0) + ct.close + } + result, _ = assert_join_threads([client_thread, udp_server_thread, tcp_server_thread]) + assert_instance_of(Array, result) + assert_equal(50, result.length) + result.each_with_index do |rr, i| + assert_instance_of(Resolv::DNS::Resource::IN::A, rr) + assert_instance_of(Resolv::IPv4, rr.address) + assert_equal("192.0.2.#{i}", rr.address.to_s) + assert_equal(3600, rr.ttl) + end } end diff --git a/test/ruby/test_allocation.rb b/test/ruby/test_allocation.rb new file mode 100644 index 00000000000000..48348c0fbd514d --- /dev/null +++ b/test/ruby/test_allocation.rb @@ -0,0 +1,656 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestAllocation < Test::Unit::TestCase + def check_allocations(checks) + assert_separately([], <<~RUBY) + $allocations = [0, 0] + $counts = {} + failures = [] + + def self.num_allocations + ObjectSpace.count_objects($counts) + arrays = $counts[:T_ARRAY] + hashes = $counts[:T_HASH] + yield + ObjectSpace.count_objects($counts) + arrays -= $counts[:T_ARRAY] + hashes -= $counts[:T_HASH] + $allocations[0] = -arrays + $allocations[1] = -hashes + end + + define_singleton_method(:check_allocations) do |num_arrays, num_hashes, check_code| + instance_eval <<~RB + empty_array = empty_array = [] + empty_hash = empty_hash = {} + array1 = array1 = [1] + hash1 = hash1 = {a: 2} + nill = nill = nil + block = block = lambda{} + + num_allocations do + \#{check_code} + end + RB + + if num_arrays != $allocations[0] + failures << "Expected \#{num_arrays} array allocations for \#{check_code.inspect}, but \#{$allocations[0]} arrays allocated" + end + if num_hashes != $allocations[1] + failures << "Expected \#{num_hashes} hash allocations for \#{check_code.inspect}, but \#{$allocations[1]} hashes allocated" + end + end + + GC.start + GC.disable + + #{checks} + + unless failures.empty? + assert_equal(true, false, failures.join("\n")) + end + RUBY + end + + class Literal < self + def test_array_literal + check_allocations(<<~RUBY) + check_allocations(1, 0, "[]") + check_allocations(1, 0, "[1]") + check_allocations(1, 0, "[*empty_array]") + check_allocations(1, 0, "[*empty_array, 1, *empty_array]") + check_allocations(1, 0, "[*empty_array, *empty_array]") + check_allocations(1, 0, "[#{'1,'*100000}]") + RUBY + end + + def test_hash_literal + check_allocations(<<~RUBY) + check_allocations(0, 1, "{}") + check_allocations(0, 1, "{a: 1}") + check_allocations(0, 1, "{**empty_hash}") + check_allocations(0, 1, "{**empty_hash, a: 1, **empty_hash}") + check_allocations(0, 1, "{**empty_hash, **empty_hash}") + check_allocations(0, 1, "{#{100000.times.map{|i| "a#{i}: 1"}.join(',')}}") + RUBY + end + end + + class MethodCall < self + def block + '' + end + + def test_no_parameters + only_block = block.empty? ? block : block[2..] + check_allocations(<<~RUBY) + def self.none(#{only_block}); end + + check_allocations(0, 0, "none(#{only_block})") + check_allocations(0, 0, "none(*empty_array#{block})") + check_allocations(0, 0, "none(**empty_hash#{block})") + check_allocations(0, 0, "none(*empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "none(*empty_array, *empty_array#{block})") + check_allocations(0, 1, "none(**empty_hash, **empty_hash#{block})") + check_allocations(1, 1, "none(*empty_array, *empty_array, **empty_hash, **empty_hash#{block})") + RUBY + end + + def test_required_parameter + check_allocations(<<~RUBY) + def self.required(x#{block}); end + + check_allocations(0, 0, "required(1#{block})") + check_allocations(0, 0, "required(1, *empty_array#{block})") + check_allocations(0, 0, "required(1, **empty_hash#{block})") + check_allocations(0, 0, "required(1, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "required(*array1#{block})") + check_allocations(0, 1, "required(**hash1#{block})") + + check_allocations(1, 0, "required(*array1, *empty_array#{block})") + check_allocations(0, 1, "required(**hash1, **empty_hash#{block})") + check_allocations(1, 0, "required(*array1, *empty_array, **empty_hash#{block})") + + # Currently allocates 1 array unnecessarily due to splatarray true + check_allocations(1, 1, "required(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_optional_parameter + check_allocations(<<~RUBY) + def self.optional(x=nil#{block}); end + + check_allocations(0, 0, "optional(1#{block})") + check_allocations(0, 0, "optional(1, *empty_array#{block})") + check_allocations(0, 0, "optional(1, **empty_hash#{block})") + check_allocations(0, 0, "optional(1, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "optional(*array1#{block})") + check_allocations(0, 1, "optional(**hash1#{block})") + + check_allocations(1, 0, "optional(*array1, *empty_array#{block})") + check_allocations(0, 1, "optional(**hash1, **empty_hash#{block})") + check_allocations(1, 0, "optional(*array1, *empty_array, **empty_hash#{block})") + + # Currently allocates 1 array unnecessarily due to splatarray true + check_allocations(1, 1, "optional(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_positional_splat_parameter + check_allocations(<<~RUBY) + def self.splat(*x#{block}); end + + check_allocations(1, 0, "splat(1#{block})") + check_allocations(1, 0, "splat(1, *empty_array#{block})") + check_allocations(1, 0, "splat(1, **empty_hash#{block})") + check_allocations(1, 0, "splat(1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat(*array1#{block})") + check_allocations(1, 0, "splat(*array1, *empty_array#{block})") + check_allocations(1, 0, "splat(*array1, **empty_hash#{block})") + check_allocations(1, 0, "splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat(1, *array1#{block})") + check_allocations(1, 0, "splat(1, *array1, *empty_array#{block})") + check_allocations(1, 0, "splat(1, *array1, **empty_hash#{block})") + check_allocations(1, 0, "splat(1, *array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat(*array1#{block})") + check_allocations(1, 1, "splat(**hash1#{block})") + + check_allocations(1, 0, "splat(*array1, *empty_array#{block})") + check_allocations(1, 1, "splat(**hash1, **empty_hash#{block})") + check_allocations(1, 0, "splat(*array1, *empty_array, **empty_hash#{block})") + check_allocations(1, 1, "splat(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_required_and_positional_splat_parameters + check_allocations(<<~RUBY) + def self.req_splat(x, *y#{block}); end + + check_allocations(1, 0, "req_splat(1#{block})") + check_allocations(1, 0, "req_splat(1, *empty_array#{block})") + check_allocations(1, 0, "req_splat(1, **empty_hash#{block})") + check_allocations(1, 0, "req_splat(1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "req_splat(*array1#{block})") + check_allocations(1, 0, "req_splat(*array1, *empty_array#{block})") + check_allocations(1, 0, "req_splat(*array1, **empty_hash#{block})") + check_allocations(1, 0, "req_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "req_splat(1, *array1#{block})") + check_allocations(1, 0, "req_splat(1, *array1, *empty_array#{block})") + check_allocations(1, 0, "req_splat(1, *array1, **empty_hash#{block})") + check_allocations(1, 0, "req_splat(1, *array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "req_splat(*array1#{block})") + check_allocations(1, 1, "req_splat(**hash1#{block})") + + check_allocations(1, 0, "req_splat(*array1, *empty_array#{block})") + check_allocations(1, 1, "req_splat(**hash1, **empty_hash#{block})") + check_allocations(1, 0, "req_splat(*array1, *empty_array, **empty_hash#{block})") + check_allocations(1, 1, "req_splat(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_positional_splat_and_post_parameters + check_allocations(<<~RUBY) + def self.splat_post(*x, y#{block}); end + + check_allocations(1, 0, "splat_post(1#{block})") + check_allocations(1, 0, "splat_post(1, *empty_array#{block})") + check_allocations(1, 0, "splat_post(1, **empty_hash#{block})") + check_allocations(1, 0, "splat_post(1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat_post(*array1#{block})") + check_allocations(1, 0, "splat_post(*array1, *empty_array#{block})") + check_allocations(1, 0, "splat_post(*array1, **empty_hash#{block})") + check_allocations(1, 0, "splat_post(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat_post(1, *array1#{block})") + check_allocations(1, 0, "splat_post(1, *array1, *empty_array#{block})") + check_allocations(1, 0, "splat_post(1, *array1, **empty_hash#{block})") + check_allocations(1, 0, "splat_post(1, *array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat_post(*array1#{block})") + check_allocations(1, 1, "splat_post(**hash1#{block})") + + check_allocations(1, 0, "splat_post(*array1, *empty_array#{block})") + check_allocations(1, 1, "splat_post(**hash1, **empty_hash#{block})") + check_allocations(1, 0, "splat_post(*array1, *empty_array, **empty_hash#{block})") + check_allocations(1, 1, "splat_post(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_keyword_parameter + check_allocations(<<~RUBY) + def self.keyword(a: nil#{block}); end + + check_allocations(0, 0, "keyword(a: 2#{block})") + check_allocations(0, 0, "keyword(*empty_array, a: 2#{block})") + check_allocations(0, 1, "keyword(a:2, **empty_hash#{block})") + check_allocations(0, 1, "keyword(**empty_hash, a: 2#{block})") + + check_allocations(0, 0, "keyword(**nil#{block})") + check_allocations(0, 0, "keyword(**empty_hash#{block})") + check_allocations(0, 0, "keyword(**hash1#{block})") + check_allocations(0, 0, "keyword(*empty_array, **hash1#{block})") + check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})") + check_allocations(0, 1, "keyword(**empty_hash, **hash1#{block})") + + check_allocations(0, 0, "keyword(*empty_array#{block})") + check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})") + check_allocations(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "keyword(*empty_array#{block})") + check_allocations(0, 1, "keyword(**hash1, **empty_hash#{block})") + check_allocations(1, 0, "keyword(*empty_array, *empty_array, **empty_hash#{block})") + + # Currently allocates 1 array unnecessarily due to splatarray true + check_allocations(1, 1, "keyword(*empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "keyword(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_keyword_splat_parameter + check_allocations(<<~RUBY) + def self.keyword_splat(**kw#{block}); end + + check_allocations(0, 1, "keyword_splat(a: 2#{block})") + check_allocations(0, 1, "keyword_splat(*empty_array, a: 2#{block})") + check_allocations(0, 1, "keyword_splat(a:2, **empty_hash#{block})") + check_allocations(0, 1, "keyword_splat(**empty_hash, a: 2#{block})") + + check_allocations(0, 1, "keyword_splat(**nil#{block})") + check_allocations(0, 1, "keyword_splat(**empty_hash#{block})") + check_allocations(0, 1, "keyword_splat(**hash1#{block})") + check_allocations(0, 1, "keyword_splat(*empty_array, **hash1#{block})") + check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})") + check_allocations(0, 1, "keyword_splat(**empty_hash, **hash1#{block})") + + check_allocations(0, 1, "keyword_splat(*empty_array#{block})") + check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})") + check_allocations(1, 1, "keyword_splat(*empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 1, "keyword_splat(*empty_array#{block})") + check_allocations(0, 1, "keyword_splat(**hash1, **empty_hash#{block})") + check_allocations(1, 1, "keyword_splat(*empty_array, *empty_array, **empty_hash#{block})") + + # Currently allocates 1 array unnecessarily due to splatarray true + check_allocations(1, 1, "keyword_splat(*empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "keyword_splat(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_keyword_and_keyword_splat_parameter + check_allocations(<<~RUBY) + def self.keyword_and_keyword_splat(a: 1, **kw#{block}); end + + check_allocations(0, 1, "keyword_and_keyword_splat(a: 2#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, a: 2#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(a:2, **empty_hash#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, a: 2#{block})") + + check_allocations(0, 1, "keyword_and_keyword_splat(**nil#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**hash1#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array, **hash1#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**empty_hash, **hash1#{block})") + + check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})") + check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 1, "keyword_and_keyword_splat(*empty_array#{block})") + check_allocations(0, 1, "keyword_and_keyword_splat(**hash1, **empty_hash#{block})") + check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, *empty_array, **empty_hash#{block})") + + # Currently allocates 1 array unnecessarily due to splatarray true + check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "keyword_and_keyword_splat(*empty_array, **hash1, **empty_hash#{block})") + RUBY + end + + def test_required_positional_and_keyword_parameter + check_allocations(<<~RUBY) + def self.required_and_keyword(b, a: nil#{block}); end + + check_allocations(0, 0, "required_and_keyword(1, a: 2#{block})") + check_allocations(0, 0, "required_and_keyword(1, *empty_array, a: 2#{block})") + check_allocations(0, 1, "required_and_keyword(1, a:2, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword(1, **empty_hash, a: 2#{block})") + + check_allocations(0, 0, "required_and_keyword(1, **nil#{block})") + check_allocations(0, 0, "required_and_keyword(1, **empty_hash#{block})") + check_allocations(0, 0, "required_and_keyword(1, **hash1#{block})") + check_allocations(0, 0, "required_and_keyword(1, *empty_array, **hash1#{block})") + check_allocations(0, 1, "required_and_keyword(1, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword(1, **empty_hash, **hash1#{block})") + + check_allocations(0, 0, "required_and_keyword(1, *empty_array#{block})") + check_allocations(0, 1, "required_and_keyword(1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "required_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "required_and_keyword(*array1, a: 2#{block})") + + check_allocations(0, 0, "required_and_keyword(*array1, **nill#{block})") + check_allocations(0, 0, "required_and_keyword(*array1, **empty_hash#{block})") + check_allocations(0, 0, "required_and_keyword(*array1, **hash1#{block})") + check_allocations(1, 0, "required_and_keyword(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "required_and_keyword(*array1, *empty_array#{block})") + check_allocations(1, 0, "required_and_keyword(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "required_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})") + + # Currently allocates 1 array unnecessarily due to splatarray true + check_allocations(1, 1, "required_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "required_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "required_and_keyword(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "required_and_keyword(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "required_and_keyword(*array1, **nil#{block})") + RUBY + end + + def test_positional_splat_and_keyword_parameter + check_allocations(<<~RUBY) + def self.splat_and_keyword(*b, a: nil#{block}); end + + check_allocations(1, 0, "splat_and_keyword(1, a: 2#{block})") + check_allocations(1, 0, "splat_and_keyword(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "splat_and_keyword(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "splat_and_keyword(1, **nil#{block})") + check_allocations(1, 0, "splat_and_keyword(1, **empty_hash#{block})") + check_allocations(1, 0, "splat_and_keyword(1, **hash1#{block})") + check_allocations(1, 0, "splat_and_keyword(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "splat_and_keyword(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "splat_and_keyword(1, *empty_array#{block})") + check_allocations(1, 1, "splat_and_keyword(1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "splat_and_keyword(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(1, 0, "splat_and_keyword(*array1, a: 2#{block})") + + check_allocations(1, 0, "splat_and_keyword(*array1, **nill#{block})") + check_allocations(1, 0, "splat_and_keyword(*array1, **empty_hash#{block})") + check_allocations(1, 0, "splat_and_keyword(*array1, **hash1#{block})") + check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array#{block})") + check_allocations(1, 0, "splat_and_keyword(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "splat_and_keyword(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 1, "splat_and_keyword(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "splat_and_keyword(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "splat_and_keyword(*array1, **nil#{block})") + RUBY + end + + def test_required_and_keyword_splat_parameter + check_allocations(<<~RUBY) + def self.required_and_keyword_splat(b, **kw#{block}); end + + check_allocations(0, 1, "required_and_keyword_splat(1, a: 2#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, a: 2#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, a:2, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, a: 2#{block})") + + check_allocations(0, 1, "required_and_keyword_splat(1, **nil#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **hash1#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array, **hash1#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **empty_hash, **hash1#{block})") + + check_allocations(0, 1, "required_and_keyword_splat(1, *empty_array#{block})") + check_allocations(0, 1, "required_and_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 1, "required_and_keyword_splat(*array1, a: 2#{block})") + + check_allocations(0, 1, "required_and_keyword_splat(*array1, **nill#{block})") + check_allocations(0, 1, "required_and_keyword_splat(*array1, **empty_hash#{block})") + check_allocations(0, 1, "required_and_keyword_splat(*array1, **hash1#{block})") + check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array#{block})") + check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "required_and_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})") + + # Currently allocates 1 array unnecessarily due to splatarray true + check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "required_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "required_and_keyword_splat(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "required_and_keyword_splat(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "required_and_keyword_splat(*array1, **nil#{block})") + RUBY + end + + def test_positional_splat_and_keyword_splat_parameter + check_allocations(<<~RUBY) + def self.splat_and_keyword_splat(*b, **kw#{block}); end + + check_allocations(1, 1, "splat_and_keyword_splat(1, a: 2#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(1, **nil#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(*array1, a: 2#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **nill#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **hash1#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "splat_and_keyword_splat(*array1, **nil#{block})") + RUBY + end + + def test_anonymous_splat_and_anonymous_keyword_splat_parameters + check_allocations(<<~RUBY) + def self.anon_splat_and_anon_keyword_splat(*, **#{block}); end + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a: 2#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **nil#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **empty_hash#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **hash1#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})") + + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})") + RUBY + end + + def test_nested_anonymous_splat_and_anonymous_keyword_splat_parameters + check_allocations(<<~RUBY) + def self.anon_splat_and_anon_keyword_splat(*, **#{block}); t(*, **) end; def self.t(*, **#{block}); end + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a: 2#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **nil#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **empty_hash#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, **hash1#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, a: 2#{block})") + + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **nill#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash#{block})") + check_allocations(0, 0, "anon_splat_and_anon_keyword_splat(*array1, **hash1#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "anon_splat_and_anon_keyword_splat(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "anon_splat_and_anon_keyword_splat(*array1, **nil#{block})") + RUBY + end + + def test_argument_forwarding + check_allocations(<<~RUBY) + def self.argument_forwarding(...); end + + check_allocations(1, 1, "argument_forwarding(1, a: 2#{block})") + check_allocations(1, 0, "argument_forwarding(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "argument_forwarding(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "argument_forwarding(1, **nil#{block})") + check_allocations(1, 0, "argument_forwarding(1, **empty_hash#{block})") + check_allocations(1, 0, "argument_forwarding(1, **hash1#{block})") + check_allocations(1, 0, "argument_forwarding(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "argument_forwarding(1, *empty_array#{block})") + check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "argument_forwarding(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})") + + check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})") + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array#{block})") + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "argument_forwarding(*array1, **nil#{block})") + RUBY + end + + def test_nested_argument_forwarding + check_allocations(<<~RUBY) + def self.argument_forwarding(...); t(...) end; def self.t(...) end + + check_allocations(1, 1, "argument_forwarding(1, a: 2#{block})") + check_allocations(1, 0, "argument_forwarding(1, *empty_array, a: 2#{block})") + check_allocations(1, 1, "argument_forwarding(1, a:2, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(1, **empty_hash, a: 2#{block})") + + check_allocations(1, 0, "argument_forwarding(1, **nil#{block})") + check_allocations(1, 0, "argument_forwarding(1, **empty_hash#{block})") + check_allocations(1, 0, "argument_forwarding(1, **hash1#{block})") + check_allocations(1, 0, "argument_forwarding(1, *empty_array, **hash1#{block})") + check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(1, **empty_hash, **hash1#{block})") + + check_allocations(1, 0, "argument_forwarding(1, *empty_array#{block})") + check_allocations(1, 1, "argument_forwarding(1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "argument_forwarding(1, *empty_array, *empty_array, **empty_hash#{block})") + + check_allocations(0, 0, "argument_forwarding(*array1, a: 2#{block})") + + check_allocations(0, 0, "argument_forwarding(*array1, **nill#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **empty_hash#{block})") + check_allocations(0, 0, "argument_forwarding(*array1, **hash1#{block})") + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **hash1#{block})") + + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array#{block})") + check_allocations(1, 0, "argument_forwarding(*array1, *empty_array, **empty_hash#{block})") + + check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(*array1, *empty_array, **hash1, **empty_hash#{block})") + + check_allocations(1, 1, "argument_forwarding(1, *empty_array, a: 2, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(1, *empty_array, **hash1, **empty_hash#{block})") + check_allocations(1, 1, "argument_forwarding(*array1, **empty_hash, a: 2#{block})") + check_allocations(1, 1, "argument_forwarding(*array1, **hash1, **empty_hash#{block})") + check_allocations(1, 0, "argument_forwarding(*array1, **nil#{block})") + RUBY + end + + class WithBlock < self + def block + ', &block' + end + end + end +end diff --git a/test/ruby/test_literal.rb b/test/ruby/test_literal.rb index 61e1e096df8d08..c6154af1f61161 100644 --- a/test/ruby/test_literal.rb +++ b/test/ruby/test_literal.rb @@ -509,9 +509,6 @@ def test_hash_duplicated_key ) do |key| assert_warning(/key #{Regexp.quote(eval(key).inspect)} is duplicated/) { eval("{#{key} => :bar, #{key} => :foo}") } end - - assert_warning(/key 1 is duplicated/) { eval("{__LINE__ => :bar, 1 => :foo}") } - assert_warning(/key \"FILENAME\" is duplicated/) { eval("{__FILE__ => :bar, 'FILENAME' => :foo}", binding, "FILENAME") } end def test_hash_frozen_key_id diff --git a/test/ruby/test_parse.rb b/test/ruby/test_parse.rb index 62498170fd5242..e365c39def583b 100644 --- a/test/ruby/test_parse.rb +++ b/test/ruby/test_parse.rb @@ -803,14 +803,20 @@ def test_magic_comment # coding: foo END end - assert_include(e.message, "# coding: foo\n ^~~") + + message = e.message.gsub(/\033\[.*?m/, "") + assert_include(message, "# coding: foo\n") + assert_include(message, " ^") e = assert_raise(ArgumentError) do eval <<-END, nil, __FILE__, __LINE__+1 # coding = foo END end - assert_include(e.message, "# coding = foo\n ^~~") + + message = e.message.gsub(/\033\[.*?m/, "") + assert_include(message, "# coding = foo\n") + assert_include(message, " ^") end def test_utf8_bom diff --git a/test/ruby/test_process.rb b/test/ruby/test_process.rb index 8982ab8b9a6a2d..7ef184d63964ab 100644 --- a/test/ruby/test_process.rb +++ b/test/ruby/test_process.rb @@ -199,58 +199,67 @@ def test_execopts_rlimit max = Process.getrlimit(:CORE).last + # When running under ASAN, we need to set disable_coredump=0 for this test; by default + # the ASAN runtime library sets RLIMIT_CORE to 0, "to avoid dumping a 16T+ core file", and + # that inteferes with this test. + asan_options = ENV['ASAN_OPTIONS'] || '' + asan_options << ':' unless asan_options.empty? + env = { + 'ASAN_OPTIONS' => "#{asan_options}disable_coredump=0" + } + n = max - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io| assert_equal("#{n}\n#{n}\n", io.read) } n = 0 - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE)", :rlimit_core=>n]) {|io| assert_equal("#{n}\n#{n}\n", io.read) } n = max - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE)", :rlimit_core=>[n]]) {|io| assert_equal("#{n}\n#{n}\n", io.read) } m, n = 0, max - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io| assert_equal("#{m}\n#{n}\n", io.read) } m, n = 0, 0 - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE)", :rlimit_core=>[m,n]]) {|io| assert_equal("#{m}\n#{n}\n", io.read) } n = max - IO.popen([RUBY, "-e", + IO.popen([env, RUBY, "-e", "puts Process.getrlimit(:CORE), Process.getrlimit(:CPU)", :rlimit_core=>n, :rlimit_cpu=>3600]) {|io| assert_equal("#{n}\n#{n}\n""3600\n3600\n", io.read) } assert_raise(ArgumentError) do - system(RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123) + system(env, RUBY, '-e', 'exit', 'rlimit_bogus'.to_sym => 123) end - assert_separately([],"#{<<~"begin;"}\n#{<<~'end;'}", 'rlimit_cpu'.to_sym => 3600) + assert_separately([env],"#{<<~"begin;"}\n#{<<~'end;'}", 'rlimit_cpu'.to_sym => 3600) BUG = "[ruby-core:82033] [Bug #13744]" begin; assert_equal([3600,3600], Process.getrlimit(:CPU), BUG) end; assert_raise_with_message(ArgumentError, /bogus/) do - system(RUBY, '-e', 'exit', :rlimit_bogus => 123) + system(env, RUBY, '-e', 'exit', :rlimit_bogus => 123) end assert_raise_with_message(ArgumentError, /rlimit_cpu/) { - system(RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600) + system(env, RUBY, '-e', 'exit', "rlimit_cpu\0".to_sym => 3600) } end @@ -2828,4 +2837,25 @@ def test_concurrent_group_and_pid_wait [t1, t2, t3].each { _1&.join rescue nil } [long_rpipe, long_wpipe, short_rpipe, short_wpipe].each { _1&.close rescue nil } end if defined?(fork) + + def test_handle_interrupt_with_fork + Thread.handle_interrupt(RuntimeError => :never) do + Thread.current.raise(RuntimeError, "Queued error") + + assert_predicate Thread, :pending_interrupt? + + pid = Process.fork do + if Thread.pending_interrupt? + exit 1 + end + end + + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + + assert_predicate Thread, :pending_interrupt? + end + rescue RuntimeError + # Ignore. + end if defined?(fork) end diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 446548d6d9a395..8396066dc1ba19 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -865,6 +865,9 @@ def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &b env = Hash === args.first ? args.shift : {} args.unshift("--yjit") if self.class.yjit_enabled? env.update({'RUBY_ON_BUG' => nil}) + # ASAN registers a segv handler which prints out "AddressSanitizer: DEADLYSIGNAL" when + # catching sigsegv; we don't expect that output, so suppress it. + env.update({'ASAN_OPTIONS' => 'handle_segv=0'}) args.unshift(env) test_stdin = "" diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 8df6d3277f867f..0dae1c5a8e49ea 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3623,7 +3623,7 @@ def test_chilled_string assert_not_predicate +chilled_string, :frozen? assert_not_same chilled_string, +chilled_string - # @- the the original string as mutable + # @- the original string as mutable assert_predicate -chilled_string, :frozen? assert_not_same chilled_string, -chilled_string end @@ -3648,6 +3648,18 @@ def setivar! Warning[:deprecated] = deprecated end + def test_chilled_string_substring + deprecated = Warning[:deprecated] + Warning[:deprecated] = false + chilled_string = eval('"a chilled string."') + substring = chilled_string[0..-1] + assert_equal("a chilled string.", substring) + chilled_string[0..-1] = "This string is defrosted." + assert_equal("a chilled string.", substring) + ensure + Warning[:deprecated] = deprecated + end + private def assert_bytesplice_result(expected, s, *args) diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 42108f955f0241..7cc5e542a7b7bd 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -734,7 +734,7 @@ def test_duplicated_when end } } - assert_warning(/3: #{w}.+4: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m) { + assert_warning(/3: #{w}/m) { eval %q{ case 1 when __LINE__, __LINE__ @@ -743,7 +743,7 @@ def test_duplicated_when end } } - assert_warning(/3: #{w}.+4: #{w}.+4: #{w}.+5: #{w}.+5: #{w}/m) { + assert_warning(/3: #{w}/m) { eval %q{ case 1 when __FILE__, __FILE__ @@ -2154,6 +2154,13 @@ def obj.bar(*args, **kws, &block) assert_equal 0...1, exp.call(a: 0) end + def test_argument_forwarding_with_super + assert_valid_syntax('def foo(...) super {}; end') + assert_valid_syntax('def foo(...) super() {}; end') + assert_syntax_error('def foo(...) super(...) {}; end', /both block arg and actual block/) + assert_syntax_error('def foo(...) super(1, ...) {}; end', /both block arg and actual block/) + end + def test_class_module_Object_ancestors assert_separately([], <<-RUBY) m = Module.new diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index ed9e34a906faea..82fb50b70492c7 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -1367,12 +1367,12 @@ def v(string) # # Yields the +specification+ to the block, if given - def vendor_gem(name = "a", version = 1) + def vendor_gem(name = "a", version = 1, &block) directory = File.join "vendor", name FileUtils.mkdir_p directory - save_gemspec name, version, directory + save_gemspec name, version, directory, &block end ## diff --git a/test/rubygems/test_gem_commands_rebuild_command.rb b/test/rubygems/test_gem_commands_rebuild_command.rb new file mode 100644 index 00000000000000..5e8c797e2d11ad --- /dev/null +++ b/test/rubygems/test_gem_commands_rebuild_command.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require_relative "helper" +require "rubygems/commands/build_command" +require "rubygems/commands/rebuild_command" +require "rubygems/package" + +class TestGemCommandsRebuildCommand < Gem::TestCase + def setup + super + + readme_file = File.join(@tempdir, "README.md") + + begin + umask_orig = File.umask(2) + File.open readme_file, "w" do |f| + f.write "My awesome gem" + end + ensure + File.umask(umask_orig) + end + + @gem_name = "rebuild_test_gem" + @gem_version = "1.0.0" + @gem = util_spec @gem_name do |s| + s.version = @gem_version + s.license = "AGPL-3.0" + s.files = ["README.md"] + end + end + + def util_test_build_gem(gem, args) + @ui = Gem::MockGemUi.new + + cmd = Gem::Commands::BuildCommand.new + + cmd.options[:args] = args + cmd.options[:build_path] = @tempdir + use_ui @ui do + cmd.execute + end + gem_file = "#{@gem_name}-#{@gem_version}.gem" + output = @ui.output.split "\n" + assert_equal " Successfully built RubyGem", output.shift + assert_equal " Name: #{@gem_name}", output.shift + assert_equal " Version: #{@gem_version}", output.shift + assert_equal " File: #{gem_file}", output.shift + assert_equal [], output + + gem_file = File.join(@tempdir, gem_file) + assert File.exist?(gem_file) + + spec = Gem::Package.new(gem_file).spec + + assert_equal @gem_name, spec.name + assert_equal "this is a summary", spec.summary + gem_file + end + + def util_test_rebuild_gem(gem, args, original_gem_file, gemspec_file, timestamp) + @ui = Gem::MockGemUi.new + + cmd = Gem::Commands::RebuildCommand.new + + cmd.options[:args] = args + cmd.options[:original_gem_file] = original_gem_file + cmd.options[:build_path] = @tempdir + cmd.options[:gemspec_file] = gemspec_file + use_ui @ui do + cmd.execute + end + gem_file = "#{@gem_name}-#{@gem_version}.gem" + output = @ui.output.split "\n" + + assert_equal " Successfully built RubyGem", output.shift + assert_equal " Name: #{@gem_name}", output.shift + assert_equal " Version: #{@gem_version}", output.shift + assert_equal " File: #{gem_file}", output.shift + assert_empty output.shift + assert_match(/^Built at: .+ \(#{timestamp}\)/, output.shift) + original_line = output.shift + original = original_line.split(" ")[-1] + assert_match(/^Original build saved to: /, original_line) + reproduced_line = output.shift + reproduced = reproduced_line.split(" ")[-1] + assert_match(/^Reproduced build saved to: /, reproduced_line) + assert_equal "Working directory: #{@tempdir}", output.shift + assert_equal "", output.shift + assert_equal "Hash comparison:", output.shift + output.shift # " #{old_hash}\t#{old_file}" + output.shift # " #{new_hash}\t#{new_file}" + assert_empty output.shift + assert_equal "SUCCESS - original and rebuild hashes matched", output.shift + assert_equal [], output + + assert File.exist?(original) + assert File.exist?(reproduced) + + old_spec = Gem::Package.new(original).spec + new_spec = Gem::Package.new(reproduced).spec + + assert_equal @gem_name, old_spec.name + assert_equal "this is a summary", old_spec.summary + + assert_equal old_spec.name, new_spec.name + assert_equal old_spec.summary, new_spec.summary + + reproduced + end + + def test_build_is_reproducible + # Back up SOURCE_DATE_EPOCH to restore later. + epoch = ENV["SOURCE_DATE_EPOCH"] + + gemspec_file = File.join(@tempdir, @gem.spec_name) + + # Initial Build + + # Set SOURCE_DATE_EPOCH to 2001-02-03 04:05:06 -0500. + ENV["SOURCE_DATE_EPOCH"] = timestamp = Time.new(2001, 2, 3, 4, 5, 6).to_i.to_s + File.write(gemspec_file, @gem.to_ruby) + gem_file = util_test_build_gem @gem, [gemspec_file] + + build_contents = File.read(gem_file) + + gem_file_dir = File.dirname(gem_file) + gem_file_name = File.basename(gem_file) + original_gem_file = File.join(gem_file_dir, "original-" + gem_file_name) + File.rename(gem_file, original_gem_file) + + # Rebuild + + # Set SOURCE_DATE_EPOCH to a different value, meaning we are + # also testing that `gem rebuild` overrides the value. + ENV["SOURCE_DATE_EPOCH"] = Time.new(2007, 8, 9, 10, 11, 12).to_s + + rebuild_gem_file = util_test_rebuild_gem(@gem, [@gem_name, @gem_version], original_gem_file, gemspec_file, timestamp) + + rebuild_contents = File.read(rebuild_gem_file) + + assert_equal build_contents, rebuild_contents + ensure + ENV["SOURCE_DATE_EPOCH"] = epoch + end +end diff --git a/test/rubygems/test_gem_source.rb b/test/rubygems/test_gem_source.rb index 4d445f343765d6..6baa203dcb1829 100644 --- a/test/rubygems/test_gem_source.rb +++ b/test/rubygems/test_gem_source.rb @@ -54,8 +54,9 @@ def test_dependency_resolver_set_bundler_api end def test_dependency_resolver_set_file_uri - File.write(File.join(@tempdir, "prerelease_specs.4.8.gz"), Gem::Util.gzip("\x04\x08[\x05".b)) - File.write(File.join(@tempdir, "specs.4.8.gz"), Gem::Util.gzip("\x04\x08[\x05".b)) + empty_dump = Gem::Util.gzip("\x04\x08[\x05".b) + File.binwrite(File.join(@tempdir, "prerelease_specs.4.8.gz"), empty_dump) + File.binwrite(File.join(@tempdir, "specs.4.8.gz"), empty_dump) source = Gem::Source.new "file://#{@tempdir}/" diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index 2af69235744e09..e17cd0abb11a01 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -978,6 +978,27 @@ def test_coderange_after_overwrite assert_predicate(s.string, :ascii_only?) end + if eval(%{ "test".frozen? && !"test".equal?("test") }) # Ruby 3.4+ chilled strings + def test_chilled_string + chilled_string = eval(%{""}) + io = StringIO.new(chilled_string) + assert_warning(/literal string will be frozen/) { io << "test" } + assert_equal("test", io.string) + assert_same(chilled_string, io.string) + end + + def test_chilled_string_string_set + io = StringIO.new + chilled_string = eval(%{""}) + io.string = chilled_string + assert_warning(/literal string will be frozen/) { io << "test" } + assert_equal("test", io.string) + assert_same(chilled_string, io.string) + end + end + + private + def assert_string(content, encoding, str, mesg = nil) assert_equal([content, encoding], [str, str.encoding], mesg) end diff --git a/test/strscan/test_stringscanner.rb b/test/strscan/test_stringscanner.rb index 2884b8ef054ff7..143cf7197df673 100644 --- a/test/strscan/test_stringscanner.rb +++ b/test/strscan/test_stringscanner.rb @@ -9,6 +9,7 @@ module StringScannerTests def test_peek_byte + omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby" s = create_string_scanner('ab') assert_equal 97, s.peek_byte assert_equal 97, s.scan_byte @@ -19,6 +20,7 @@ def test_peek_byte end def test_scan_byte + omit("not implemented on TruffleRuby") if RUBY_ENGINE == "truffleruby" s = create_string_scanner('ab') assert_equal 97, s.scan_byte assert_equal 98, s.scan_byte diff --git a/thread.c b/thread.c index b8dd8626344b87..196f9092b49ebb 100644 --- a/thread.c +++ b/thread.c @@ -527,9 +527,6 @@ void ruby_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame) { native_thread_init_stack(th, local_in_parent_frame); -#ifdef RUBY_ASAN_ENABLED - th->asan_fake_stack_handle = asan_get_thread_fake_stack_handle(); -#endif } const VALUE * @@ -4728,6 +4725,7 @@ void rb_thread_atfork(void) { rb_thread_t *th = GET_THREAD(); + rb_threadptr_pending_interrupt_clear(th); rb_thread_atfork_internal(th, terminate_atfork_i); th->join_list = NULL; rb_fiber_atfork(th); @@ -5101,12 +5099,12 @@ recursive_list_access(VALUE sym) } /* - * Returns Qtrue if and only if obj (or the pair ) is already + * Returns true if and only if obj (or the pair ) is already * in the recursion list. * Assumes the recursion list is valid. */ -static VALUE +static bool recursive_check(VALUE list, VALUE obj, VALUE paired_obj_id) { #if SIZEOF_LONG == SIZEOF_VOIDP @@ -5118,18 +5116,18 @@ recursive_check(VALUE list, VALUE obj, VALUE paired_obj_id) VALUE pair_list = rb_hash_lookup2(list, obj, Qundef); if (UNDEF_P(pair_list)) - return Qfalse; + return false; if (paired_obj_id) { if (!RB_TYPE_P(pair_list, T_HASH)) { if (!OBJ_ID_EQL(paired_obj_id, pair_list)) - return Qfalse; + return false; } else { if (NIL_P(rb_hash_lookup(pair_list, paired_obj_id))) - return Qfalse; + return false; } } - return Qtrue; + return true; } /* @@ -5209,7 +5207,7 @@ exec_recursive_i(RB_BLOCK_CALL_FUNC_ARGLIST(tag, data)) * Calls func(obj, arg, recursive), where recursive is non-zero if the * current method is called recursively on obj, or on the pair * If outer is 0, then the innermost func will be called with recursive set - * to Qtrue, otherwise the outermost func will be called. In the latter case, + * to true, otherwise the outermost func will be called. In the latter case, * all inner func are short-circuited by throw. * Implementation details: the value thrown is the recursive list which is * proper to the current method and unlikely to be caught anywhere else. @@ -5300,7 +5298,7 @@ rb_exec_recursive_paired(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE pai /* * If recursion is detected on the current method and obj, the outermost - * func will be called with (obj, arg, Qtrue). All inner func will be + * func will be called with (obj, arg, true). All inner func will be * short-circuited using throw. */ @@ -5318,7 +5316,7 @@ rb_exec_recursive_outer_mid(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE /* * If recursion is detected on the current method, obj and paired_obj, - * the outermost func will be called with (obj, arg, Qtrue). All inner + * the outermost func will be called with (obj, arg, true). All inner * func will be short-circuited using throw. */ diff --git a/thread_pthread.c b/thread_pthread.c index fb446eee82c08e..82b5e362ccc0eb 100644 --- a/thread_pthread.c +++ b/thread_pthread.c @@ -305,7 +305,7 @@ event_name(rb_event_flag_t event) static rb_serial_t current_fork_gen = 1; /* We can't use GET_VM()->fork_gen */ -#if defined(SIGVTALRM) && !defined(__CYGWIN__) && !defined(__EMSCRIPTEN__) +#if defined(SIGVTALRM) && !defined(__EMSCRIPTEN__) # define USE_UBF_LIST 1 #endif @@ -2081,6 +2081,7 @@ native_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame) rb_nativethread_id_t curr = pthread_self(); #ifdef RUBY_ASAN_ENABLED local_in_parent_frame = asan_get_real_stack_addr(local_in_parent_frame); + th->ec->machine.asan_fake_stack_handle = asan_get_thread_fake_stack_handle(); #endif if (!native_main_thread.id) { diff --git a/tool/bundler/vendor_gems.rb b/tool/bundler/vendor_gems.rb index 2500e6c80028d3..f02d02656d2e45 100644 --- a/tool/bundler/vendor_gems.rb +++ b/tool/bundler/vendor_gems.rb @@ -9,7 +9,7 @@ gem "net-protocol", "0.2.2" gem "optparse", "0.4.0" gem "pub_grub", github: "jhawthorn/pub_grub" -gem "resolv", "0.3.0" +gem "resolv", "0.4.0" gem "timeout", "0.4.1" gem "thor", "1.3.0" gem "tsort", "0.2.0" diff --git a/tool/downloader.rb b/tool/downloader.rb index 59bfd55f17c192..3a91ea0b938aed 100644 --- a/tool/downloader.rb +++ b/tool/downloader.rb @@ -79,6 +79,9 @@ def self.download(name, dir = nil, since = true, options = {}) require 'rubygems' options = options.dup options[:ssl_ca_cert] = Dir.glob(File.expand_path("../lib/rubygems/ssl_certs/**/*.pem", File.dirname(__FILE__))) + if Gem::Version.new(name[/-\K[^-]*(?=\.gem\z)/]).prerelease? + options[:ignore_http_client_errors] = true + end super("https://rubygems.org/downloads/#{name}", name, dir, since, options) end end @@ -237,6 +240,7 @@ def self.download(url, name, dir = nil, since = true, options = {}) $stdout.flush end mtime = nil + ignore_http_client_errors = options.delete(:ignore_http_client_errors) options = options.merge(http_options(file, since.nil? ? true : since)) begin data = with_retry(10) do @@ -247,12 +251,18 @@ def self.download(url, name, dir = nil, since = true, options = {}) data end rescue OpenURI::HTTPError => http_error - if http_error.message =~ /^304 / # 304 Not Modified + case http_error.message + when /^304 / # 304 Not Modified if $VERBOSE $stdout.puts "#{name} not modified" $stdout.flush end return file.to_path + when /^40/ # Net::HTTPClientError: 403 Forbidden, 404 Not Found + if ignore_http_client_errors + puts "Ignore #{url}: #{http_error.message}" + return file.to_path + end end raise rescue Timeout::Error diff --git a/tool/lib/webrick/httprequest.rb b/tool/lib/webrick/httprequest.rb index d34eac7ecf6ed0..258ee37a38efbe 100644 --- a/tool/lib/webrick/httprequest.rb +++ b/tool/lib/webrick/httprequest.rb @@ -402,7 +402,7 @@ def fixup() # :nodoc: # This method provides the metavariables defined by the revision 3 # of "The WWW Common Gateway Interface Version 1.1" # To browse the current document of CGI Version 1.1, see below: - # http://tools.ietf.org/html/rfc3875 + # https://www.rfc-editor.org/rfc/rfc3875 def meta_vars meta = Hash.new diff --git a/tool/lrama/NEWS.md b/tool/lrama/NEWS.md index 3fcb0ee88a91e3..96aaaf94f57d5f 100644 --- a/tool/lrama/NEWS.md +++ b/tool/lrama/NEWS.md @@ -1,5 +1,45 @@ # NEWS for Lrama +## Lrama 0.6.5 (2024-03-25) + +### Typed Midrule Actions + +User can specify the type of mid rule action by tag (``) instead of specifying it with in an action. + +``` +primary: k_case expr_value terms? + { + $$ = p->case_labels; + p->case_labels = Qnil; + } + case_body + k_end + { + ... + } +``` + +can be written as + +``` +primary: k_case expr_value terms? + { + $$ = p->case_labels; + p->case_labels = Qnil; + } + case_body + k_end + { + ... + } +``` + +`%destructor` for midrule action is invoked only when tag is specified by Typed Midrule Actions. + +Difference from Bison's Typed Midrule Actions is that tag is postposed in Lrama however it's preposed in Bison. + +Bison supports this feature from 3.1. + ## Lrama 0.6.4 (2024-03-22) ### Parameterizing rules (preceded, terminated, delimited) diff --git a/tool/lrama/lib/lrama/grammar/rule_builder.rb b/tool/lrama/lib/lrama/grammar/rule_builder.rb index 24020db6519c91..b2ccc3e2437b8c 100644 --- a/tool/lrama/lib/lrama/grammar/rule_builder.rb +++ b/tool/lrama/lib/lrama/grammar/rule_builder.rb @@ -128,10 +128,11 @@ def process_rhs(parameterizing_rule_resolver) end when Lrama::Lexer::Token::UserCode prefix = token.referred ? "@" : "$@" + tag = token.tag || lhs_tag new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s) @replaced_rhs << new_token - rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, i, lhs_tag: lhs_tag, skip_preprocess_references: true) + rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, i, lhs_tag: tag, skip_preprocess_references: true) rule_builder.lhs = new_token rule_builder.user_code = token rule_builder.complete_input diff --git a/tool/lrama/lib/lrama/lexer/token/user_code.rb b/tool/lrama/lib/lrama/lexer/token/user_code.rb index 14c69f3de6b423..4d487bf01cee4e 100644 --- a/tool/lrama/lib/lrama/lexer/token/user_code.rb +++ b/tool/lrama/lib/lrama/lexer/token/user_code.rb @@ -4,6 +4,8 @@ module Lrama class Lexer class Token class UserCode < Token + attr_accessor :tag + def references @references ||= _references end diff --git a/tool/lrama/lib/lrama/parser.rb b/tool/lrama/lib/lrama/parser.rb index 404b7c27b2bca0..0a46f759c0c787 100644 --- a/tool/lrama/lib/lrama/parser.rb +++ b/tool/lrama/lib/lrama/parser.rb @@ -658,7 +658,7 @@ def token_to_str(t) module Lrama class Parser < Racc::Parser -module_eval(<<'...end parser.y/module_eval...', 'parser.y', 528) +module_eval(<<'...end parser.y/module_eval...', 'parser.y', 529) include Lrama::Report::Duration @@ -759,7 +759,7 @@ def raise_parse_error(error_message, location) 126, 127, 128, 129, 130, 133, 137, 138, 139, 142, 143, 144, 146, 161, 163, 164, 165, 166, 167, 168, 169, 142, 171, 179, 180, 189, 194, 195, 197, 202, - 189, 94, 194, 216, 218, 94, 223, 94 ] + 189, 94, 194, 216, 218, 94, 194, 224, 94 ] racc_action_check = [ 48, 141, 48, 141, 140, 141, 170, 188, 170, 188, @@ -789,7 +789,7 @@ def raise_parse_error(error_message, location) 102, 103, 104, 105, 106, 110, 118, 119, 120, 121, 122, 123, 125, 145, 147, 148, 149, 150, 151, 152, 153, 154, 156, 160, 162, 168, 173, 177, 187, 190, - 197, 198, 203, 206, 211, 216, 222, 223 ] + 197, 198, 203, 206, 211, 216, 220, 222, 224 ] racc_action_pointer = [ nil, 20, 9, 26, 97, nil, nil, 23, nil, 32, @@ -814,10 +814,10 @@ def raise_parse_error(error_message, location) 229, 166, 163, nil, nil, nil, nil, 225, 221, -16, nil, 183, 188, 264, 189, nil, 253, 9, nil, nil, 194, 272, nil, 172, nil, nil, 225, 173, nil, nil, - nil, nil, 256, 227, nil ] + 268, nil, 257, nil, 228, nil ] racc_action_default = [ - -2, -136, -8, -136, -136, -3, -4, -136, 225, -136, + -2, -136, -8, -136, -136, -3, -4, -136, 226, -136, -9, -10, -11, -136, -136, -136, -136, -136, -136, -136, -23, -24, -136, -28, -136, -136, -136, -136, -136, -136, -136, -136, -136, -136, -136, -136, -136, -136, -136, -136, @@ -839,58 +839,56 @@ def raise_parse_error(error_message, location) -92, -136, -114, -105, -135, -108, -130, -60, -118, -92, -65, -136, -136, -134, -136, -116, -136, -59, -62, -63, -136, -136, -68, -136, -106, -115, -118, -136, -66, -117, - -109, -64, -136, -118, -67 ] + -134, -64, -136, -109, -118, -67 ] racc_goto_table = [ - 93, 51, 73, 68, 116, 75, 108, 173, 193, 1, - 188, 196, 2, 191, 117, 196, 196, 141, 4, 71, - 41, 83, 83, 83, 83, 42, 79, 84, 85, 86, - 52, 54, 55, 181, 185, 186, 89, 5, 214, 207, - 109, 116, 205, 114, 213, 113, 75, 108, 135, 209, - 170, 39, 217, 119, 10, 71, 71, 90, 11, 116, - 12, 48, 95, 125, 162, 102, 147, 83, 83, 108, - 103, 148, 104, 149, 105, 150, 106, 151, 131, 67, - 72, 134, 110, 132, 75, 136, 113, 187, 211, 222, - 123, 160, 100, 145, 71, 140, 71, 177, 206, 120, - nil, nil, 83, nil, 83, nil, 113, nil, nil, nil, - nil, nil, 172, 157, nil, nil, nil, nil, 71, nil, - nil, nil, 83, nil, nil, nil, nil, nil, nil, nil, - nil, 178, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 157, 192, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 208, nil, nil, nil, nil, nil, nil, - 198, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 220, nil, 212, 192, nil, 215, nil, 224, 198, - nil, nil, 192 ] + 93, 75, 51, 68, 73, 193, 116, 108, 191, 173, + 196, 1, 117, 2, 196, 196, 141, 4, 42, 41, + 71, 89, 83, 83, 83, 83, 188, 79, 84, 85, + 86, 52, 54, 55, 5, 214, 181, 185, 186, 213, + 109, 113, 75, 116, 205, 114, 135, 217, 108, 170, + 90, 209, 223, 39, 119, 207, 71, 71, 10, 11, + 12, 116, 48, 95, 125, 162, 102, 147, 83, 83, + 108, 103, 148, 104, 149, 105, 150, 106, 131, 151, + 75, 67, 113, 134, 72, 110, 132, 136, 187, 211, + 222, 123, 160, 100, 145, 71, 140, 71, 177, 206, + 120, nil, 113, 83, nil, 83, nil, nil, nil, 157, + nil, nil, 172, nil, nil, nil, nil, nil, nil, 71, + nil, nil, nil, 83, nil, nil, nil, 178, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 157, 192, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 208, nil, nil, 198, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 212, + 192, 220, 215, nil, nil, 198, nil, nil, 192, 225 ] racc_goto_check = [ - 41, 34, 46, 32, 53, 40, 33, 42, 59, 1, - 39, 63, 2, 43, 52, 63, 63, 58, 3, 34, - 4, 34, 34, 34, 34, 54, 31, 31, 31, 31, - 14, 14, 14, 20, 20, 20, 5, 6, 59, 39, - 32, 53, 42, 46, 43, 40, 40, 33, 52, 42, - 58, 7, 43, 8, 9, 34, 34, 54, 10, 53, - 11, 12, 13, 15, 16, 17, 18, 34, 34, 33, - 21, 22, 23, 24, 25, 26, 27, 28, 32, 29, - 30, 46, 35, 36, 40, 37, 40, 38, 44, 45, - 48, 49, 50, 51, 34, 57, 34, 60, 61, 62, - nil, nil, 34, nil, 34, nil, 40, nil, nil, nil, - nil, nil, 41, 40, nil, nil, nil, nil, 34, nil, - nil, nil, 34, nil, nil, nil, nil, nil, nil, nil, - nil, 40, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 40, 40, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 41, nil, nil, nil, nil, nil, nil, - 40, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 41, nil, 40, 40, nil, 40, nil, 41, 40, - nil, nil, 40 ] + 41, 40, 34, 32, 46, 59, 53, 33, 43, 42, + 63, 1, 52, 2, 63, 63, 58, 3, 54, 4, + 34, 5, 34, 34, 34, 34, 39, 31, 31, 31, + 31, 14, 14, 14, 6, 59, 20, 20, 20, 43, + 32, 40, 40, 53, 42, 46, 52, 43, 33, 58, + 54, 42, 59, 7, 8, 39, 34, 34, 9, 10, + 11, 53, 12, 13, 15, 16, 17, 18, 34, 34, + 33, 21, 22, 23, 24, 25, 26, 27, 32, 28, + 40, 29, 40, 46, 30, 35, 36, 37, 38, 44, + 45, 48, 49, 50, 51, 34, 57, 34, 60, 61, + 62, nil, 40, 34, nil, 34, nil, nil, nil, 40, + nil, nil, 41, nil, nil, nil, nil, nil, nil, 34, + nil, nil, nil, 34, nil, nil, nil, 40, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 40, 40, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 41, nil, nil, 40, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 40, + 40, 41, 40, nil, nil, 40, nil, nil, 40, 41 ] racc_goto_pointer = [ - nil, 9, 12, 16, 11, -5, 35, 45, -35, 50, - 54, 56, 47, 14, 15, -38, -82, 8, -60, nil, - -132, 12, -56, 13, -55, 14, -54, 15, -53, 47, - 47, -8, -29, -62, -13, 11, -27, -33, -81, -158, - -28, -45, -150, -158, -112, -129, -31, nil, -9, -53, - 39, -31, -67, -76, 16, nil, nil, -26, -104, -165, - -61, -97, 8, -170 ] + nil, 11, 13, 15, 10, -20, 32, 47, -34, 54, + 55, 56, 48, 15, 16, -37, -81, 9, -59, nil, + -129, 13, -55, 14, -54, 15, -53, 16, -51, 49, + 51, -7, -29, -61, -12, 14, -24, -31, -80, -142, + -32, -45, -148, -163, -111, -128, -29, nil, -8, -52, + 40, -30, -69, -74, 9, nil, nil, -25, -105, -168, + -60, -96, 9, -171 ] racc_goto_default = [ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, @@ -1011,7 +1009,7 @@ def raise_parse_error(error_message, location) 6, 111, :_reduce_106, 0, 113, :_reduce_107, 0, 114, :_reduce_108, - 7, 111, :_reduce_109, + 8, 111, :_reduce_109, 3, 111, :_reduce_110, 1, 95, :_reduce_111, 1, 95, :_reduce_112, @@ -1041,7 +1039,7 @@ def raise_parse_error(error_message, location) racc_reduce_n = 136 -racc_shift_n = 225 +racc_shift_n = 226 racc_token_table = { false => 0, @@ -2066,6 +2064,7 @@ def _reduce_108(val, _values, result) def _reduce_109(val, _values, result) user_code = val[3] user_code.alias_name = val[6] + user_code.tag = val[7] builder = val[0] builder.user_code = user_code result = builder @@ -2074,7 +2073,7 @@ def _reduce_109(val, _values, result) end .,., -module_eval(<<'.,.,', 'parser.y', 474) +module_eval(<<'.,.,', 'parser.y', 475) def _reduce_110(val, _values, result) sym = @grammar.find_symbol_by_id!(val[2]) @prec_seen = true @@ -2086,49 +2085,49 @@ def _reduce_110(val, _values, result) end .,., -module_eval(<<'.,.,', 'parser.y', 481) +module_eval(<<'.,.,', 'parser.y', 482) def _reduce_111(val, _values, result) result = "option" result end .,., -module_eval(<<'.,.,', 'parser.y', 482) +module_eval(<<'.,.,', 'parser.y', 483) def _reduce_112(val, _values, result) result = "nonempty_list" result end .,., -module_eval(<<'.,.,', 'parser.y', 483) +module_eval(<<'.,.,', 'parser.y', 484) def _reduce_113(val, _values, result) result = "list" result end .,., -module_eval(<<'.,.,', 'parser.y', 485) +module_eval(<<'.,.,', 'parser.y', 486) def _reduce_114(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'parser.y', 486) +module_eval(<<'.,.,', 'parser.y', 487) def _reduce_115(val, _values, result) result = val[0].append(val[2]) result end .,., -module_eval(<<'.,.,', 'parser.y', 487) +module_eval(<<'.,.,', 'parser.y', 488) def _reduce_116(val, _values, result) result = [Lrama::Lexer::Token::InstantiateRule.new(s_value: val[1].s_value, location: @lexer.location, args: val[0])] result end .,., -module_eval(<<'.,.,', 'parser.y', 488) +module_eval(<<'.,.,', 'parser.y', 489) def _reduce_117(val, _values, result) result = [Lrama::Lexer::Token::InstantiateRule.new(s_value: val[0].s_value, location: @lexer.location, args: val[2])] result @@ -2137,7 +2136,7 @@ def _reduce_117(val, _values, result) # reduce 118 omitted -module_eval(<<'.,.,', 'parser.y', 491) +module_eval(<<'.,.,', 'parser.y', 492) def _reduce_119(val, _values, result) result = val[1].s_value result @@ -2148,7 +2147,7 @@ def _reduce_119(val, _values, result) # reduce 121 omitted -module_eval(<<'.,.,', 'parser.y', 498) +module_eval(<<'.,.,', 'parser.y', 499) def _reduce_122(val, _values, result) begin_c_declaration('\Z') @grammar.epilogue_first_lineno = @lexer.line + 1 @@ -2157,7 +2156,7 @@ def _reduce_122(val, _values, result) end .,., -module_eval(<<'.,.,', 'parser.y', 503) +module_eval(<<'.,.,', 'parser.y', 504) def _reduce_123(val, _values, result) end_c_declaration @grammar.epilogue = val[2].s_value @@ -2176,14 +2175,14 @@ def _reduce_123(val, _values, result) # reduce 128 omitted -module_eval(<<'.,.,', 'parser.y', 514) +module_eval(<<'.,.,', 'parser.y', 515) def _reduce_129(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'parser.y', 515) +module_eval(<<'.,.,', 'parser.y', 516) def _reduce_130(val, _values, result) result = val[0].append(val[1]) result @@ -2194,7 +2193,7 @@ def _reduce_130(val, _values, result) # reduce 132 omitted -module_eval(<<'.,.,', 'parser.y', 520) +module_eval(<<'.,.,', 'parser.y', 521) def _reduce_133(val, _values, result) result = Lrama::Lexer::Token::Ident.new(s_value: val[0]) result diff --git a/tool/lrama/lib/lrama/version.rb b/tool/lrama/lib/lrama/version.rb index 0070c177c65bd3..ccd593f344f532 100644 --- a/tool/lrama/lib/lrama/version.rb +++ b/tool/lrama/lib/lrama/version.rb @@ -1,3 +1,3 @@ module Lrama - VERSION = "0.6.4".freeze + VERSION = "0.6.5".freeze end diff --git a/tool/missing-baseruby.bat b/tool/missing-baseruby.bat index 34b37361e5ef6f..87a9857e066fcc 100755 --- a/tool/missing-baseruby.bat +++ b/tool/missing-baseruby.bat @@ -1,12 +1,14 @@ -: " +:"" == " @echo off || ( :warn echo>&2.%~1 goto :eof :abort exit /b 1 +)||( +:)"||( + s = %^# ) -: " : ; call() { local call=${1#:}; shift; $call "$@"; } : ; warn() { echo "$1" >&2; } : ; abort () { exit 1; } @@ -14,3 +16,4 @@ call :warn "executable host ruby is required. use --with-baseruby option." call :warn "Note that BASERUBY must be Ruby 3.0.0 or later." call :abort +: || (:^; abort if RUBY_VERSION < s[%r"warn .*Ruby ([\d.]+)(?:\.0)?",1]) diff --git a/tool/rbinstall.rb b/tool/rbinstall.rb index 2faaece8c92d68..63f4beb9434861 100755 --- a/tool/rbinstall.rb +++ b/tool/rbinstall.rb @@ -549,47 +549,108 @@ def initialize(gemspec, srcdir, relative_base) end def collect - ruby_libraries.sort + requirable_features.sort + end + + private + + def features_from_makefile(makefile_path) + makefile = File.read(makefile_path) + + name = makefile[/^TARGET[ \t]*=[ \t]*((?:.*\\\n)*.*)/, 1] + return [] if name.nil? || name.empty? + + feature = makefile[/^DLLIB[ \t]*=[ \t]*((?:.*\\\n)*.*)/, 1] + feature = feature.sub("$(TARGET)", name) + + target_prefix = makefile[/^target_prefix[ \t]*=[ \t]*((?:.*\\\n)*.*)/, 1] + feature = File.join(target_prefix.delete_prefix("/"), feature) unless target_prefix.empty? + + Array(feature) end class Ext < self - def skip_install?(files) + def requirable_features # install ext only when it's configured - !File.exist?("#{$ext_build_dir}/#{relative_base}/Makefile") + return [] unless File.exist?(makefile_path) + + ruby_features + ext_features + end + + private + + def ruby_features + Dir.glob("**/*.rb", base: "#{makefile_dir}/lib") end - def ruby_libraries - Dir.glob("lib/**/*.rb", base: "#{srcdir}/ext/#{relative_base}") + def ext_features + features_from_makefile(makefile_path) + end + + def makefile_path + if File.exist?("#{makefile_dir}/Makefile") + "#{makefile_dir}/Makefile" + else + # for out-of-place build + "#{$ext_build_dir}/#{relative_base}/Makefile" + end + end + + def makefile_dir + "#{root}/#{relative_base}" + end + + def root + File.expand_path($ext_build_dir, srcdir) end end class Lib < self - def skip_install?(files) - files.empty? + def requirable_features + ruby_features + ext_features end - def ruby_libraries + private + + def ruby_features gemname = File.basename(gemspec, ".gemspec") base = relative_base || gemname # for lib/net/net-smtp.gemspec if m = /.*(?=-(.*)\z)/.match(gemname) base = File.join(base, *m.to_a.select {|n| !base.include?(n)}) end - files = Dir.glob("lib/#{base}{.rb,/**/*.rb}", base: srcdir) + files = Dir.glob("#{base}{.rb,/**/*.rb}", base: root) if !relative_base and files.empty? # no files at the toplevel # pseudo gem like ruby2_keywords - files << "lib/#{gemname}.rb" + files << "#{gemname}.rb" end case gemname when "net-http" - files << "lib/net/https.rb" + files << "net/https.rb" when "optparse" - files << "lib/optionparser.rb" + files << "optionparser.rb" end files end + + def ext_features + loaded_gemspec = load_gemspec("#{root}/#{gemspec}") + extension = loaded_gemspec.extensions.first + return [] unless extension + + extconf = File.expand_path(extension, srcdir) + ext_build_dir = File.dirname(extconf) + makefile_path = "#{ext_build_dir}/Makefile" + return [] unless File.exist?(makefile_path) + + features_from_makefile(makefile_path) + end + + def root + "#{srcdir}/lib" + end end end end @@ -757,7 +818,7 @@ def install_default_gem(dir, srcdir, bindir) spec = load_gemspec("#{base}/#{src}") file_collector = RbInstall::Specs::FileCollector.for(srcdir, dir, src) files = file_collector.collect - if file_collector.skip_install?(files) + if files.empty? next end spec.files = files diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index df7e59c9563a4b..0307028eb7569b 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -461,7 +461,7 @@ def commits_in_ranges(gem, repo, default_branch, ranges) # \r? needed in the regex in case the commit has windows-style line endings (because e.g. we're running # tests on Windows) pattern = "https://github\.com/#{Regexp.quote(repo)}/commit/([0-9a-f]+)\r?$" - log = IO.popen(%W"git log -E --grep=#{pattern} -n1 --format=%B", &:read) + log = IO.popen(%W"git log -E --grep=#{pattern} -n1 --format=%B", "rb", &:read) ranges = ["#{log[%r[#{pattern}\n\s*(?i:co-authored-by:.*)*\s*\Z], 1]}..#{gem}/#{default_branch}"] end @@ -471,7 +471,7 @@ def commits_in_ranges(gem, repo, default_branch, ranges) range = "#{range}~1..#{range}" end - IO.popen(%W"git log --format=%H,%s #{range} --") do |f| + IO.popen(%W"git log --format=%H,%s #{range} --", "rb") do |f| f.read.split("\n").reverse.map{|commit| commit.split(',', 2)} end end @@ -581,7 +581,7 @@ def pickup_files(gem, changed, picked) def pickup_commit(gem, sha, edit) # Attempt to cherry-pick a commit - result = IO.popen(%W"git cherry-pick #{sha}", &:read) + result = IO.popen(%W"git cherry-pick #{sha}", "rb", &:read) picked = $?.success? if result =~ /nothing\ to\ commit/ `git reset` diff --git a/tool/test/testunit/tests_for_parallel/slow_helper.rb b/tool/test/testunit/tests_for_parallel/slow_helper.rb index d8372730a8f50b..38067c1f470fb5 100644 --- a/tool/test/testunit/tests_for_parallel/slow_helper.rb +++ b/tool/test/testunit/tests_for_parallel/slow_helper.rb @@ -2,6 +2,7 @@ module TestSlowTimeout def test_slow - sleep (ENV['sec'] || 3).to_i if on_parallel_worker? + sleep_for = EnvUtil.apply_timeout_scale((ENV['sec'] || 3).to_i) + sleep sleep_for if on_parallel_worker? end end diff --git a/tool/test_for_warn_bundled_gems/test.sh b/tool/test_for_warn_bundled_gems/test.sh index ce714c7e13314f..ef5007f320ab77 100755 --- a/tool/test_for_warn_bundled_gems/test.sh +++ b/tool/test_for_warn_bundled_gems/test.sh @@ -2,30 +2,44 @@ echo "* Show warning require and LoadError" ruby test_warn_bundled_gems.rb +echo echo "* Show warning when bundled gems called as dependency" ruby test_warn_dependency.rb +echo echo "* Show warning sub-feature like bigdecimal/util" ruby test_warn_sub_feature.rb +echo echo "* Show warning dash gem like net/smtp" ruby test_warn_dash_gem.rb +echo echo "* Show warning when bundle exec with ruby and script" bundle exec ruby test_warn_bundle_exec.rb +echo echo "* Show warning when bundle exec with shebang's script" bundle exec ./test_warn_bundle_exec_shebang.rb +echo + +echo "* Show warning with bootsnap" +ruby test_warn_bootsnap.rb +echo + +echo "* Show warning with zeitwerk" +ruby test_warn_zeitwerk.rb +echo echo "* Don't show warning bundled gems on Gemfile" ruby test_no_warn_dependency.rb - -echo "* Don't show warning with bootsnap" -ruby test_no_warn_bootsnap.rb +echo echo "* Don't show warning with net/smtp when net-smtp on Gemfile" ruby test_no_warn_dash_gem.rb +echo echo "* Don't show warning bigdecimal/util when bigdecimal on Gemfile" ruby test_no_warn_sub_feature.rb +echo diff --git a/tool/test_for_warn_bundled_gems/test_no_warn_bootsnap.rb b/tool/test_for_warn_bundled_gems/test_warn_bootsnap.rb similarity index 100% rename from tool/test_for_warn_bundled_gems/test_no_warn_bootsnap.rb rename to tool/test_for_warn_bundled_gems/test_warn_bootsnap.rb diff --git a/tool/test_for_warn_bundled_gems/test_warn_zeitwerk.rb b/tool/test_for_warn_bundled_gems/test_warn_zeitwerk.rb new file mode 100644 index 00000000000000..d554a0e6756845 --- /dev/null +++ b/tool/test_for_warn_bundled_gems/test_warn_zeitwerk.rb @@ -0,0 +1,12 @@ +require "bundler/inline" + +gemfile do + source "https://rubygems.org" + gem "zeitwerk", require: false +end + +require "zeitwerk" +loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false) +loader.setup + +require 'csv' diff --git a/universal_parser.c b/universal_parser.c index 08fdfe5b4a8008..dfb02eaa4c1db7 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -54,6 +54,10 @@ #define st_delete rb_parser_st_delete #undef st_is_member #define st_is_member parser_st_is_member +#undef st_init_table +#define st_init_table rb_parser_st_init_table +#undef st_lookup +#define st_lookup rb_parser_st_lookup #define rb_encoding void @@ -211,8 +215,6 @@ struct rb_imemo_tmpbuf_struct { #define rb_ascii8bit_encoding p->config->ascii8bit_encoding #define rb_enc_codelen p->config->enc_codelen #define rb_enc_mbcput p->config->enc_mbcput -#define rb_char_to_option_kcode p->config->char_to_option_kcode -#define rb_ascii8bit_encindex p->config->ascii8bit_encindex #define rb_enc_find_index p->config->enc_find_index #define rb_enc_from_index p->config->enc_from_index #define rb_enc_associate_index p->config->enc_associate_index @@ -221,8 +223,6 @@ struct rb_imemo_tmpbuf_struct { #define ENC_CODERANGE_UNKNOWN p->config->enc_coderange_unknown #define rb_enc_compatible p->config->enc_compatible #define rb_enc_from_encoding p->config->enc_from_encoding -#define ENCODING_GET p->config->encoding_get -#define ENCODING_SET p->config->encoding_set #define ENCODING_IS_ASCII8BIT p->config->encoding_is_ascii8bit #define rb_usascii_encoding p->config->usascii_encoding @@ -231,9 +231,6 @@ struct rb_imemo_tmpbuf_struct { #define rb_local_defined p->config->local_defined #define rb_dvar_defined p->config->dvar_defined -#define literal_cmp p->config->literal_cmp -#define literal_hash p->config->literal_hash - #define rb_syntax_error_append p->config->syntax_error_append #define rb_raise p->config->raise #define syntax_error_new p->config->syntax_error_new diff --git a/vm.c b/vm.c index 82bf1ac658d3f3..36f6700ad7d459 100644 --- a/vm.c +++ b/vm.c @@ -3399,10 +3399,7 @@ rb_execution_context_mark(const rb_execution_context_t *ec) if (ec->machine.stack_start && ec->machine.stack_end && ec != GET_EC() /* marked for current ec at the first stage of marking */ ) { - rb_gc_mark_machine_stack(ec); - rb_gc_mark_locations((VALUE *)&ec->machine.regs, - (VALUE *)(&ec->machine.regs) + - sizeof(ec->machine.regs) / (sizeof(VALUE))); + rb_gc_mark_machine_context(ec); } rb_gc_mark(ec->errinfo); @@ -3911,6 +3908,7 @@ Init_VM(void) /* FrozenCore (hidden) */ fcore = rb_class_new(rb_cBasicObject); rb_set_class_path(fcore, rb_cRubyVM, "FrozenCore"); + rb_vm_register_global_object(rb_class_path_cached(fcore)); RBASIC(fcore)->flags = T_ICLASS; klass = rb_singleton_class(fcore); rb_define_method_id(klass, id_core_set_method_alias, m_core_set_method_alias, 3); @@ -3930,7 +3928,6 @@ Init_VM(void) RBASIC_CLEAR_CLASS(klass); rb_obj_freeze(klass); rb_vm_register_global_object(fcore); - rb_vm_register_global_object(rb_class_path_cached(fcore)); rb_mRubyVMFrozenCore = fcore; /* @@ -4201,7 +4198,9 @@ Init_VM(void) */ rb_define_global_const("TOPLEVEL_BINDING", rb_binding_new()); +#ifdef _WIN32 rb_objspace_gc_enable(vm->objspace); +#endif } vm_init_redefined_flag(); diff --git a/vm_backtrace.c b/vm_backtrace.c index 120f9f9c26ee95..6f4379ad23e85c 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -134,7 +134,14 @@ static void location_mark(void *ptr) { struct valued_frame_info *vfi = (struct valued_frame_info *)ptr; - rb_gc_mark(vfi->btobj); + rb_gc_mark_movable(vfi->btobj); +} + +static void +location_ref_update(void *ptr) +{ + struct valued_frame_info *vfi = ptr; + vfi->btobj = rb_gc_location(vfi->btobj); } static void @@ -150,6 +157,7 @@ static const rb_data_type_t location_data_type = { location_mark, RUBY_TYPED_DEFAULT_FREE, NULL, // No external memory to report, + location_ref_update, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE }; @@ -287,15 +295,14 @@ location_label(rb_backtrace_location_t *loc) * 1.times do * puts caller_locations(0).first.label * end - * * end * end * * The result of calling +foo+ is this: * - * label: foo - * label: block in foo - * label: block (2 levels) in foo + * foo + * block in foo + * block (2 levels) in foo * */ static VALUE @@ -315,9 +322,28 @@ location_base_label(rb_backtrace_location_t *loc) } /* - * Returns the base label of this frame. + * Returns the base label of this frame, which is usually equal to the label, + * without decoration. * - * Usually same as #label, without decoration. + * Consider the following example: + * + * def foo + * puts caller_locations(0).first.base_label + * + * 1.times do + * puts caller_locations(0).first.base_label + * + * 1.times do + * puts caller_locations(0).first.base_label + * end + * end + * end + * + * The result of calling +foo+ is this: + * + * foo + * foo + * foo */ static VALUE location_base_label_m(VALUE self) @@ -514,7 +540,10 @@ static const rb_data_type_t backtrace_data_type = { NULL, // No external memory to report, backtrace_update, }, - 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE + /* Cannot set the RUBY_TYPED_EMBEDDABLE flag because the loc of frame_info + * points elements in the backtrace array. This can cause the loc to become + * incorrect if this backtrace object is moved by compaction. */ + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED }; int diff --git a/vm_core.h b/vm_core.h index da1f568b66194c..2a9d5f906f101f 100644 --- a/vm_core.h +++ b/vm_core.h @@ -1044,6 +1044,10 @@ struct rb_execution_context_struct { VALUE *stack_end; size_t stack_maxsize; RUBY_ALIGNAS(SIZEOF_VALUE) jmp_buf regs; + +#ifdef RUBY_ASAN_ENABLED + void *asan_fake_stack_handle; +#endif } machine; }; @@ -1164,11 +1168,6 @@ typedef struct rb_thread_struct { void **specific_storage; struct rb_ext_config ext_config; - -#ifdef RUBY_ASAN_ENABLED - void *asan_fake_stack_handle; -#endif - } rb_thread_t; static inline unsigned int @@ -1875,7 +1874,7 @@ void rb_vm_register_special_exception_str(enum ruby_special_exceptions sp, VALUE #define rb_vm_register_special_exception(sp, e, m) \ rb_vm_register_special_exception_str(sp, e, rb_usascii_str_new_static((m), (long)rb_strlen_lit(m))) -void rb_gc_mark_machine_stack(const rb_execution_context_t *ec); +void rb_gc_mark_machine_context(const rb_execution_context_t *ec); void rb_vm_rewrite_cref(rb_cref_t *node, VALUE old_klass, VALUE new_klass, rb_cref_t **new_cref_ptr); diff --git a/vm_eval.c b/vm_eval.c index 50c81f79d25353..a859a76f245bc8 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1663,8 +1663,6 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, pm_parse_result_t result = { 0 }; pm_options_line_set(&result.options, line); - pm_options_frozen_string_literal_init(&result, rb_iseq_opt_frozen_string_literal()); - // Cout scopes, one for each parent iseq, plus one for our local scope int scopes_count = 0; do { diff --git a/warning.rb b/warning.rb index 4e34c6383393e5..aab5e7c2c6965d 100644 --- a/warning.rb +++ b/warning.rb @@ -40,7 +40,7 @@ module Kernel # baz.rb:6: warning: invalid call to foo # # If category keyword argument is given, passes the category - # to Warning.warn. The category given must be be one of the + # to Warning.warn. The category given must be one of the # following categories: # # :deprecated :: Used for warning for deprecated functionality that may diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 80a43d00dae209..7dbb020eee11fc 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -11,13 +11,15 @@ PATH_SEPARATOR = ; TZ = # skip timezone tests PWD = $(MAKEDIR) empty = +tooldir = $(srcdir)/tool !ifndef MFLAGS MFLAGS=-l !endif !if "$(BASERUBY)" == "" -! if [for %I in (ruby.exe) do @echo BASERUBY = %~s$$PATH:I > baseruby.mk] +! if [ruby $(tooldir)/missing-baseruby.bat 2> nul] +! else if [for %I in (ruby.exe) do @echo BASERUBY = %~s$$PATH:I > baseruby.mk] ! else ! include baseruby.mk ! endif @@ -27,7 +29,7 @@ MFLAGS=-l BASERUBY = !endif !if "$(BASERUBY)" == "" -BASERUBY = echo executable host ruby is required. use --with-baseruby option.^& exit 1 +BASERUBY = $(tooldir:/=\)\missing-baseruby.bat HAVE_BASERUBY = no !else HAVE_BASERUBY = yes @@ -485,7 +487,6 @@ EXTOBJS = dmyext.$(OBJEXT) arch_hdrdir = $(EXTOUT)/include/$(arch) top_srcdir = $(srcdir) hdrdir = $(srcdir)/include -tooldir = $(srcdir)/tool VPATH = $(arch_hdrdir)/ruby;$(hdrdir)/ruby;$(srcdir);$(srcdir)/missing;$(win_srcdir) !ifndef GIT diff --git a/win32/setup.mak b/win32/setup.mak index d4178127bf2282..8c279948215f9c 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -66,6 +66,7 @@ RJIT_SUPPORT = $(RJIT_SUPPORT) # TOOLS << !if defined(BASERUBY) + $(BASERUBY:/=\) "$(srcdir)/tool/missing-baseruby.bat" @echo BASERUBY = $(BASERUBY:/=\)>> $(MAKEFILE) !endif !if "$(RUBY_DEVEL)" == "yes" diff --git a/yjit.h b/yjit.h index 46218a47d72d44..dde9f750aaf72b 100644 --- a/yjit.h +++ b/yjit.h @@ -28,6 +28,7 @@ extern uint64_t rb_yjit_call_threshold; extern uint64_t rb_yjit_cold_threshold; extern uint64_t rb_yjit_live_iseq_count; +extern uint64_t rb_yjit_iseq_alloc_count; extern bool rb_yjit_enabled_p; void rb_yjit_incr_counter(const char *counter_name); void rb_yjit_invalidate_all_method_lookup_assumptions(void); @@ -46,6 +47,7 @@ void rb_yjit_constant_ic_update(const rb_iseq_t *const iseq, IC ic, unsigned ins void rb_yjit_tracing_invalidate_all(void); void rb_yjit_show_usage(int help, int highlight, unsigned int width, int columns); void rb_yjit_lazy_push_frame(const VALUE *pc); +void rb_yjit_invalidate_no_singleton_class(VALUE klass); #else // !USE_YJIT @@ -68,6 +70,7 @@ static inline void rb_yjit_before_ractor_spawn(void) {} static inline void rb_yjit_constant_ic_update(const rb_iseq_t *const iseq, IC ic, unsigned insn_idx) {} static inline void rb_yjit_tracing_invalidate_all(void) {} static inline void rb_yjit_lazy_push_frame(const VALUE *pc) {} +static inline void rb_yjit_invalidate_no_singleton_class(VALUE klass) {} #endif // #if USE_YJIT diff --git a/yjit.rb b/yjit.rb index 50cb2483980fec..eedd00c358150b 100644 --- a/yjit.rb +++ b/yjit.rb @@ -346,6 +346,7 @@ def _print_stats(out: $stderr) # :nodoc: out.puts "bindings_set: " + format_number(13, stats[:binding_set]) out.puts "compilation_failure: " + format_number(13, compilation_failure) if compilation_failure != 0 out.puts "live_iseq_count: " + format_number(13, stats[:live_iseq_count]) + out.puts "iseq_alloc_count: " + format_number(13, stats[:iseq_alloc_count]) out.puts "compiled_iseq_entry: " + format_number(13, stats[:compiled_iseq_entry]) out.puts "cold_iseq_entry: " + format_number_pct(13, stats[:cold_iseq_entry], stats[:compiled_iseq_entry] + stats[:cold_iseq_entry]) out.puts "compiled_iseq_count: " + format_number(13, stats[:compiled_iseq_count]) diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 1d9955260b2322..c6e50beab1b156 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -30,6 +30,7 @@ pub use crate::virtualmem::CodePtr; /// Status returned by code generation functions #[derive(PartialEq, Debug)] enum CodegenStatus { + SkipNextInsn, KeepCompiling, EndBlock, } @@ -97,6 +98,9 @@ pub struct JITState { /// not been written to for the block to be valid. pub stable_constant_names_assumption: Option<*const ID>, + /// A list of classes that are not supposed to have a singleton class. + pub no_singleton_class_assumptions: Vec, + /// When true, the block is valid only when there is a total of one ractor running pub block_assumes_single_ractor: bool, @@ -125,6 +129,7 @@ impl JITState { method_lookup_assumptions: vec![], bop_assumptions: vec![], stable_constant_names_assumption: None, + no_singleton_class_assumptions: vec![], block_assumes_single_ractor: false, perf_map: Rc::default(), perf_stack: vec![], @@ -231,6 +236,20 @@ impl JITState { Some(()) } + /// Assume that objects of a given class will have no singleton class. + /// Return true if there has been no such singleton class since boot + /// and we can safely invalidate it. + pub fn assume_no_singleton_class(&mut self, asm: &mut Assembler, ocb: &mut OutlinedCb, klass: VALUE) -> bool { + if jit_ensure_block_entry_exit(self, asm, ocb).is_none() { + return false; // out of space, give up + } + if has_singleton_class_of(klass) { + return false; // we've seen a singleton class. disable the optimization to avoid an invalidation loop. + } + self.no_singleton_class_assumptions.push(klass); + true + } + fn get_cfp(&self) -> *mut rb_control_frame_struct { unsafe { get_ec_cfp(self.ec) } } @@ -1179,6 +1198,13 @@ pub fn gen_single_block( // Move to the next instruction to compile insn_idx += insn_len(opcode) as u16; + // Move past next instruction when instructed + if status == Some(SkipNextInsn) { + let next_pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx.into()) }; + let next_opcode: usize = unsafe { rb_iseq_opcode_at_pc(iseq, next_pc) }.try_into().unwrap(); + insn_idx += insn_len(next_opcode) as u16; + } + // If the instruction terminates this block if status == Some(EndBlock) { break; @@ -1325,7 +1351,7 @@ fn jit_putobject(asm: &mut Assembler, arg: VALUE) { fn gen_putobject_int2fix( jit: &mut JITState, asm: &mut Assembler, - _ocb: &mut OutlinedCb, + ocb: &mut OutlinedCb, ) -> Option { let opcode = jit.opcode; let cst_val: usize = if opcode == YARVINSN_putobject_INT2FIX_0_.as_usize() { @@ -1333,22 +1359,86 @@ fn gen_putobject_int2fix( } else { 1 }; + let cst_val = VALUE::fixnum_from_usize(cst_val); + + if let Some(result) = fuse_putobject_opt_ltlt(jit, asm, cst_val, ocb) { + return Some(result); + } - jit_putobject(asm, VALUE::fixnum_from_usize(cst_val)); + jit_putobject(asm, cst_val); Some(KeepCompiling) } fn gen_putobject( jit: &mut JITState, asm: &mut Assembler, - _ocb: &mut OutlinedCb, + ocb: &mut OutlinedCb, ) -> Option { let arg: VALUE = jit.get_arg(0); + if let Some(result) = fuse_putobject_opt_ltlt(jit, asm, arg, ocb) { + return Some(result); + } + jit_putobject(asm, arg); Some(KeepCompiling) } +/// Combine `putobject` and and `opt_ltlt` together if profitable, for example when +/// left shifting an integer by a constant amount. +fn fuse_putobject_opt_ltlt( + jit: &mut JITState, + asm: &mut Assembler, + constant_object: VALUE, + ocb: &mut OutlinedCb, +) -> Option { + let next_opcode = unsafe { rb_vm_insn_addr2opcode(jit.pc.add(insn_len(jit.opcode).as_usize()).read().as_ptr()) }; + if next_opcode == YARVINSN_opt_ltlt as i32 && constant_object.fixnum_p() { + // Untag the fixnum shift amount + let shift_amt = constant_object.as_isize() >> 1; + if shift_amt > 63 || shift_amt < 0 { + return None; + } + if !jit.at_current_insn() { + defer_compilation(jit, asm, ocb); + return Some(EndBlock); + } + + let lhs = jit.peek_at_stack(&asm.ctx, 0); + if !lhs.fixnum_p() { + return None; + } + + if !assume_bop_not_redefined(jit, asm, ocb, INTEGER_REDEFINED_OP_FLAG, BOP_LTLT) { + return None; + } + + asm_comment!(asm, "integer left shift with rhs={shift_amt}"); + let lhs = asm.stack_opnd(0); + + // Guard that lhs is a fixnum if necessary + let lhs_type = asm.ctx.get_opnd_type(lhs.into()); + if lhs_type != Type::Fixnum { + asm_comment!(asm, "guard arg0 fixnum"); + asm.test(lhs, Opnd::UImm(RUBY_FIXNUM_FLAG as u64)); + + jit_chain_guard( + JCC_JZ, + jit, + asm, + ocb, + SEND_MAX_DEPTH, + Counter::guard_send_not_fixnums, + ); + } + + asm.stack_pop(1); + fixnum_left_shift_body(asm, lhs, shift_amt as u64); + return Some(SkipNextInsn); + } + return None; +} + fn gen_putself( _jit: &mut JITState, asm: &mut Assembler, @@ -1504,7 +1594,7 @@ fn gen_newarray( ); asm.stack_pop(n.as_usize()); - let stack_ret = asm.stack_push(Type::TArray); + let stack_ret = asm.stack_push(Type::CArray); asm.mov(stack_ret, new_ary); Some(KeepCompiling) @@ -1527,7 +1617,7 @@ fn gen_duparray( vec![ary.into()], ); - let stack_ret = asm.stack_push(Type::TArray); + let stack_ret = asm.stack_push(Type::CArray); asm.mov(stack_ret, new_ary); Some(KeepCompiling) @@ -1547,7 +1637,7 @@ fn gen_duphash( // call rb_hash_resurrect(VALUE hash); let hash = asm.ccall(rb_hash_resurrect as *const u8, vec![hash.into()]); - let stack_ret = asm.stack_push(Type::THash); + let stack_ret = asm.stack_push(Type::CHash); asm.mov(stack_ret, hash); Some(KeepCompiling) @@ -2303,12 +2393,12 @@ fn gen_newhash( asm.cpop_into(new_hash); // x86 alignment asm.stack_pop(num.try_into().unwrap()); - let stack_ret = asm.stack_push(Type::THash); + let stack_ret = asm.stack_push(Type::CHash); asm.mov(stack_ret, new_hash); } else { // val = rb_hash_new(); let new_hash = asm.ccall(rb_hash_new as *const u8, vec![]); - let stack_ret = asm.stack_push(Type::THash); + let stack_ret = asm.stack_push(Type::CHash); asm.mov(stack_ret, new_hash); } @@ -2330,7 +2420,7 @@ fn gen_putstring( vec![EC, put_val.into(), 0.into()] ); - let stack_top = asm.stack_push(Type::TString); + let stack_top = asm.stack_push(Type::CString); asm.mov(stack_top, str_opnd); Some(KeepCompiling) @@ -2351,7 +2441,7 @@ fn gen_putchilledstring( vec![EC, put_val.into(), 1.into()] ); - let stack_top = asm.stack_push(Type::TString); + let stack_top = asm.stack_push(Type::CString); asm.mov(stack_top, str_opnd); Some(KeepCompiling) @@ -4493,8 +4583,18 @@ fn jit_guard_known_klass( let val_type = asm.ctx.get_opnd_type(insn_opnd); if val_type.known_class() == Some(known_klass) { - // We already know from type information that this is a match - return; + // Unless frozen, Array, Hash, and String objects may change their RBASIC_CLASS + // when they get a singleton class. Those types need invalidations. + if unsafe { [rb_cArray, rb_cHash, rb_cString].contains(&known_klass) } { + if jit.assume_no_singleton_class(asm, ocb, known_klass) { + // Speculate that this object will not have a singleton class, + // and invalidate the block in case it does. + return; + } + } else { + // We already know from type information that this is a match + return; + } } if unsafe { known_klass == rb_cNilClass } { @@ -4613,14 +4713,11 @@ fn jit_guard_known_klass( jit_chain_guard(JCC_JNE, jit, asm, ocb, max_chain_depth, counter); if known_klass == unsafe { rb_cString } { - // Upgrading to Type::CString here is incorrect. - // The guard we put only checks RBASIC_CLASS(obj), - // which adding a singleton class can change. We - // additionally need to know the string is frozen - // to claim Type::CString. - asm.ctx.upgrade_opnd_type(insn_opnd, Type::TString); + asm.ctx.upgrade_opnd_type(insn_opnd, Type::CString); } else if known_klass == unsafe { rb_cArray } { - asm.ctx.upgrade_opnd_type(insn_opnd, Type::TArray); + asm.ctx.upgrade_opnd_type(insn_opnd, Type::CArray); + } else if known_klass == unsafe { rb_cHash } { + asm.ctx.upgrade_opnd_type(insn_opnd, Type::CHash); } } } @@ -5048,8 +5145,13 @@ fn jit_rb_int_lshift( Counter::lshift_amount_changed, ); + fixnum_left_shift_body(asm, lhs, shift_amt as u64); + true +} + +fn fixnum_left_shift_body(asm: &mut Assembler, lhs: Opnd, shift_amt: u64) { let in_val = asm.sub(lhs, 1.into()); - let shift_opnd = Opnd::UImm(shift_amt as u64); + let shift_opnd = Opnd::UImm(shift_amt); let out_val = asm.lshift(in_val, shift_opnd); let unshifted = asm.rshift(out_val, shift_opnd); @@ -5062,7 +5164,6 @@ fn jit_rb_int_lshift( let ret_opnd = asm.stack_push(Type::Fixnum); asm.mov(ret_opnd, out_val); - true } fn jit_rb_int_rshift( @@ -6729,11 +6830,16 @@ fn gen_send_bmethod( /// The kind of a value an ISEQ returns enum IseqReturn { Value(VALUE), + LocalVariable(u32), Receiver, } -/// Return the ISEQ's return value if it consists of only putnil/putobject and leave. -fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option) -> Option { +extern { + fn rb_simple_iseq_p(iseq: IseqPtr) -> bool; +} + +/// Return the ISEQ's return value if it consists of one simple instruction and leave. +fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: u32) -> Option { // Expect only two instructions and one possible operand let iseq_size = unsafe { get_iseq_encoded_size(iseq) }; if !(2..=3).contains(&iseq_size) { @@ -6749,6 +6855,17 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option) -> Option { + // Only accept simple positional only cases for both the caller and the callee. + // Reject block ISEQs to avoid autosplat and other block parameter complications. + if captured_opnd.is_none() && unsafe { rb_simple_iseq_p(iseq) } && ci_flags & VM_CALL_ARGS_SIMPLE != 0 { + let ep_offset = unsafe { *rb_iseq_pc_at_idx(iseq, 1) }.as_u32(); + let local_idx = ep_offset_to_local_idx(iseq, ep_offset); + Some(IseqReturn::LocalVariable(local_idx)) + } else { + None + } + } YARVINSN_putnil => Some(IseqReturn::Value(Qnil)), YARVINSN_putobject => Some(IseqReturn::Value(unsafe { *rb_iseq_pc_at_idx(iseq, 1) })), YARVINSN_putobject_INT2FIX_0_ => Some(IseqReturn::Value(VALUE::fixnum_from_usize(0))), @@ -7030,11 +7147,24 @@ fn gen_send_iseq( } // Inline simple ISEQs whose return value is known at compile time - if let (Some(value), None, false) = (iseq_get_return_value(iseq, captured_opnd), block_arg_type, opt_send_call) { + if let (Some(value), None, false) = (iseq_get_return_value(iseq, captured_opnd, flags), block_arg_type, opt_send_call) { asm_comment!(asm, "inlined simple ISEQ"); gen_counter_incr(asm, Counter::num_send_iseq_inline); match value { + IseqReturn::LocalVariable(local_idx) => { + // Put the local variable at the return slot + let stack_local = asm.stack_opnd(argc - 1 - local_idx as i32); + let stack_return = asm.stack_opnd(argc); + asm.mov(stack_return, stack_local); + + // Update the mapping for the return value + let mapping = asm.ctx.get_opnd_mapping(stack_local.into()); + asm.ctx.set_opnd_mapping(stack_return.into(), mapping); + + // Pop everything but the return value + asm.stack_pop(argc as usize); + } IseqReturn::Value(value) => { // Pop receiver and arguments asm.stack_pop(argc as usize + if captured_opnd.is_some() { 0 } else { 1 }); @@ -10330,57 +10460,6 @@ mod tests { assert!(cb.get_write_pos() > 0); } - #[test] - fn test_putobject_qtrue() { - // Test gen_putobject with Qtrue - let (mut jit, _context, mut asm, mut cb, mut ocb) = setup_codegen(); - - let mut value_array: [u64; 2] = [0, Qtrue.into()]; - let pc: *mut VALUE = &mut value_array as *mut u64 as *mut VALUE; - jit.pc = pc; - - let status = gen_putobject(&mut jit, &mut asm, &mut ocb); - - let tmp_type_top = asm.ctx.get_opnd_type(StackOpnd(0)); - - assert_eq!(status, Some(KeepCompiling)); - assert_eq!(tmp_type_top, Type::True); - asm.compile(&mut cb, None).unwrap(); - assert!(cb.get_write_pos() > 0); - } - - #[test] - fn test_putobject_fixnum() { - // Test gen_putobject with a Fixnum to test another conditional branch - let (mut jit, _context, mut asm, mut cb, mut ocb) = setup_codegen(); - - // The Fixnum 7 is encoded as 7 * 2 + 1, or 15 - let mut value_array: [u64; 2] = [0, 15]; - let pc: *mut VALUE = &mut value_array as *mut u64 as *mut VALUE; - jit.pc = pc; - - let status = gen_putobject(&mut jit, &mut asm, &mut ocb); - - let tmp_type_top = asm.ctx.get_opnd_type(StackOpnd(0)); - - assert_eq!(status, Some(KeepCompiling)); - assert_eq!(tmp_type_top, Type::Fixnum); - asm.compile(&mut cb, None).unwrap(); - assert!(cb.get_write_pos() > 0); - } - - #[test] - fn test_int2fix() { - let (mut jit, _context, mut asm, _cb, mut ocb) = setup_codegen(); - jit.opcode = YARVINSN_putobject_INT2FIX_0_.as_usize(); - let status = gen_putobject_int2fix(&mut jit, &mut asm, &mut ocb); - - let tmp_type_top = asm.ctx.get_opnd_type(StackOpnd(0)); - - // Right now we're not testing the generated machine code to make sure a literal 1 or 0 was pushed. I've checked locally. - assert_eq!(status, Some(KeepCompiling)); - assert_eq!(tmp_type_top, Type::Fixnum); - } #[test] fn test_putself() { diff --git a/yjit/src/core.rs b/yjit/src/core.rs index 000e0c0be03314..7f6e7d47f9defd 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -52,20 +52,18 @@ pub enum Type { Flonum, ImmSymbol, - #[allow(unused)] - HeapSymbol, - TString, // An object with the T_STRING flag set, possibly an rb_cString CString, // An un-subclassed string of type rb_cString (can have instance vars in some cases) TArray, // An object with the T_ARRAY flag set, possibly an rb_cArray + CArray, // An un-subclassed array of type rb_cArray (can have instance vars in some cases) THash, // An object with the T_HASH flag set, possibly an rb_cHash + CHash, // An un-subclassed hash of type rb_cHash (can have instance vars in some cases) BlockParamProxy, // A special sentinel value indicating the block parameter should be read from // the current surrounding cfp // The context currently relies on types taking at most 4 bits (max value 15) - // to encode, so if we add two more, we will need to refactor the context, - // or we could remove HeapSymbol, which is currently unused. + // to encode, so if we add any more, we will need to refactor the context. } // Default initialization @@ -98,8 +96,11 @@ impl Type { // Core.rs can't reference rb_cString because it's linked by Rust-only tests. // But CString vs TString is only an optimisation and shouldn't affect correctness. #[cfg(not(test))] - if val.class_of() == unsafe { rb_cString } && val.is_frozen() { - return Type::CString; + match val.class_of() { + class if class == unsafe { rb_cArray } => return Type::CArray, + class if class == unsafe { rb_cHash } => return Type::CHash, + class if class == unsafe { rb_cString } => return Type::CString, + _ => {} } // We likewise can't reference rb_block_param_proxy, but it's again an optimisation; // we can just treat it as a normal Object. @@ -150,8 +151,9 @@ impl Type { match self { Type::UnknownHeap => true, Type::TArray => true, + Type::CArray => true, Type::THash => true, - Type::HeapSymbol => true, + Type::CHash => true, Type::TString => true, Type::CString => true, Type::BlockParamProxy => true, @@ -161,21 +163,17 @@ impl Type { /// Check if it's a T_ARRAY object (both TArray and CArray are T_ARRAY) pub fn is_array(&self) -> bool { - matches!(self, Type::TArray) + matches!(self, Type::TArray | Type::CArray) } - /// Check if it's a T_HASH object + /// Check if it's a T_HASH object (both THash and CHash are T_HASH) pub fn is_hash(&self) -> bool { - matches!(self, Type::THash) + matches!(self, Type::THash | Type::CHash) } /// Check if it's a T_STRING object (both TString and CString are T_STRING) pub fn is_string(&self) -> bool { - match self { - Type::TString => true, - Type::CString => true, - _ => false, - } + matches!(self, Type::TString | Type::CString) } /// Returns an Option with the T_ value type if it is known, otherwise None @@ -186,9 +184,9 @@ impl Type { Type::False => Some(RUBY_T_FALSE), Type::Fixnum => Some(RUBY_T_FIXNUM), Type::Flonum => Some(RUBY_T_FLOAT), - Type::TArray => Some(RUBY_T_ARRAY), - Type::THash => Some(RUBY_T_HASH), - Type::ImmSymbol | Type::HeapSymbol => Some(RUBY_T_SYMBOL), + Type::TArray | Type::CArray => Some(RUBY_T_ARRAY), + Type::THash | Type::CHash => Some(RUBY_T_HASH), + Type::ImmSymbol => Some(RUBY_T_SYMBOL), Type::TString | Type::CString => Some(RUBY_T_STRING), Type::Unknown | Type::UnknownImm | Type::UnknownHeap => None, Type::BlockParamProxy => None, @@ -204,7 +202,9 @@ impl Type { Type::False => Some(rb_cFalseClass), Type::Fixnum => Some(rb_cInteger), Type::Flonum => Some(rb_cFloat), - Type::ImmSymbol | Type::HeapSymbol => Some(rb_cSymbol), + Type::ImmSymbol => Some(rb_cSymbol), + Type::CArray => Some(rb_cArray), + Type::CHash => Some(rb_cHash), Type::CString => Some(rb_cString), _ => None, } @@ -255,6 +255,16 @@ impl Type { return TypeDiff::Compatible(1); } + // A CArray is also a TArray. + if self == Type::CArray && dst == Type::TArray { + return TypeDiff::Compatible(1); + } + + // A CHash is also a THash. + if self == Type::CHash && dst == Type::THash { + return TypeDiff::Compatible(1); + } + // A CString is also a TString. if self == Type::CString && dst == Type::TString { return TypeDiff::Compatible(1); @@ -1644,6 +1654,9 @@ impl JITState { if let Some(idlist) = self.stable_constant_names_assumption { track_stable_constant_names_assumption(blockref, idlist); } + for klass in self.no_singleton_class_assumptions { + track_no_singleton_class_assumption(blockref, klass); + } blockref } diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 31d842531f5515..9547e3fa2cc67b 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -711,6 +711,7 @@ mod manual_defs { pub const RUBY_FIXNUM_MAX: isize = RUBY_LONG_MAX / 2; // From vm_callinfo.h - uses calculation that seems to confuse bindgen + pub const VM_CALL_ARGS_SIMPLE: u32 = 1 << VM_CALL_ARGS_SIMPLE_bit; pub const VM_CALL_ARGS_SPLAT: u32 = 1 << VM_CALL_ARGS_SPLAT_bit; pub const VM_CALL_ARGS_BLOCKARG: u32 = 1 << VM_CALL_ARGS_BLOCKARG_bit; pub const VM_CALL_FCALL: u32 = 1 << VM_CALL_FCALL_bit; diff --git a/yjit/src/invariants.rs b/yjit/src/invariants.rs index 59f7b70e201d8f..c4facb31dd00d9 100644 --- a/yjit/src/invariants.rs +++ b/yjit/src/invariants.rs @@ -53,6 +53,12 @@ pub struct Invariants { /// A map from a block to a set of IDs that it is assuming have not been /// redefined. block_constant_states: HashMap>, + + /// A map from a class to a set of blocks that assume objects of the class + /// will have no singleton class. When the set is empty, it means that + /// there has been a singleton class for the class after boot, so you cannot + /// assume no singleton class going forward. + no_singleton_classes: HashMap>, } /// Private singleton instance of the invariants global struct. @@ -69,6 +75,7 @@ impl Invariants { single_ractor: HashSet::new(), constant_state_blocks: HashMap::new(), block_constant_states: HashMap::new(), + no_singleton_classes: HashMap::new(), }); } } @@ -130,6 +137,23 @@ pub fn track_method_lookup_stability_assumption( .insert(uninit_block); } +/// Track that a block will assume that `klass` objects will have no singleton class. +pub fn track_no_singleton_class_assumption(uninit_block: BlockRef, klass: VALUE) { + Invariants::get_instance() + .no_singleton_classes + .entry(klass) + .or_default() + .insert(uninit_block); +} + +/// Returns true if we've seen a singleton class of a given class since boot. +pub fn has_singleton_class_of(klass: VALUE) -> bool { + Invariants::get_instance() + .no_singleton_classes + .get(&klass) + .map_or(false, |blocks| blocks.is_empty()) +} + // Checks rb_method_basic_definition_p and registers the current block for invalidation if method // lookup changes. // A "basic method" is one defined during VM boot, so we can use this to check assumptions based on @@ -391,6 +415,11 @@ pub fn block_assumptions_free(blockref: BlockRef) { if invariants.constant_state_blocks.is_empty() { invariants.constant_state_blocks.shrink_to_fit(); } + + // Remove tracking for blocks assumping no singleton class + for (_, blocks) in invariants.no_singleton_classes.iter_mut() { + blocks.remove(&blockref); + } } /// Callback from the opt_setinlinecache instruction in the interpreter. @@ -457,6 +486,35 @@ pub extern "C" fn rb_yjit_constant_ic_update(iseq: *const rb_iseq_t, ic: IC, ins }); } +/// Invalidate blocks that assume objects of a given class will have no singleton class. +#[no_mangle] +pub extern "C" fn rb_yjit_invalidate_no_singleton_class(klass: VALUE) { + // Skip tracking singleton classes during boot. Such objects already have a singleton class + // before entering JIT code, so they get rejected when they're checked for the first time. + if unsafe { INVARIANTS.is_none() } { + return; + } + + // We apply this optimization only to Array, Hash, and String for now. + if unsafe { [rb_cArray, rb_cHash, rb_cString].contains(&klass) } { + let no_singleton_classes = &mut Invariants::get_instance().no_singleton_classes; + match no_singleton_classes.get_mut(&klass) { + Some(blocks) => { + // Invalidate existing blocks and let has_singleton_class_of() + // return true when they are compiled again + for block in mem::take(blocks) { + invalidate_block_version(&block); + incr_counter!(invalidate_no_singleton_class); + } + } + None => { + // Let has_singleton_class_of() return true for this class + no_singleton_classes.insert(klass, HashSet::new()); + } + } + } +} + // Invalidate all generated code and patch C method return code to contain // logic for firing the c_return TracePoint event. Once rb_vm_barrier() // returns, all other ractors are pausing inside RB_VM_LOCK_ENTER(), which diff --git a/yjit/src/stats.rs b/yjit/src/stats.rs index c1315aad90c4c9..fb4ed03bd5c4af 100644 --- a/yjit/src/stats.rs +++ b/yjit/src/stats.rs @@ -15,10 +15,14 @@ use crate::cruby::*; use crate::options::*; use crate::yjit::yjit_enabled_p; -/// A running total of how many ISeqs are in the system. +/// Running total of how many ISeqs are in the system. #[no_mangle] pub static mut rb_yjit_live_iseq_count: u64 = 0; +/// Monotonically increasing total of how many ISEQs were allocated +#[no_mangle] +pub static mut rb_yjit_iseq_alloc_count: u64 = 0; + /// A middleware to count Rust-allocated bytes as yjit_alloc_size. #[global_allocator] static GLOBAL_ALLOCATOR: StatsAlloc = StatsAlloc { alloc_size: AtomicUsize::new(0) }; @@ -261,7 +265,7 @@ macro_rules! make_counters { /// The list of counters that are available without --yjit-stats. /// They are incremented only by `incr_counter!` and don't use `gen_counter_incr`. -pub const DEFAULT_COUNTERS: [Counter; 15] = [ +pub const DEFAULT_COUNTERS: [Counter; 16] = [ Counter::code_gc_count, Counter::compiled_iseq_entry, Counter::cold_iseq_entry, @@ -278,6 +282,7 @@ pub const DEFAULT_COUNTERS: [Counter; 15] = [ Counter::invalidate_ractor_spawn, Counter::invalidate_constant_state_bump, Counter::invalidate_constant_ic_fill, + Counter::invalidate_no_singleton_class, ]; /// Macro to increase a counter by name and count @@ -559,6 +564,7 @@ make_counters! { invalidate_ractor_spawn, invalidate_constant_state_bump, invalidate_constant_ic_fill, + invalidate_no_singleton_class, // Currently, it's out of the ordinary (might be impossible) for YJIT to leave gaps in // executable memory, so this should be 0. @@ -746,6 +752,7 @@ fn rb_yjit_gen_stats_dict(context: bool) -> VALUE { hash_aset_usize!(hash, "vm_insns_count", rb_vm_insns_count as usize); hash_aset_usize!(hash, "live_iseq_count", rb_yjit_live_iseq_count as usize); + hash_aset_usize!(hash, "iseq_alloc_count", rb_yjit_iseq_alloc_count as usize); } // If we're not generating stats, put only default counters