diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92056856e6..e9f1863587 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,7 +52,7 @@ jobs: - name: Ruby version run: ruby -v - name: Cache cosmocc - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 id: cache-cosmocc with: path: ~/cosmo diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 28db35af5f..1901f512b9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,12 +22,12 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@v4 + uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: category: "Security" diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index e2ea661c77..f665c8fc88 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -9,7 +9,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 + - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" sync-labels: true diff --git a/.github/workflows/pre-commit-manual.yml b/.github/workflows/pre-commit-manual.yml index 9857e9703a..bd7ac01c62 100644 --- a/.github/workflows/pre-commit-manual.yml +++ b/.github/workflows/pre-commit-manual.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: j178/prek-action@53276d8b0d10f8b6672aa85b4588c6921d0370cc # v2.0.1 + - uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4 with: install-only: true - name: Run manual pre-commit hooks diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 7e01bd0c73..cec73055db 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -15,6 +15,6 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: j178/prek-action@53276d8b0d10f8b6672aa85b4588c6921d0370cc # v2.0.1 + - uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4 with: extra-args: --all-files diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 26403803fa..3eb4f79945 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,7 +40,7 @@ jobs: mv .sha256 "$packagename.sha256" ) - name: Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: draft: true prerelease: ${{ contains(github.ref_name, '-rc') }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3e59a26ddb..928f2df469 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,6 +15,9 @@ repos: - id: check-hooks-apply name: run check-hooks-apply description: check hooks apply to the repository + - id: check-useless-excludes + name: run check-useless-excludes + description: clean up unnecessary exclusion patterns - repo: local hooks: - id: prettier @@ -26,6 +29,15 @@ repos: additional_dependencies: ["prettier@3.7.4"] pass_filenames: false stages: [manual] + - id: check-makefile-indentation + name: check Makefiles are indented with tabs + description: ensures that Makefiles are indented with tabs + entry: ./scripts/check_makefiles_for_tabs.sh + language: system + files: "(?i)^makefile$" + pass_filenames: true # <-- Crucial change: pass filenames to the script + types: [file] # Ensure only regular files are passed, not directories + stages: [manual] - id: check-zip-file-is-not-committed name: disallow zip files description: Zip files are not allowed in the repository @@ -41,7 +53,7 @@ repos: name: run gitleaks description: detect hardcoded secrets with gitleaks - repo: https://github.com/oxipng/oxipng - rev: v10.1.0 + rev: v10.1.1 hooks: - id: oxipng name: run oxipng @@ -106,7 +118,7 @@ repos: types: [markdown] files: \.md$ - repo: https://github.com/rubocop/rubocop - rev: v1.86.0 + rev: v1.86.2 hooks: - id: rubocop name: run rubocop diff --git a/AUTHORS b/AUTHORS index 19e7d0a122..25b3359af5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,25 +1,25 @@ # Authors of mruby (mruby developers) -## The List of Contributors sorted by number of commits (as of 2026-03-02 02877f0) +## The List of Contributors sorted by number of commits (as of 2026-05-31 9d084b0) - 7532 Yukihiro "Matz" Matsumoto (@matz)* - 712 dearblue (@dearblue)* + 7747 Yukihiro "Matz" Matsumoto (@matz)* + 749 dearblue (@dearblue)* 587 KOBAYASHI Shuji (@shuujii) 353 Daniel Bovensiepen (@bovi)* 345 Takeshi Watanabe (@take-cheeze)* 333 Masaki Muranaka (@monaka) - 255 John Bampton (@jbampton) + 266 John Bampton (@jbampton) 234 Jun Hiroe (@suzukaze) 228 Tomoyuki Sahara (@tsahara)* 220 Cremno (@cremno)* 209 Yuki Kurihara (@ksss)+ - 144 Yasuhiro Matsumoto (@mattn)* + 146 Yasuhiro Matsumoto (@mattn)* 113 Carson McDonald (@carsonmcdonald) 104 Tomasz Pędraszewski (@dabroz)* 83 Akira Yumiyama (@akiray03)* 83 skandhas (@skandhas) 80 Masamitsu MURASE (@masamitsu-murase) - 73 Hiroshi Mimaki (@mimaki)* + 79 Hiroshi Mimaki (@mimaki)* 71 Tatsuhiko Kubo (@cubicdaiya)* 71 Yuichiro MASUI (@masuidrive) 62 Yuichiro Kaneko (@yui-knk)+ @@ -36,8 +36,10 @@ 32 Masayoshi Takahashi (@takahashim)+ 31 MATSUMOTO Ryosuke (@matsumotory)* 30 Nobuyoshi Nakada (@nobu) + 29 HASUMI Hitoshi (@hasumikin) 26 Hoshiumi Arata (@hoshiumiarata)* 25 Julian Aron Prenner (@furunkel)* + 23 leviongit (@leviongit) 22 Clayton Smith (@clayton-shopify) 22 Uchio Kondo (@udzura)* 22 Zachary Scott (@zzak)* @@ -50,10 +52,9 @@ 18 Corey Powell (@IceDragon200) 18 Hidetaka Takano (@TJ-Hidetaka-Takano) 18 Jon Maken (@jonforums)+ - 18 leviongit (@leviongit) 18 mirichi (@mirichi) 17 Mitchell Blank Jr (@mitchblank)* - 16 HASUMI Hitoshi (@hasumikin) + 16 Hendrik (@Asmod4n) 16 bggd (@bggd) 16 kano4 (@kano4) 15 Felix Jones (@felixjones)* @@ -76,7 +77,7 @@ 11 RIZAL Reckordp (@Reckordp)+ 11 Seeker (@SeekingMeaning) 11 takkaw (@takkaw) - 10 Hendrik (@Asmod4n) + 10 Chris Hasiński (@khasinski) 10 Miura Hideki (@miura1729) 10 Narihiro Nakamura (@authorNari) 10 YAMAMOTO Masaya (pandax381) @@ -88,6 +89,7 @@ 8 Wataru Ashihara (@wataash)* 7 Bhargava Shastry (@bshastry)* 7 Kouichi Nakanishi (@keizo042) + 7 Paweł Świątkowski (@katafrakt) 7 Rubyist (@expeditiousRubyist) 7 Simon Génier (@simon-shopify) 7 Terence Lee (@hone) @@ -101,7 +103,6 @@ 6 INOUE Yasuyuki (@yasuyuki) 6 Junji Sawada (@junjis0203) 6 Kenji Okimoto (@okkez)+ - 6 Paweł Świątkowski (@katafrakt) 6 Selman ULUG (@selman) 6 Yusuke Endoh (@mame)* 6 buty4649 (@buty4649) @@ -120,7 +121,6 @@ 5 dreamedge (@dreamedge) 5 nkshigeru (@nkshigeru) 5 xuejianqing (@joans321) - 4 Chris Hasiński (@khasinski) 4 Dante Catalfamo (@dantecatalfamo) 4 Goro Kikuchi (@gorogit) 4 Herwin Weststrate (@herwinw) @@ -140,6 +140,7 @@ 4 Yuji Yamano (@yyamano) 4 kurodash (@kurodash)* 4 wanabe (@wanabe)* + 2 0x1eef (@0x1eef) 3 Anton Davydov (@davydovanton) 3 Aurora Nockert (@auroranockert) 3 Carlo Prelz (@asfluido)* @@ -192,6 +193,7 @@ 2 Masahiro Wakame (@vvkame)+ 2 Minao Yamamoto (@tarosay)+ 2 Nihad Abbasov (@NARKOZ) + 2 Pete Kinnecom (@petekinnecom) 2 Robert Mosolgo (@rmosolgo) 2 Russel Hunter Yukawa (@rhykw)+ 2 Ryunosuke SATO (@tricknotes) @@ -220,6 +222,7 @@ 1 Colin MacKenzie IV (@sinisterchipmunk) 1 Daehyub Kim (@lateau) 1 Daniel Varga (@vargad) + 1 David Korczynski (@DavidKorczynski) 1 Diamond Rivero (@diamant3) 1 Edgar Boda-Majer (@eboda) 1 Fangrui Song (@MaskRay) @@ -274,7 +277,6 @@ 1 Patrick Ellis (@pje) 1 Patrick Pokatilo (@SHyx0rmZ) 1 Pavel Evstigneev (@Paxa)+ - 1 Pete Kinnecom (@petekinnecom) 1 Piotr Usewicz (@pusewicz) 1 Prayag Verma (@pra85) 1 Ranmocy (@ranmocy) @@ -282,6 +284,7 @@ 1 Ryan Scott Lewis (@RyanScottLewis) 1 Ryo Okubo (@syucream) 1 SAkira a.k.a. Akira Suzuki (@sakisakira) + 1 SaekiMototsune (@saeki-mototsune) 1 Santiago Rodriguez (@sanrodari) 1 Satoh, Hiroh (@cho45)+ 1 Satoru Naba (@snaba)+ @@ -325,6 +328,7 @@ 1 sbsoftware (@sbsoftware) 1 ssmallkirby (@smallkirby) 1 taku toyama (@tsuichu) + 1 vobloeb (@vobloeb) `*` - Entries unified according to names and addresses `+` - Entries with names different from commits diff --git a/Doxyfile b/Doxyfile index 8c7eb7a262..aaaf13bcf9 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = mruby # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 3.4.0 +PROJECT_NUMBER = 4.0.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/Gemfile.lock b/Gemfile.lock index dc23fe4660..6943833262 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,8 +2,8 @@ GEM remote: https://rubygems.org/ specs: coderay (1.1.3) - rake (13.3.1) - yard (0.9.38) + rake (13.4.2) + yard (0.9.44) yard-coderay (0.1.0) coderay yard diff --git a/README.md b/README.md index 0ec9b9701f..90b674d720 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ To get mruby, you can download the stable version 4.0.0 from the official mruby GitHub repository or clone the trunk of the mruby source tree with the "git clone" command. You can also install and compile mruby using [ruby-install](https://github.com/postmodern/ruby-install), [ruby-build](https://github.com/rbenv/ruby-build), [rvm](https://github.com/rvm/rvm), [conda](https://anaconda.org/channels/conda-forge/packages/mruby/overview) or [Homebrew](https://formulae.brew.sh/formula/mruby). +The release candidate version 4.0.0 of mruby can be downloaded via the following URL: [https://github.com/mruby/mruby/archive/4.0.0-rc3.zip](https://github.com/mruby/mruby/archive/4.0.0-rc3.zip) + The latest development version of mruby can be downloaded via the following URL: [https://github.com/mruby/mruby/zipball/master](https://github.com/mruby/mruby/zipball/master) The trunk of the mruby source tree can be checked out with the diff --git a/benchmark/bm_ao_render.rb b/benchmark/bm_ao_render.rb index ddb42d5c6e..d48f1e1940 100644 --- a/benchmark/bm_ao_render.rb +++ b/benchmark/bm_ao_render.rb @@ -292,9 +292,7 @@ def render(w, h, nsubsamples) r = rad.x / nsfs g = rad.y / nsfs b = rad.z / nsfs - printf("%c", clamp(r)) - printf("%c", clamp(g)) - printf("%c", clamp(b)) + print([clamp(r), clamp(g), clamp(b)].pack("CCC")) end end end @@ -303,7 +301,7 @@ def render(w, h, nsubsamples) # File.open("ao.ppm", "w") do |fp| printf("P6\n") printf("%d %d\n", IMAGE_WIDTH, IMAGE_HEIGHT) - printf("255\n", IMAGE_WIDTH, IMAGE_HEIGHT) + printf("255\n") Scene.new.render(IMAGE_WIDTH, IMAGE_HEIGHT, NSUBSAMPLES) # Scene.new.render(256, 256, 2) # end diff --git a/build_config/cosmopolitan.rb b/build_config/cosmopolitan.rb index 756b4d15cc..c7f46b4326 100644 --- a/build_config/cosmopolitan.rb +++ b/build_config/cosmopolitan.rb @@ -64,10 +64,8 @@ # APE binaries use .com extension conf.exts.executable = '.com' - # Cosmopolitan provides POSIX compatibility, explicitly select POSIX HALs - conf.gem core: 'hal-posix-io' - conf.gem core: 'hal-posix-socket' - conf.gem core: 'hal-posix-dir' + # Cosmopolitan provides POSIX compatibility + conf.ports :posix # Standard library conf.gembox 'stdlib' diff --git a/build_config/glib_hal_test.rb b/build_config/glib_hal_test.rb new file mode 100644 index 0000000000..c90dd47fed --- /dev/null +++ b/build_config/glib_hal_test.rb @@ -0,0 +1,88 @@ +MRuby::Build.new do |conf| + # load specific toolchain settings + conf.toolchain + + # Use mrbgems + # conf.gem 'examples/mrbgems/ruby_extension_example' + # conf.gem 'examples/mrbgems/c_extension_example' do |g| + # g.cc.flags << '-g' # append cflags in this gem + # end + # conf.gem 'examples/mrbgems/c_and_ruby_extension_example' + # conf.gem :core => 'mruby-eval' + # conf.gem :mgem => 'mruby-onig-regexp' + # conf.gem :github => 'mattn/mruby-onig-regexp' + # conf.gem :git => 'git@github.com:mattn/mruby-onig-regexp.git', :branch => 'master', :options => '-v' + + # include the GEM box + #conf.gembox 'default' + + # C compiler settings + # conf.cc do |cc| + # cc.command = ENV['CC'] || 'gcc' + # cc.flags = [ENV['CFLAGS'] || %w()] + # cc.include_paths = ["#{root}/include"] + # cc.defines = %w() + # cc.option_include_path = %q[-I"%s"] + # cc.option_define = '-D%s' + # cc.compile_options = %Q[%{flags} -MMD -o "%{outfile}" -c "%{infile}"] + # end + + # mrbc settings + # conf.mrbc do |mrbc| + # mrbc.compile_options = "-g -B%{funcname} -o-" # The -g option is required for line numbers + # end + + # Linker settings + # conf.linker do |linker| + # linker.command = ENV['LD'] || 'gcc' + # linker.flags = [ENV['LDFLAGS'] || []] + # linker.flags_before_libraries = [] + # linker.libraries = %w() + # linker.flags_after_libraries = [] + # linker.library_paths = [] + # linker.option_library = '-l%s' + # linker.option_library_path = '-L%s' + # linker.link_options = %Q[%{flags} -o "%{outfile}" %{objs} %{libs}] + # end + + # Archiver settings + # conf.archiver do |archiver| + # archiver.command = ENV['AR'] || 'ar' + # archiver.archive_options = 'rs "%{outfile}" %{objs}' + # end + + # Parser generator settings + # conf.yacc do |yacc| + # yacc.command = ENV['YACC'] || 'bison' + # yacc.compile_options = %q[-o "%{outfile}" "%{infile}"] + # end + + # gperf settings + # conf.gperf do |gperf| + # gperf.command = 'gperf' + # gperf.compile_options = %q[-L ANSI-C -C -j1 -i 1 -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"] + # end + + # file extensions + # conf.exts do |exts| + # exts.object = '.o' + # exts.executable = '' # '.exe' if Windows + # exts.library = '.a' + # end + + # file separator + # conf.file_separator = '/' + + # change library directory name from the default "lib" if necessary + # conf.libdir_name = 'lib64' + + # Turn on `enable_debug` for better debugging + conf.enable_sanitizer 'address,undefined' + conf.enable_debug + conf.enable_bintest + conf.enable_test + conf.ports :glib + conf.cc.defines << 'MRB_TASK_BUILD_DEMO' + conf.gem core: 'mruby-task' + conf.gem core: 'mruby-compiler' +end diff --git a/build_config/host-cxx.rb b/build_config/host-cxx.rb index 15cb0c3476..5dc37edd09 100644 --- a/build_config/host-cxx.rb +++ b/build_config/host-cxx.rb @@ -1,4 +1,4 @@ -MRuby::Build.new do |conf| +MRuby::Build.new('host-cxx') do |conf| conf.toolchain # include the default GEMs diff --git a/build_config/host-debug.rb b/build_config/host-debug.rb index 600a4d78fe..8565b77890 100644 --- a/build_config/host-debug.rb +++ b/build_config/host-debug.rb @@ -13,6 +13,8 @@ # Generate mruby debugger command (require mruby-eval) conf.gem :core => "mruby-bin-debugger" + # Regexp is included via stdlib.gembox + # test conf.enable_test # bintest diff --git a/build_config/host-shared.rb b/build_config/host-shared.rb index 76e3a07134..9eaef55f24 100644 --- a/build_config/host-shared.rb +++ b/build_config/host-shared.rb @@ -1,36 +1,94 @@ -# NOTE: Currently, this configuration file does not support VisualC++! -# Your help is needed! +# Build mruby with a shared libmruby.so (in addition to the usual +# libmruby.a / executables). +# +# Produces (in build/host/lib/): +# libmruby.a the static archive (as in the default build) +# libmruby.so the shared library, with SONAME=libmruby.so.. +# libmruby.so.. symlink to libmruby.so (matches SONAME) +# libmruby.map linker version script (MRUBY_) +# +# Also produces the matching libmruby_core.so + symlink for completeness. +# +# Symbol versioning ties to MRUBY_RELEASE_NO (e.g. MRUBY_40000 for 4.0.0). +# mruby has historically had ABI breaks between TEENY versions, so the +# version tag uses the full release number rather than just MAJOR.MINOR. +# +# The shared library is built FROM the static archive via +# `-Wl,--whole-archive`, so the existing static-build pipeline (including +# the test infrastructure) is unaffected. Executables in build/host/bin +# remain statically linked; distros that want dynamically-linked +# executables can rebuild them against the .so. +# +# NOTE: gcc/clang only — VisualC++ support requires a separate config. + +require "mruby/source" MRuby::Build.new do |conf| - # load specific toolchain settings conf.toolchain # include the GEM box conf.gembox 'default' - # C compiler settings + # -fPIC so the static archive's contents can be linked into the .so. conf.compilers.each do |cc| cc.flags << '-fPIC' end - conf.archiver do |archiver| - archiver.command = cc.command - archiver.archive_options = '-shared -o %{outfile} %{objs}' - end - - # file extensions - conf.exts do |exts| - exts.library = '.so' - end - - # file separator - # conf.file_separator = '/' - - # enable this if better compatibility with C++ is desired - #conf.enable_cxx_exception - # Turn on `enable_debug` for better debugging conf.enable_debug conf.enable_bintest conf.enable_test end + +# Add the shared-library targets as a post-build pass, so the default +# static-build pipeline remains untouched. +MRuby.each_target do + next unless name == "host" + + libdir = File.join(build_dir, libdir_name) + vermap = File.join(build_dir, "libmruby.map") + vertag = "MRUBY_#{MRuby::Source::MRUBY_RELEASE_NO}" + + # Generate the version script eagerly — it has no .o dependencies and is + # tiny enough that lazy generation isn't worth the rake plumbing. + mkdir_p File.dirname(vermap) + File.write(vermap, <<~MAP) + #{vertag} { + global: *; + local: *; + }; + MAP + + major = MRuby::Source::MRUBY_RELEASE_MAJOR + minor = MRuby::Source::MRUBY_RELEASE_MINOR + + [ + [libmruby_static, "libmruby"], + [libmruby_core_static, "libmruby_core"], + ].each do |archive, basename| + so = File.join(libdir, "#{basename}.so") + symlink = "#{so}.#{major}.#{minor}" + soname = "#{basename}.so.#{major}.#{minor}" + + # Build .so from the static archive via --whole-archive. + file so => [archive, vermap] do |t| + _pp "LD", so.relative_path + sh "#{cc.command} -shared -fPIC -o #{so}" \ + " -Wl,-soname,#{soname}" \ + " -Wl,--version-script=#{vermap}" \ + " -Wl,--whole-archive #{archive} -Wl,--no-whole-archive" \ + " -lm" + end + products << so + + # SONAME-matching symlink: needed so executables linked with -lmruby + # (which embeds the SONAME as DT_NEEDED) can find the library at + # runtime via standard search paths. + file symlink => so do |t| + _pp "LN", "#{symlink.relative_path} -> #{File.basename(so)}" + rm_f symlink + File.symlink(File.basename(so), symlink) + end + products << symlink + end +end diff --git a/build_config/playstationportable.rb b/build_config/playstationportable.rb index 66096b0546..453082517e 100644 --- a/build_config/playstationportable.rb +++ b/build_config/playstationportable.rb @@ -72,7 +72,9 @@ conf.gem :core => "mruby-os-memsize" conf.gem :core => "mruby-proc-binding" conf.gem :core => "mruby-sleep" - conf.gem :core => "mruby-io" - conf.gem :core => "mruby-dir" - #conf.gem :core => "mruby-socket" unsupported + # Disabled until PSP-specific HALs are available; the POSIX HALs depend on + # APIs that the PSP SDK does not fully provide. + # conf.gem :core => "mruby-io" + # conf.gem :core => "mruby-dir" + # conf.gem :core => "mruby-socket" end diff --git a/doc/guides/amalgamation.md b/doc/guides/amalgamation.md index 955f150817..aa9db3dcf0 100644 --- a/doc/guides/amalgamation.md +++ b/doc/guides/amalgamation.md @@ -73,14 +73,14 @@ The following gems work with amalgamation: - `mruby-enum-ext`, `mruby-compar-ext` - `mruby-error`, `mruby-math`, `mruby-struct` - `mruby-bigint`, `mruby-rational`, `mruby-complex` -- `mruby-io` (with `hal-posix-io`) -- `mruby-task` (with `hal-posix-task`) +- `mruby-io` (with the active `ports//` HAL) +- `mruby-task` (with the active `ports//` HAL) ### Platform-Dependent Gems Gems that use a HAL (Hardware Abstraction Layer) include platform-specific code in the amalgamation. For example, if -`mruby-io` selects `hal-posix-io` on Linux, the generated `mruby.c` +`mruby-io` selects its POSIX port on Linux, the generated `mruby.c` contains POSIX-specific code and cannot be compiled on Windows. If you need amalgamated files for multiple platforms, generate them diff --git a/doc/guides/capi.md b/doc/guides/capi.md index f5c24152ea..bc91d9c478 100644 --- a/doc/guides/capi.md +++ b/doc/guides/capi.md @@ -381,6 +381,13 @@ mrb_define_method(mrb, point, "initialize", point_init, MRB_ARGS_REQ(2)); mrb_define_method(mrb, point, "x", point_x, MRB_ARGS_NONE()); ``` +**Do not call into mruby from a `dfree` handler.** The handler runs +from inside GC sweep; allocating Ruby objects, calling +`mrb_funcall`, `mrb_yield`, raising exceptions, or otherwise +re-entering the VM can trigger a recursive GC that revisits the +same object and causes double-free. Keep `dfree` to `mrb_free` / +plain C cleanup of the wrapped data only. + ## Exception Handling ### Raising Exceptions diff --git a/doc/guides/debugger.md b/doc/guides/debugger.md index 7059132c0e..b2c948b1dc 100644 --- a/doc/guides/debugger.md +++ b/doc/guides/debugger.md @@ -40,7 +40,7 @@ To confirm mrdb was installed properly, run mrdb with the `--version` option: ```bash $ mrdb --version -mruby 3.4.0 (2025-04-20) +mruby 4.0.0 (2026-04-20) ``` ## 2.2 Basic Operation diff --git a/doc/guides/mrbgems.md b/doc/guides/mrbgems.md index d7104821ee..07f59f7f73 100644 --- a/doc/guides/mrbgems.md +++ b/doc/guides/mrbgems.md @@ -172,6 +172,8 @@ The maximal GEM structure looks like this: | +- src/ <- Source for C extension | + +- ports// <- Platform-specific C sources (see Platform Ports) + | +- tools/ <- Source for Executable (in C) | +- test/ <- Test code (Ruby) @@ -182,6 +184,8 @@ contains C/C++ files to extend mruby. The `include` directory contains C/C++ hea files. The `test` directory contains C/C++ and pure Ruby files for testing purposes which will be used by `mrbtest`. `mrbgem.rake` contains the specification to compile C and Ruby files. `README.md` is a short description of your GEM. +The optional `ports//` directories hold platform-specific C sources +selected at build time; see [Platform Ports](#platform-ports-ports) below. ## Build process @@ -332,6 +336,64 @@ end **NOTE**: Using the `build_settings` method will cause GEM's all build command settings directly written in the block passed to `MRuby::Gem::Specification.new` to be ignored. +## Platform Ports (ports/) + +A gem may ship platform-specific C sources under `ports//` +subdirectories. The build configuration selects which port name(s) +are active via `conf.ports`, and each gem compiles the sources of +the first matching `ports//` it ships: + +```ruby +MRuby::Build.new do |conf| + conf.toolchain + conf.ports :posix # selects ports/posix/ across all gems +end +``` + +`conf.ports` accepts multiple names as a fallback chain. Each gem +picks the first directory in the list that exists on its side: + +```ruby +conf.ports :rp2040, :posix # try rp2040 per-gem, else posix +``` + +Host builds auto-detect `:posix` or `:win` when `conf.ports` is +not set. Sources outside `ports/` (i.e. `src/`) are always +compiled regardless of the port selection. + +### External HAL Provider Gems + +A third-party gem may replace another gem's bundled port at build +time. A gem whose name matches `hal--` is recognized +as the external HAL provider for the target gem whose name's last +`-`-separated segment is ``. For example, `hal-task-glib` +overrides the HAL of `mruby-task`; `hal-io-uring` would override +`mruby-io`. The HAL provider must depend on its target so it can +`#include` the target's HAL header: + +```ruby +MRuby::Gem::Specification.new('hal-task-glib') do |spec| + spec.license = 'MIT' + spec.author = 'Your Name' + spec.summary = 'GLib HAL for mruby-task' + spec.add_dependency 'mruby-task', core: 'mruby-task' + # src/ contains the HAL implementation +end +``` + +When a matching HAL provider gem is present in the build, the +target gem's `ports//` sources are dropped from the +build automatically. The HAL provider's own sources supply the +implementation instead, avoiding duplicate symbol errors at link +time. Loading two gems that match the same `hal--*` +prefix is a build error. + +The naming convention is the only signal -- no spec attribute, +no `add_dependency` flag is required. A gem author who wants to +contribute an additional bundled port upstream sends a PR adding +`/ports//`; a gem author who prefers to ship +out of tree publishes a `hal--` gem instead. + ## C Extension mruby can be extended with C. This is possible by using the C API to diff --git a/doc/guides/rom-method-table.md b/doc/guides/rom-method-table.md index 0e7f7f0c03..f6470fc35c 100644 --- a/doc/guides/rom-method-table.md +++ b/doc/guides/rom-method-table.md @@ -117,8 +117,8 @@ Defined in `include/mruby/class.h`: ```c union mrb_mt_ptr { + mrb_func_t func; /* first member: see MRB_MT_ENTRY note */ const struct RProc *proc; - mrb_func_t func; }; typedef struct mrb_mt_entry { @@ -139,9 +139,12 @@ typedef struct mrb_mt_tbl { ```c /* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with - MRB_MT_PRIVATE. The macro OR's in MRB_MT_FUNC automatically. */ + MRB_MT_PRIVATE. The macro OR's in MRB_MT_FUNC automatically. + `func` must be the first member of `union mrb_mt_ptr` so that + positional initialization works on legacy C++ compilers that do + not accept C99 designated initializers. */ #define MRB_MT_ENTRY(fn, sym, flags) \ - { { .func = (fn) }, (sym), (flags) | MRB_MT_FUNC } + { { (fn) }, (sym), (flags) | MRB_MT_FUNC } /* Extract aspec from combined flags */ #define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff)) diff --git a/doc/internal/gc.md b/doc/internal/gc.md index 3171bcbe16..2e4ba8e0ef 100644 --- a/doc/internal/gc.md +++ b/doc/internal/gc.md @@ -131,7 +131,9 @@ limit = (GC_STEP_SIZE / 100) * step_ratio ``` With default `step_ratio = 200` and `GC_STEP_SIZE = 1024`, the -limit is 2048 objects per step. +limit is 2048 objects per step. After each step, `gc_debt` is +decremented by the actual number of objects processed, so larger +steps repay more debt. When the gray stack is exhausted, the final marking phase re-marks the arena and global variables to catch objects created during @@ -273,7 +275,7 @@ From Ruby: `GC.generational_mode = true/false`. `mrb_obj_alloc()` is the core allocation function: 1. If `MRB_GC_STRESS` is defined, run a full GC -2. If `gc->live >= gc->threshold`, run `mrb_incremental_gc()` +2. Increment `gc->gc_debt`; if positive, run `mrb_incremental_gc()` 3. Ensure arena has space (`gc_arena_keep`) 4. Pop an object from the freelist of `gc->free_heaps` 5. If no free pages, allocate a new page (`add_heap`) @@ -299,18 +301,43 @@ The object's type is set to `MRB_TT_FREE` after freeing. ## Triggering GC -### Automatic +### Debt Model -GC runs automatically when `gc->live >= gc->threshold` during -object allocation. After each cycle: +GC uses a **debt-based feedback model** to balance allocation +rate against collection work. The key field is `gc->gc_debt` +(signed integer): + +- **Negative** = credit (GC is ahead, no collection needed) +- **Zero** = balanced +- **Positive** = debt (allocation outpacing collection, GC runs) + +Each object allocation increments `gc_debt` by 1. When debt +goes positive, `mrb_incremental_gc()` runs. Each incremental +step decrements debt by `GC_STEP_SIZE` (1024), giving credit +for many future allocations. + +When a GC cycle completes, credit is calculated from +`interval_ratio`: ```text -threshold = (live_after_mark / 100) * interval_ratio +credit = (live_after_mark / 100) * interval_ratio - live_after_mark minimum: GC_STEP_SIZE (1024) +gc_debt = -credit ``` -With default `interval_ratio = 200`, GC triggers when live objects -roughly double. +With default `interval_ratio = 200` and 1000 live objects: +`credit = (1000/100)*200 - 1000 = 1000`, so approximately 1000 +allocations can occur before the next GC cycle begins. + +### Malloc Pressure + +When `gc->malloc_threshold` is set (non-zero), the GC also +tracks bytes allocated through `mrb_realloc_simple()` in +`gc->malloc_increase`. When `malloc_increase` exceeds +`malloc_threshold`, the counter resets and an incremental GC +step runs. This captures memory pressure from large buffers +(e.g., long strings) that would otherwise be invisible to the +object-count-based debt model. ### Manual @@ -333,6 +360,7 @@ From Ruby: `GC.start`. | `MRB_GC_FIXED_ARENA` | off | Use fixed-size arena | | `MRB_GC_TURN_OFF_GENERATIONAL` | off | Disable generational mode | | `MRB_GC_STRESS` | off | Full GC on every allocation (debug) | +| `MRB_GC_STATS` | off | Enable GC statistics counters | | `MRB_USE_MALLOC_TRIM` | off | Call `malloc_trim()` after full GC | ### Runtime @@ -340,18 +368,120 @@ From Ruby: `GC.start`. From Ruby code: ```ruby -GC.interval_ratio = 200 # threshold = live * ratio / 100 -GC.step_ratio = 200 # objects per incremental step +GC.interval_ratio = 200 # controls debt credit after GC cycle +GC.step_ratio = 200 # objects per incremental step +GC.step_limit = 0 # 0=unlimited, >0=absolute step cap +GC.malloc_threshold = 0 # 0=disabled, >0=bytes to trigger GC GC.generational_mode = true -GC.start # force full GC -GC.enable # re-enable GC -GC.disable # disable GC +GC.start # force full GC +GC.enable # re-enable GC +GC.disable # disable GC ``` +### GC Statistics + +`GC.stat` returns a Hash with GC state and statistics: + +```ruby +GC.stat +# => { +# :live => 5432, # live object count +# :debt => -1024, # GC debt (negative=credit, positive=behind) +# :state => 0, # 0=root, 1=marking, 2=sweeping +# :generational => true, # generational mode enabled +# :full => false, # major GC in progress +# :step_limit => 0, # current step limit setting +# :malloc_increase => 8192, # malloc bytes since last cycle +# :malloc_threshold => 0, # current malloc threshold setting +# } +``` + +With `MRB_GC_STATS` enabled, additional keys are available: + +```ruby +# :total => 15, # total GC invocations +# :minor => 12, # minor GC count +# :major => 3, # major GC count +``` + +### Tuning Guide + +**`interval_ratio`** (default 200): Controls how many allocations +occur between GC cycles. Higher values reduce GC frequency but +increase peak memory. The debt credit after each cycle is +`(live_after_mark / 100) * interval_ratio - live_after_mark`. + +**`step_ratio`** (default 200): Controls how much work each +incremental step performs. Higher values make each step larger, +reducing total GC overhead but increasing individual pause times. + +**`step_limit`** (default 0, unlimited): Caps the maximum work +per incremental step regardless of `step_ratio`. Useful for +real-time applications that need bounded pause times. The +effective step size is `min(step_ratio calculation, step_limit)`. + +**`malloc_threshold`** (default 0, disabled): Triggers GC when +cumulative `malloc`/`realloc` bytes exceed this threshold. Useful +when applications allocate large buffers (strings, data objects) +that create memory pressure without proportional object count +increase. + +### Practical Tuning Examples + +**Allocation-heavy workloads** (many short-lived Procs, closures, +blocks): GC sweep dominates because of high object churn. Increase +`interval_ratio` to reduce GC frequency: + +```ruby +GC.interval_ratio = 400 # ~12% faster than default (200) +``` + +Higher values (400-600) reduce sweep overhead at the cost of more +dead objects accumulating before collection. Values above 600 show +diminishing returns. Peak memory usage increases temporarily, but +live object count after GC remains the same. + +**CPU-intensive workloads** (numeric computation, recursive methods +with no object allocation): GC parameters have negligible impact +because GC rarely runs. No tuning needed. + +**Real-time or latency-sensitive** applications: Use `step_limit` +to bound pause times: + +```ruby +GC.step_limit = 256 # cap incremental step to 256 objects +``` + +This makes GC pauses more predictable but increases total GC +overhead (more steps needed per cycle). + +**Large buffer workloads** (reading files, building long strings): +Set `malloc_threshold` to trigger GC when buffer allocations +accumulate, even if object count is low: + +```ruby +GC.malloc_threshold = 1024 * 1024 # trigger GC per ~1MB allocated +``` + +### Diagnosing GC Overhead + +Use `GC.stat` to monitor GC behavior at runtime: + +```ruby +s = GC.stat +puts "live objects: #{s[:live]}" +puts "GC debt: #{s[:debt]}" # positive = GC is behind +puts "GC state: #{s[:state]}" # 0=idle, 1=marking, 2=sweeping +``` + +If `debt` is frequently positive during performance-critical +sections, increase `interval_ratio`. If memory usage is too high, +decrease it. + ## Source Files | File | Contents | | -------------------- | --------------------------------- | -| `src/gc.c` | GC implementation (~1400 lines) | +| `src/gc.c` | GC implementation | | `include/mruby/gc.h` | `mrb_gc` structure, public GC API | | `include/mruby.h` | Arena save/restore macros | diff --git a/doc/limitations.md b/doc/limitations.md index 5d7a3181f9..b0908583a7 100644 --- a/doc/limitations.md +++ b/doc/limitations.md @@ -15,6 +15,23 @@ This document is collecting these limitations. This document does not contain a complete list of limitations. Please help to improve it by submitting your findings. +## Features provided by mrbgems + +Many Ruby features that CRuby builds into its core are provided by +mrbgems in mruby. Which features are actually available depends on +which mrbgems are linked into the build. The `default.gembox` and +`stdlib.gembox` cover the common cases, but a minimal build can omit +familiar features such as `Kernel#binding` (provided by +`mruby-binding`), `Kernel#catch`/`throw` (by `mruby-catch`), +`Enumerable` extensions, `Comparable`, IO, regular expressions, and +many more. + +This is by design rather than a limitation per se. When porting Ruby +code to mruby, a `NoMethodError` or `NameError` often means "the gem +providing this feature is not linked in" rather than "mruby does not +support it." Adding the relevant gem to the build configuration is +usually enough. + ## `Kernel.raise` in rescue clause `Kernel.raise` without arguments does not raise the current exception within @@ -133,12 +150,6 @@ The re-defined `+` operator does not accept any arguments. `'ab'` Behavior of the operator wasn't changed. -## `Kernel#binding` is not supported without mruby-binding gem - -`Kernel#binding` method requires the `mruby-binding` gem (included -in the `metaprog` gembox). Without this gem, `binding` is not -available. - ## `nil?` redefinition in conditional expressions Redefinition of `nil?` is ignored in conditional expressions. @@ -290,3 +301,151 @@ arbitrary-precision integers when included. (included in the `stdlib` gembox). Even with the gem, `ObjectSpace.each_object` has limited functionality compared to CRuby. + +## No Implicit Type Conversion (`to_int`, `to_str`, `to_ary`, ...) + +mruby does not perform implicit type conversion through methods +like `to_int`, `to_str`, `to_ary`, or `to_hash`. CRuby uses these +to let user-defined classes duck-type as built-in types — for +example `Array#[]` calls `to_int` on its argument, `String#+` calls +`to_str`, and multiple assignment calls `to_ary` on its right-hand +side. mruby's built-in operations require the actual built-in type +and do not consult these conversion methods. + +```ruby +class MyInt; def to_int; 42; end; end +class MyStr; def to_str; "x"; end; end +class MyAry; def to_ary; [1,2,3]; end; end +``` + +#### CRuby + +``` +[1,2,3][MyInt.new] # => nil (to_int called -> ary[42]) +"a" + MyStr.new # => "ax" (to_str called) +a, b, c = MyAry.new # => a=1, b=2, c=3 (to_ary called) +``` + +#### mruby + +``` +[1,2,3][MyInt.new] # TypeError +"a" + MyStr.new # TypeError +a, b, c = MyAry.new # a=, b=nil, c=nil (treated as single value) +``` + +Identity versions of `to_int`, `to_str`, `to_sym`, and `to_hash` +remain defined on the corresponding built-in types so that +`respond_to?(:to_str)`-style checks work for built-in instances. +`Float#to_int` and `Array#to_ary` are intentionally not defined. + +Explicit conversion methods (`to_i`, `to_s`, `to_a`) work as in +CRuby and are called by features such as string interpolation and +the splat operator (`*obj`). + +This is a deliberate trade-off: implicit conversion forces every +coercion site to go through method dispatch and can silently mask +type-mismatch bugs. + +## Nested `def` in Singleton-Method Context + +`def` written inside a singleton method (`def self.foo`) is placed +on a different class in mruby than in CRuby. CRuby registers the +inner method as an instance method of the lexical enclosing class. +mruby registers it as a method of the enclosing receiver's +singleton class, which makes it visible as a class method of the +enclosing class. + +```ruby +class SomeClass + def self.class_method + def nested; 'nested!'; end + end +end +SomeClass.class_method +``` + +#### CRuby + +``` +SomeClass.nested # NoMethodError +SomeClass.new.nested # => "nested!" (instance method) +``` + +#### mruby + +``` +SomeClass.nested # => "nested!" (class method) +SomeClass.new.nested # NoMethodError +``` + +Writing nested `def` like this is unusual; this difference rarely +surfaces in practical code. + +## `Proc#dup` / `Proc#clone` is Always Orphan + +A `dup` or `clone` of a block given to a method is always treated as +an orphan block in mruby — calling it raises `LocalJumpError` if the +block contains `break` or `return`. CRuby is finer-grained: the copy +inherits the orphan status of its original, so the copy only becomes +orphan once the original yielding method returns. + +```ruby +def m(&b) + b.dup +end + +x = m { break 1 } +x.call +``` + +#### CRuby + +``` +LocalJumpError # raised only after m returns; if called inside m, + # the dup is still a live block +``` + +#### mruby + +``` +LocalJumpError # always raised — the dup is orphan from the moment + # it is created +``` + +mruby's stricter rule keeps `RProc` from needing a back-pointer to +the original block (which would also enlarge the GC mark set). + +## `Class#initialize` Can Be Re-Invoked + +CRuby raises `TypeError: already initialized class` when `initialize` +is invoked on a class that has already been set up. mruby's +`Class#initialize` has no such guard — invoking it on an existing +class through `__send__`, `send`, or `UnboundMethod#bind_call` +silently succeeds. The superclass argument is ignored in this case, +so the call cannot rewrite the class hierarchy; only the block (if +any) is evaluated with the class as receiver. + +```ruby +Klass = Class.new +Klass.__send__(:initialize) {} +``` + +#### CRuby + +``` +TypeError: already initialized class +``` + +#### mruby + +``` +The block is evaluated in the context of Klass; no error is raised. +The superclass is not changed even when one is passed as an argument. +``` + +`Module#initialize` is re-callable in both implementations, so this +divergence is `Class`-specific. Adding the CRuby check would require +an additional flag bit on every `RClass`; mruby leaves the bit +unspent because no destructive side effects are possible through +this path. diff --git a/doc/mruby4.0.md b/doc/mruby4.0.md new file mode 100644 index 0000000000..200b58b9e3 --- /dev/null +++ b/doc/mruby4.0.md @@ -0,0 +1,367 @@ +# User visible changes in `mruby4.0` from `mruby3.4` + +"**_NOTE_**:" are changes to be aware of. + +# The language + +## Pattern Matching + +mruby now supports pattern matching (case/in) syntax: + +- Basic pattern matching with `case`/`in` syntax ([dadfac6](https://github.com/mruby/mruby/commit/dadfac6)) +- Array pattern matching ([ec67fd9](https://github.com/mruby/mruby/commit/ec67fd9)) +- Hash pattern matching ([2147263](https://github.com/mruby/mruby/commit/2147263)) +- Find pattern matching (`[*pre, target, *post]`) ([6c4d98b](https://github.com/mruby/mruby/commit/6c4d98b)) +- Pin operator (`^variable`) ([1de6340](https://github.com/mruby/mruby/commit/1de6340)) +- Guard clauses (`if`/`unless` conditions) ([07ac110](https://github.com/mruby/mruby/commit/07ac110)) +- One-line pattern matching (`expr in pattern`) ([e76ce24](https://github.com/mruby/mruby/commit/e76ce24)) +- Brace-less hash pattern support ([e8096bf](https://github.com/mruby/mruby/commit/e8096bf)) + +## Other Language Changes + +- `&nil` in formal parameters to explicitly opt out of block arguments ([b07518e](https://github.com/mruby/mruby/commit/b07518e)) +- Trailing comma in method definition parameters: `def foo(a, b,)` ([f78334b](https://github.com/mruby/mruby/commit/f78334b)) +- Array/Hash/String subclasses can now override `[]` and `[]=` methods ([#6675](https://github.com/mruby/mruby/pull/6675)) +- `OP_SETIDX` optimization for Array and Hash ([ddd8fe1](https://github.com/mruby/mruby/commit/ddd8fe1)) +- `case`/`in` without `else` now raises `NoMatchingPatternError` ([d8de35b](https://github.com/mruby/mruby/commit/d8de35b)) +- Allow compound statement in parenthesized argument context ([919cbd8](https://github.com/mruby/mruby/commit/919cbd8)) + +# Changes in C API + +- **_NOTE_**: `mrb_alloca()` renamed to `mrb_temp_alloc()` ([7fe5c2e](https://github.com/mruby/mruby/commit/7fe5c2e)) +- **_NOTE_**: `mruby/ext/io.h` renamed to `mruby/io.h` ([2813f79](https://github.com/mruby/mruby/commit/2813f79)) +- `mrb_gc_add_region()` for contiguous heap region support ([072855a](https://github.com/mruby/mruby/commit/072855a)) +- `mrb_class_outer()` to get the outer class/module ([3a1b771](https://github.com/mruby/mruby/commit/3a1b771)) +- `MRB_ENSURE()` macro for exception-safe cleanup ([3ac682b](https://github.com/mruby/mruby/commit/3ac682b)) +- `mrb_time_get_tm()` for accessing struct tm ([daaaafe](https://github.com/mruby/mruby/commit/daaaafe)) +- `MRB_OPEN_FAILURE()` macro for checking mrb_open result ([40b0cb9](https://github.com/mruby/mruby/commit/40b0cb9)) +- `mrb_print_error()` now handles NULL gracefully ([8e50a45](https://github.com/mruby/mruby/commit/8e50a45)) +- `mrb_open()` returns mrb_state with exc set on init failure ([05ffe0c](https://github.com/mruby/mruby/commit/05ffe0c)) +- `mrb_utf8_to_buf()` for UTF-8 encoding consolidation ([7e28e68](https://github.com/mruby/mruby/commit/7e28e68)) +- `kh_is_end()` macro for safe khash iteration ([893cc75](https://github.com/mruby/mruby/commit/893cc75)) +- `mrb_bigint_p()` always defined regardless of bigint gem presence ([6c4a8c0](https://github.com/mruby/mruby/commit/6c4a8c0)) +- `RInteger` and `RFloat` added to `RVALUE` union ([13dbca0](https://github.com/mruby/mruby/commit/13dbca0)) + +# ROM Method Tables + +All built-in classes and most extension gems now use read-only method +tables stored in `.rodata` instead of heap-allocated hash tables. Method +definitions no longer consume heap memory, significantly reducing memory +footprint for embedded use. + +Core classes converted: BasicObject, Object, Module, Class, Kernel, +String, Array, Hash, Numeric, Integer, Float, NilClass, TrueClass, +FalseClass, Range, Symbol, Exception, Proc. + +Extension gems converted: mruby-string-ext, mruby-array-ext, mruby-set, +mruby-struct, mruby-class-ext, mruby-numeric-ext, mruby-random, +mruby-kernel-ext, mruby-complex, mruby-rational, mruby-io, mruby-socket, +mruby-method, mruby-metaprog, mruby-time, mruby-hash-ext, mruby-proc-ext, +mruby-symbol-ext, mruby-range-ext, mruby-object-ext. + +# GC and Memory + +- **_NOTE_**: `MRB_NO_PRESYM` removed; presym is now always enabled ([81689045](https://github.com/mruby/mruby/commit/81689045)) +- Replace `gcnext` gray linked list with fixed-size gray stack, reducing per-object overhead ([31fea170](https://github.com/mruby/mruby/commit/31fea170)) +- `mrb_gc_add_region()` for providing contiguous memory buffers as GC heap pages ([072855a](https://github.com/mruby/mruby/commit/072855a)) +- Chunk-based pool for symbol string allocation ([e05bd8f](https://github.com/mruby/mruby/commit/e05bd8f)) +- Reduce `IV_INITIAL_SIZE` from 4 to 2 ([6bd1f51](https://github.com/mruby/mruby/commit/6bd1f51)) +- Lossless float encoding using rotation in word boxing ([b6148c8](https://github.com/mruby/mruby/commit/b6148c8)) +- Lossless rotation encoding for 32-bit float32 word boxing ([14a5cfb](https://github.com/mruby/mruby/commit/14a5cfb)) +- Consolidated irep allocation for .mrb loading ([74fb045](https://github.com/mruby/mruby/commit/74fb045)) +- Object shapes (hidden classes) for `MRB_TT_OBJECT` IV storage, sharing key layouts across objects with the same instance variable assignment order ([8d10056](https://github.com/mruby/mruby/commit/8d10056)) + +# Build & Configuration + +- **_NOTE_**: `MRB_WORDBOX_NO_FLOAT_TRUNCATE` renamed to `MRB_WORDBOX_NO_INLINE_FLOAT` (old name still works) ([59e1fe2](https://github.com/mruby/mruby/commit/59e1fe2)) +- **_NOTE_**: `MRB_INT64` on 32-bit now requires `MRB_NO_BOXING` (other boxing modes cannot guarantee alignment for heap-allocated 64-bit integers) ([eaaa66b](https://github.com/mruby/mruby/commit/eaaa66b)) +- Amalgamation support via `rake amalgam` task ([d995ca2](https://github.com/mruby/mruby/commit/d995ca2)) +- New Platform: Cosmopolitan Libc ([#6681](https://github.com/mruby/mruby/pull/6681)) +- Emscripten: use native WASM exception handling ([ca364e3](https://github.com/mruby/mruby/commit/ca364e3)) +- HAL (Hardware Abstraction Layer) for platform abstraction in mruby-io, mruby-socket, mruby-dir, mruby-task ([74ca22f](https://github.com/mruby/mruby/commit/74ca22f)) +- `MRUBY_MIRB_READLINE` environment variable to control readline library selection ([0aafb83](https://github.com/mruby/mruby/commit/0aafb83)) +- MSYS2 drive letter support in build script ([77f6ffe](https://github.com/mruby/mruby/commit/77f6ffe)) +- Inter-gem headers separated from external API headers ([#6671](https://github.com/mruby/mruby/pull/6671)) + +# Changes in mrbgems + +## New Gems + +- **mruby-task**: Cooperative multitasking with preemptive scheduling ([ae0d7a0](https://github.com/mruby/mruby/commit/ae0d7a0)) +- **mruby-benchmark**: Benchmarking gem ([2f40f3d](https://github.com/mruby/mruby/commit/2f40f3d)) +- **mruby-strftime**: Time#strftime implementation ([b31e22f](https://github.com/mruby/mruby/commit/b31e22f)) + +## mruby-bin-mirb Improvements + +- Custom multi-line editor replacing readline ([527018c](https://github.com/mruby/mruby/commit/527018c)) +- Syntax highlighting for keywords, strings, result values, hash key symbols ([624272b](https://github.com/mruby/mruby/commit/624272b), [1713d4a](https://github.com/mruby/mruby/commit/1713d4a)) +- Automatic light/dark theme detection via OSC 11 ([db4c8d9](https://github.com/mruby/mruby/commit/db4c8d9)) +- Tab completion support ([2f15282](https://github.com/mruby/mruby/commit/2f15282)) +- Colored output for prompts and errors ([b36e0b4](https://github.com/mruby/mruby/commit/b36e0b4)) +- Auto-indentation and auto-dedent ([d52f318](https://github.com/mruby/mruby/commit/d52f318), [e901b6d](https://github.com/mruby/mruby/commit/e901b6d)) +- Command history with Up/Down navigation ([5f85c1b](https://github.com/mruby/mruby/commit/5f85c1b)) +- Line numbers in multi-line prompts ([5a3f0e2](https://github.com/mruby/mruby/commit/5a3f0e2)) +- UTF-8 multibyte character support ([4a97da3](https://github.com/mruby/mruby/commit/4a97da3)) + +## mruby-bigint Improvements + +- Toom-3 multiplication for large numbers ([99620804](https://github.com/mruby/mruby/commit/99620804)) +- Karatsuba multiplication for medium-sized numbers ([85e81072](https://github.com/mruby/mruby/commit/85e81072)) +- Balance multiplication for asymmetric operands ([0220ec2b](https://github.com/mruby/mruby/commit/0220ec2b)) +- Divide-and-conquer optimization for `to_s` ([990ff90f](https://github.com/mruby/mruby/commit/990ff90f)) +- Consolidated mpn layer for low-level limb operations ([9ef3362f](https://github.com/mruby/mruby/commit/9ef3362f)) +- Always use 32-bit limbs by default ([c747c77f](https://github.com/mruby/mruby/commit/c747c77f)) + +## Other Gem Changes + +- **_NOTE_**: `Hash#deconstruct_keys` removed for CRuby compatibility ([34b9412](https://github.com/mruby/mruby/commit/34b9412)) +- **mruby-enum-lazy**: Fix `Lazy#flat_map` to handle non-enumerable block return values ([#6765](https://github.com/mruby/mruby/pull/6765)) +- **mruby-array-ext**: Add `Array#find` and `Array#rfind` methods +- **mruby-io**: Add `IO#putc` and `Kernel#putc` ([baff6e6](https://github.com/mruby/mruby/commit/baff6e6)) +- **mruby-random**: Replace xoshiro with PCG for better memory efficiency ([f1bab01](https://github.com/mruby/mruby/commit/f1bab01)) +- **mruby-compiler**: Variable-sized AST nodes for reduced memory usage +- **mruby-compiler**: `no_return_value` context flag for script optimization ([613b03a](https://github.com/mruby/mruby/commit/613b03a)) +- `initialize_copy` and `respond_to_missing?` defined as private ([#6708](https://github.com/mruby/mruby/pull/6708)) +- Struct keyword argument initialization ([#6574](https://github.com/mruby/mruby/pull/6574)) + +# Compiler Improvements + +- Variable-sized AST nodes for reduced memory consumption ([821b989](https://github.com/mruby/mruby/commit/821b989)) +- Pattern matching bytecode optimizations ([21d4135](https://github.com/mruby/mruby/commit/21d4135)) +- Optimized masgn to generate literals directly into target registers ([fb5d966](https://github.com/mruby/mruby/commit/fb5d966)) +- Optimized splat of literal arrays in args/literals ([1cb8d73](https://github.com/mruby/mruby/commit/1cb8d73)) +- Early termination after too many parse errors ([510ebd7](https://github.com/mruby/mruby/commit/510ebd7)) +- Chunk array literals at 64 elements to reduce register pressure ([f98d641](https://github.com/mruby/mruby/commit/f98d641)) +- Chunk `%w()` and `%i()` literals to reduce register pressure ([62cf0dc](https://github.com/mruby/mruby/commit/62cf0dc)) + +# VM Optimizations + +New super-instructions that fuse common opcode sequences to reduce bytecode size and improve performance: + +- `OP_SEND0`/`OP_SSEND0`: Zero-argument method call, avoiding argument count setup ([9123ef4](https://github.com/mruby/mruby/commit/9123ef4)) +- `OP_TDEF`/`OP_SDEF`: Fused method definition combining TCLASS/SCLASS+METHOD+DEF into single instruction, saving 4 bytes per method ([8d4f47e](https://github.com/mruby/mruby/commit/8d4f47e)) +- `OP_GETIDX0`: Fast path for `array[0]` and `Array#first` access ([680f7ec](https://github.com/mruby/mruby/commit/680f7ec)) +- `OP_ADDILV`/`OP_SUBILV`: Local variable increment/decrement fusion for `i += n` patterns ([43f64b9](https://github.com/mruby/mruby/commit/43f64b9)) +- `OP_RETSELF`: Single-byte instruction for `return self` pattern ([a71db8c](https://github.com/mruby/mruby/commit/a71db8c)) +- `OP_RETNIL`: Single-byte instruction for `return nil` pattern ([64e30bf](https://github.com/mruby/mruby/commit/64e30bf)) +- `OP_RETTRUE`/`OP_RETFALSE`: Single-byte instructions for `return true`/`return false` patterns ([0b15727](https://github.com/mruby/mruby/commit/0b15727)) +- `OP_MATCHERR`: Pattern matching error with conditional execution ([944168a](https://github.com/mruby/mruby/commit/944168a)) +- `OP_BLKCALL`: Direct block call for `yield`, bypassing method dispatch (13-17% faster) ([3aa2872](https://github.com/mruby/mruby/commit/3aa2872)) + +Other optimizations: + +- 1.5x stack growth instead of linear growth for reduced reallocations ([f7988c93](https://github.com/mruby/mruby/commit/f7988c93)) +- Skip keyword argument hash duplication ([5970e350](https://github.com/mruby/mruby/commit/5970e350)) + +# Fixed GitHub Issues + +- [#5531](https://github.com/mruby/mruby/issues/5531) Hash recursion detection +- [#6506](https://github.com/mruby/mruby/issues/6506) Constant lookup in singleton class +- [#6507](https://github.com/mruby/mruby/issues/6507) tally multi-values +- [#6508](https://github.com/mruby/mruby/issues/6508) Enumerable#sum index +- [#6509](https://github.com/mruby/mruby/issues/6509) scope_new nregs initialization +- [#6515](https://github.com/mruby/mruby/issues/6515) y.tab.c in repository +- [#6516](https://github.com/mruby/mruby/issues/6516) Private backquote +- [#6554](https://github.com/mruby/mruby/issues/6554) Socket private #initialize +- [#6570](https://github.com/mruby/mruby/issues/6570) instance_eval crash +- [#6613](https://github.com/mruby/mruby/issues/6613) const_added hook during bootstrapping +- [#6635](https://github.com/mruby/mruby/issues/6635), [#6636](https://github.com/mruby/mruby/issues/6636) Colon3 constant lookup +- [#6637](https://github.com/mruby/mruby/issues/6637) arm64 mingw64 builtin setjmp/longjmp +- [#6642](https://github.com/mruby/mruby/issues/6642) Task segfault when sleep called from C +- [#6645](https://github.com/mruby/mruby/issues/6645) Set memory leak from double initialization +- [#6646](https://github.com/mruby/mruby/issues/6646) IO#gets negative length +- [#6647](https://github.com/mruby/mruby/issues/6647) IO#ungetc buffer overflow +- [#6648](https://github.com/mruby/mruby/issues/6648) sprintf buffer overread +- [#6649](https://github.com/mruby/mruby/issues/6649) Array#sort! use-after-realloc +- [#6650](https://github.com/mruby/mruby/issues/6650) Array#fill validation +- [#6652](https://github.com/mruby/mruby/issues/6652) Array comparison use-after-realloc +- [#6657](https://github.com/mruby/mruby/issues/6657) Exception handling for ||= on class variables +- [#6659](https://github.com/mruby/mruby/issues/6659) Super with keyword arguments +- [#6660](https://github.com/mruby/mruby/issues/6660) Regression on struct/array/hash == override with super +- [#6662](https://github.com/mruby/mruby/issues/6662) Array set operations use-after-free +- [#6664](https://github.com/mruby/mruby/issues/6664) Set#flatten memory leak +- [#6666](https://github.com/mruby/mruby/issues/6666) Regexp literal with encoding +- [#6668](https://github.com/mruby/mruby/issues/6668) Method#== for aliased methods and comparison bug +- [#6671](https://github.com/mruby/mruby/issues/6671) Separate inter-gem headers from external API headers +- [#6674](https://github.com/mruby/mruby/issues/6674) Document pattern matching limitations +- [#6675](https://github.com/mruby/mruby/issues/6675) Allow Hash#[] to be aliased again +- [#6687](https://github.com/mruby/mruby/issues/6687) Expand MRB_SYM/MRB_GVSYM support for symbols with special characters +- [#6698](https://github.com/mruby/mruby/issues/6698) Bigint tests fail on architectures other than x86_64 and i386 +- [#6701](https://github.com/mruby/mruby/issues/6701) Heap-use-after-free in mrb_vm_exec involving mruby-rational / mruby-bigint +- [#6702](https://github.com/mruby/mruby/issues/6702) mruby-bigint doesn't compile in C++ project +- [#6704](https://github.com/mruby/mruby/issues/6704) Heap-buffer-overflow in mrb_vm_exec via malformed source code +- [#6705](https://github.com/mruby/mruby/issues/6705) Can't get outer class of an object in C +- [#6713](https://github.com/mruby/mruby/issues/6713) mruby-polarssl not work +- [#6720](https://github.com/mruby/mruby/issues/6720) Random float range: different behavior from CRuby +- [#6722](https://github.com/mruby/mruby/issues/6722) RBreak size overflow on 32-bit platforms with MRB_NO_BOXING +- [#6740](https://github.com/mruby/mruby/issues/6740) `%w()`/`%i()` register pressure with large literals +- [#6741](https://github.com/mruby/mruby/issues/6741) `case`/`in` without `else` should raise `NoMatchingPatternError` +- [#6760](https://github.com/mruby/mruby/issues/6760) `mrb_gc_unregister()` not removing all matching entries + +# Merged Pull Requests + +- [#6418](https://github.com/mruby/mruby/pull/6418) Add `ls-lint` with GitHub Actions +- [#6492](https://github.com/mruby/mruby/pull/6492) fix a typo, update specs +- [#6493](https://github.com/mruby/mruby/pull/6493) Fix TYPO in memory.md +- [#6495](https://github.com/mruby/mruby/pull/6495) Remove `MRB_ENDIAN_LOHI()` that is no longer in use +- [#6497](https://github.com/mruby/mruby/pull/6497) gha: update `build.yml` try `windows-2025` image +- [#6498](https://github.com/mruby/mruby/pull/6498) Clean up and standardize the pre-commit config +- [#6501](https://github.com/mruby/mruby/pull/6501) Update pre-commit Node.js version to `v22.14.0 LTS` +- [#6502](https://github.com/mruby/mruby/pull/6502) pre-commit: update prettier to the latest version +- [#6503](https://github.com/mruby/mruby/pull/6503) misc: fix typos +- [#6505](https://github.com/mruby/mruby/pull/6505) mrbgems: fix spelling +- [#6510](https://github.com/mruby/mruby/pull/6510) Fixed class method visibility via `module_function` +- [#6511](https://github.com/mruby/mruby/pull/6511) Exclude the external project "lrama" from pre-commit +- [#6513](https://github.com/mruby/mruby/pull/6513) mruby 3.4.0 released +- [#6517](https://github.com/mruby/mruby/pull/6517) core/codegen.c: remove unneeded duplicate semicolon +- [#6518](https://github.com/mruby/mruby/pull/6518) Change mrbc_args.flags bit width from 2 to 3 +- [#6519](https://github.com/mruby/mruby/pull/6519) Add `tools/lrama` to `.prettierignore` +- [#6520](https://github.com/mruby/mruby/pull/6520) pre-commit: autoupdate and update node LTS version +- [#6521](https://github.com/mruby/mruby/pull/6521) Add codespell config file `.codespellrc` +- [#6522](https://github.com/mruby/mruby/pull/6522) gha: label more files +- [#6523](https://github.com/mruby/mruby/pull/6523) add `rand(Range)` and unify implementations of `Random#rand` and `Kernel#rand` +- [#6524](https://github.com/mruby/mruby/pull/6524) Fix Kernel#p when no argument +- [#6525](https://github.com/mruby/mruby/pull/6525) Skip adding empty input to mirb history +- [#6526](https://github.com/mruby/mruby/pull/6526) Add build config for Luckfox Pico embedded SBC +- [#6528](https://github.com/mruby/mruby/pull/6528) misc: fix spelling +- [#6530](https://github.com/mruby/mruby/pull/6530) Revert "class.c (find_visibility_scope): when callinfo returns, \*ep == NULL; #6512" +- [#6531](https://github.com/mruby/mruby/pull/6531) Improve method table performance by rehashing at 75% load factor +- [#6532](https://github.com/mruby/mruby/pull/6532) Reverted method table optimizations to prioritize memory savings +- [#6533](https://github.com/mruby/mruby/pull/6533) Fix calling `extended` callback +- [#6534](https://github.com/mruby/mruby/pull/6534) Add descriptive comment to mrb_read_float function +- [#6535](https://github.com/mruby/mruby/pull/6535) Added descriptive comments for functions/macros in src/mempool.c +- [#6536](https://github.com/mruby/mruby/pull/6536) Add descriptive comments to public functions in src/debug.c +- [#6537](https://github.com/mruby/mruby/pull/6537) Updated comments in `cdump.c` to remove the `@brief` tag +- [#6539](https://github.com/mruby/mruby/pull/6539) Add descriptive comments for functions in src/load.c +- [#6540](https://github.com/mruby/mruby/pull/6540) Add descriptive comments to MRB_API functions in object.c +- [#6541](https://github.com/mruby/mruby/pull/6541) Add descriptive comments for MRB_API functions in src/array.c +- [#6542](https://github.com/mruby/mruby/pull/6542) Add descriptive comments to MRB_API functions in src/symbol.c +- [#6543](https://github.com/mruby/mruby/pull/6543) Add descriptive comments to several functions in src/dump.c +- [#6544](https://github.com/mruby/mruby/pull/6544) Fix build strings that must be mutable +- [#6545](https://github.com/mruby/mruby/pull/6545) Add descriptive comments for MRB_API functions in src/class.c +- [#6548](https://github.com/mruby/mruby/pull/6548) Add descriptive comments to MRB_API functions in src/etc.c +- [#6549](https://github.com/mruby/mruby/pull/6549) Add descriptive comments to kernel functions +- [#6550](https://github.com/mruby/mruby/pull/6550) Add descriptive comments for MRB_API functions in src/proc.c +- [#6551](https://github.com/mruby/mruby/pull/6551) Add descriptive comments for MRB_API functions in src/state.c +- [#6552](https://github.com/mruby/mruby/pull/6552) Fix: Correct placement of comments in src/variable.c +- [#6553](https://github.com/mruby/mruby/pull/6553) Add descriptive comments for MRB_API functions in src/vm.c +- [#6555](https://github.com/mruby/mruby/pull/6555) `mrb_mt_foreach()` needs to update the pointer at each loop +- [#6556](https://github.com/mruby/mruby/pull/6556) `iv_foreach()` needs to update the pointer at each loop +- [#6560](https://github.com/mruby/mruby/pull/6560) Refactor: Improve Set GC marking and freeing +- [#6561](https://github.com/mruby/mruby/pull/6561) pre-commit updates and fix prettier entrypoint +- [#6562](https://github.com/mruby/mruby/pull/6562) misc: fix spelling word case +- [#6563](https://github.com/mruby/mruby/pull/6563) pre-commit add rubocop with one rule spaces for indentation +- [#6564](https://github.com/mruby/mruby/pull/6564) Remove jumanjihouse pre-commit hooks no longer maintained +- [#6565](https://github.com/mruby/mruby/pull/6565) Rubocop: fix target Ruby version; add two more cops; fix lint error +- [#6566](https://github.com/mruby/mruby/pull/6566) Removed unreferenced variables in `CrossBuild#run_bintest` +- [#6567](https://github.com/mruby/mruby/pull/6567) Avoid array object creation in `cmd_bin` method in bintest +- [#6568](https://github.com/mruby/mruby/pull/6568) mruby-bin-debugger depends on mruby-bin-mrbc in bintest +- [#6569](https://github.com/mruby/mruby/pull/6569) sed s/Mruby/MRuby/g +- [#6571](https://github.com/mruby/mruby/pull/6571) Update limitations.md to add behavior on small hash +- [#6572](https://github.com/mruby/mruby/pull/6572) Add Claude Code GitHub Workflow +- [#6573](https://github.com/mruby/mruby/pull/6573) pre-commit fixes and updates +- [#6574](https://github.com/mruby/mruby/pull/6574) Support initializing structs via keyword arguments +- [#6575](https://github.com/mruby/mruby/pull/6575) Fix typo in file time methods +- [#6581](https://github.com/mruby/mruby/pull/6581) Merge `mrb_obj_iv_inspect()` into `mrb_obj_inspect()` +- [#6582](https://github.com/mruby/mruby/pull/6582) Stricter type tag in `mrb_obj_alloc()` +- [#6583](https://github.com/mruby/mruby/pull/6583) Add fallback to local build_config.rb before using default configuration +- [#6585](https://github.com/mruby/mruby/pull/6585) Fix typo in mruby3.2 docs +- [#6586](https://github.com/mruby/mruby/pull/6586) Makefile: refactor add docs and add command line `help` target +- [#6587](https://github.com/mruby/mruby/pull/6587) Add Set#hash tests +- [#6588](https://github.com/mruby/mruby/pull/6588) Add CodeQL Analysis for GitHub Actions +- [#6589](https://github.com/mruby/mruby/pull/6589) Add pre-commit hook `check-zip-file-is-not-committed` +- [#6591](https://github.com/mruby/mruby/pull/6591) mruby-eval fix license link in README +- [#6593](https://github.com/mruby/mruby/pull/6593) README: Add Contributors Avatars, Star History, Table of Contents +- [#6598](https://github.com/mruby/mruby/pull/6598) Fix heap buffer overflow in `#method_missing` +- [#6599](https://github.com/mruby/mruby/pull/6599) pre-commit: run `markdown-link-check`, `oxipng`, `prettier` manually +- [#6600](https://github.com/mruby/mruby/pull/6600) `dreamcast_shelf build config`: update to use KallistiOS wrappers +- [#6601](https://github.com/mruby/mruby/pull/6601) fix: skip local build_config.rb when working in MRUBY_ROOT +- [#6602](https://github.com/mruby/mruby/pull/6602) Improved iseq annotations for `new` and `!=` +- [#6604](https://github.com/mruby/mruby/pull/6604) pre-commit config updates +- [#6607](https://github.com/mruby/mruby/pull/6607) fix bigint on raspberry pi +- [#6610](https://github.com/mruby/mruby/pull/6610) Extract golden ratio prime into constant +- [#6614](https://github.com/mruby/mruby/pull/6614) Fix uninitialized variable in io_gets causing segmentation fault +- [#6617](https://github.com/mruby/mruby/pull/6617) Fix various minor problems and speed up build +- [#6618](https://github.com/mruby/mruby/pull/6618) Stop generating unnecessary C++ files in mruby-bin-mruby +- [#6621](https://github.com/mruby/mruby/pull/6621) Set up all GEMS before mruby core tasks definition +- [#6624](https://github.com/mruby/mruby/pull/6624) Fixed wrong `MRuby::Build.current` at the top level of `mrbgem.rake` +- [#6628](https://github.com/mruby/mruby/pull/6628) Revert `File.absolute_path` logic +- [#6629](https://github.com/mruby/mruby/pull/6629) pre-commit update +- [#6631](https://github.com/mruby/mruby/pull/6631) Revert "Rakefile: make the whole thing parallel unless SERIAL=1" +- [#6633](https://github.com/mruby/mruby/pull/6633) Fix a heap-buffer-overflow in str strip! methods +- [#6643](https://github.com/mruby/mruby/pull/6643) Fix crash caused by an incorrect node type check in `codegen_masgn` +- [#6651](https://github.com/mruby/mruby/pull/6651) Address stack-use-after-return in the mruby bigint implementation +- [#6653](https://github.com/mruby/mruby/pull/6653) Improve HAL-related components for MinGW +- [#6655](https://github.com/mruby/mruby/pull/6655) Preventing Memory Leaks in `Array#__combination_init` +- [#6656](https://github.com/mruby/mruby/pull/6656) Fix integer overflow in allocation size calculation +- [#6663](https://github.com/mruby/mruby/pull/6663) Added the `kh_is_end()` macro function +- [#6665](https://github.com/mruby/mruby/pull/6665) Fixed use-after-free with `Set#join` +- [#6670](https://github.com/mruby/mruby/pull/6670) Arranging VM dispatch macros +- [#6673](https://github.com/mruby/mruby/pull/6673) Adjust broken license links; clean up Markdown +- [#6677](https://github.com/mruby/mruby/pull/6677) gha: run pre-commit with `--color=always` +- [#6678](https://github.com/mruby/mruby/pull/6678) Put ls-lint and pre-commit in separate workflow files +- [#6679](https://github.com/mruby/mruby/pull/6679) pre-commit autoupdate; update node and prettier +- [#6681](https://github.com/mruby/mruby/pull/6681) Add Cosmopolitan Libc build configuration +- [#6689](https://github.com/mruby/mruby/pull/6689) docs: fix pre-commit manual hooks; fix link +- [#6694](https://github.com/mruby/mruby/pull/6694) Fix mirb build under Cosmopolitan +- [#6695](https://github.com/mruby/mruby/pull/6695) Dependabot: add a cooldown period for new releases +- [#6696](https://github.com/mruby/mruby/pull/6696) Fix parse error with required kwargs and omitted parens +- [#6699](https://github.com/mruby/mruby/pull/6699) Fix mruby-task for PicoRuby Integration +- [#6700](https://github.com/mruby/mruby/pull/6700) Fix float/double pack/unpack on s390x +- [#6706](https://github.com/mruby/mruby/pull/6706) Refactor task class to use symbol IDs +- [#6708](https://github.com/mruby/mruby/pull/6708) `initialize_copy` and `respond_to_missing?` defined as private +- [#6709](https://github.com/mruby/mruby/pull/6709) Add the `MRB_ENSURE()` macro +- [#6711](https://github.com/mruby/mruby/pull/6711) Fix out of bounds read and write in IO.select +- [#6714](https://github.com/mruby/mruby/pull/6714) Fix OP_DEBUG operand type and add NULL check for debug_op_hook +- [#6716](https://github.com/mruby/mruby/pull/6716) Fixes identity for proc object +- [#6717](https://github.com/mruby/mruby/pull/6717) Fix mruby-task: wrapping by critical section and setting initial task receiver +- [#6718](https://github.com/mruby/mruby/pull/6718) Add installation instructions for conda and Homebrew +- [#6723](https://github.com/mruby/mruby/pull/6723) Add `RInteger` and `RFloat` to `RVALUE` +- [#6727](https://github.com/mruby/mruby/pull/6727) Language documentation: update wording of "overloading" section +- [#6729](https://github.com/mruby/mruby/pull/6729) Simplifying dependency addition for gensym task +- [#6730](https://github.com/mruby/mruby/pull/6730) Simplifying presym file generation actions +- [#6733](https://github.com/mruby/mruby/pull/6733) Include `mruby/presym.h` for all source files +- [#6734](https://github.com/mruby/mruby/pull/6734) Chunk array literals at 64 elements to reduce register pressure +- [#6735](https://github.com/mruby/mruby/pull/6735) Prevent full recompilation without changes to presym file +- [#6739](https://github.com/mruby/mruby/pull/6739) Fix MSYS2 build error with drive letters +- [#6743](https://github.com/mruby/mruby/pull/6743) Chunk `%w()` and `%i()` literals to reduce register pressure +- [#6744](https://github.com/mruby/mruby/pull/6744) Raise `NoMatchingPatternError` in `case`/`in` without `else` +- [#6747](https://github.com/mruby/mruby/pull/6747) Correctly handle empty hash as default named argument +- [#6749](https://github.com/mruby/mruby/pull/6749) Fix microcontroller profile +- [#6750](https://github.com/mruby/mruby/pull/6750) Fix out-of-bounds read and divide-by-zero in `Array#product` +- [#6752](https://github.com/mruby/mruby/pull/6752) Fix `attr_reader`-generated methods accepting extra arguments +- [#6753](https://github.com/mruby/mruby/pull/6753) Further optimize `Array#product` +- [#6754](https://github.com/mruby/mruby/pull/6754) Mark `attr_reader` procs as noarg +- [#6755](https://github.com/mruby/mruby/pull/6755) Reload `ci` after `mrb_hash_delete_key()` in keyword argument handling +- [#6756](https://github.com/mruby/mruby/pull/6756) Avoid impact of object modifications caused by `mrb_vm_exec()` calls +- [#6758](https://github.com/mruby/mruby/pull/6758) Don't assign result of `mrb_funcall()` directly to `regs` +- [#6759](https://github.com/mruby/mruby/pull/6759) Define `mrb_bigint_p()` always +- [#6761](https://github.com/mruby/mruby/pull/6761) Fix `mrb_gc_unregister()` to remove all matching entries +- [#6762](https://github.com/mruby/mruby/pull/6762) Write generated test C files atomically to avoid build race condition +- [#6765](https://github.com/mruby/mruby/pull/6765) Fix `Lazy#flat_map` to handle non-enumerable block return values +- [#6767](https://github.com/mruby/mruby/pull/6767) Allow compound statement in parenthesized argument context +- [#6780](https://github.com/mruby/mruby/pull/6780) Fix `String#prepend` with self-referencing arguments +- [#6781](https://github.com/mruby/mruby/pull/6781) Protect `sprintf` format string from mutation during callbacks +- [#6783](https://github.com/mruby/mruby/pull/6783) Pin GitHub Actions workflows to commit hashes + +# Security Fixes + +- Buffer overflow in bigint uadd ([3f2611e](https://github.com/mruby/mruby/commit/3f2611e)) +- Stack buffer overflow in Montgomery reduction ([edce0a3](https://github.com/mruby/mruby/commit/edce0a3)) +- Buffer overflow in pack_uu encoding ([2993302](https://github.com/mruby/mruby/commit/2993302)) +- Buffer overflow in IO#ungetc ([01ab2ff](https://github.com/mruby/mruby/commit/01ab2ff)) +- Heap-buffer-overflow in pattern alternation codegen ([eea9e30](https://github.com/mruby/mruby/commit/eea9e30)) +- Out of bounds read and write in IO.select ([44831711](https://github.com/mruby/mruby/commit/44831711)) +- Off-by-one in bounds check for symbol names and pool strings in load.c ([b3b8c01](https://github.com/mruby/mruby/commit/b3b8c01)) +- Use-after-free in Set operations ([a6b55e7](https://github.com/mruby/mruby/commit/a6b55e7)) +- Use-after-free in Array set operations ([729b84c](https://github.com/mruby/mruby/commit/729b84c)) +- Use-after-free in Set#join ([0e653eb](https://github.com/mruby/mruby/commit/0e653eb)) +- Use-after-realloc in Array#sort! ([eb39897](https://github.com/mruby/mruby/commit/eb39897)) +- Heap-use-after-free in insertion_sort ([099d2c47](https://github.com/mruby/mruby/commit/099d2c47)) +- Integer overflow in str_check_length ([6afff1c3](https://github.com/mruby/mruby/commit/6afff1c3)) +- Integer overflow in Integer#lcm ([070bef24](https://github.com/mruby/mruby/commit/070bef24)) +- Heap buffer overflow in `#method_missing` ([550d10a](https://github.com/mruby/mruby/commit/550d10a)) +- Out-of-bounds read and divide-by-zero in `Array#product` ([8441eaf](https://github.com/mruby/mruby/commit/8441eaf)) +- Heap buffer overflow in `String#prepend` with self-referencing arguments ([18ba026](https://github.com/mruby/mruby/commit/18ba026)) +- Use-after-free in `sprintf` via `to_s` callback mutating format string ([48fc422](https://github.com/mruby/mruby/commit/48fc422)) +- Multiple memory leak fixes in bigint, Set, Array, and Task gems diff --git a/include/mrbconf.h b/include/mrbconf.h index 34797f6f96..8938e6287a 100644 --- a/include/mrbconf.h +++ b/include/mrbconf.h @@ -172,6 +172,13 @@ #define MRB_SYMBOL_LINEAR_THRESHOLD 256 #endif +/* Maximum number of dynamic symbols (created at runtime via to_sym etc.) + Presyms, inline symbols, and mrb_intern_static symbols are excluded. + Set to 0 to disable the limit. */ +#ifndef MRB_SYMBOL_MAX +#define MRB_SYMBOL_MAX 4096 +#endif + /* obsolete configurations */ #if defined(DISABLE_STDIO) || defined(MRB_DISABLE_STDIO) # define MRB_NO_STDIO diff --git a/include/mruby.h b/include/mruby.h index 1c6e8385ad..4a51b09730 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -113,6 +113,8 @@ #include "mrbconf.h" +typedef struct mrb_state mrb_state; + #include #include #include @@ -156,8 +158,6 @@ typedef uint32_t mrb_aspec; typedef struct mrb_irep mrb_irep; -struct mrb_state; - #ifndef MRB_FIXED_STATE_ATEXIT_STACK_SIZE #define MRB_FIXED_STATE_ATEXIT_STACK_SIZE 5 #endif @@ -224,7 +224,7 @@ mrb_static_assert_powerof2(MRB_METHOD_CACHE_SIZE); * @param self The self object * @return [mrb_value] The function's return value */ -typedef mrb_value (*mrb_func_t)(struct mrb_state *mrb, mrb_value self); +typedef mrb_value (*mrb_func_t)(mrb_state *mrb, mrb_value self); typedef struct { uint32_t flags; /* method flags (no symbol packed) */ @@ -243,9 +243,26 @@ struct mrb_cache_entry { }; #endif +#ifdef MRB_CONST_CACHE_SIZE +# undef MRB_NO_CONST_CACHE +mrb_static_assert_powerof2(MRB_CONST_CACHE_SIZE); +#else +/* default constant cache size: 64 */ +/* cache size needs to be power of 2 */ +# define MRB_CONST_CACHE_SIZE (1<<6) +#endif + +#ifndef MRB_NO_CONST_CACHE +struct mrb_const_cache_entry { + const struct mrb_irep *irep; + mrb_sym sym; + mrb_value value; +}; +#endif + struct mrb_jmpbuf; -typedef void (*mrb_atexit_func)(struct mrb_state*); +typedef void (*mrb_atexit_func)(mrb_state*); #ifdef MRB_USE_TASK_SCHEDULER struct mrb_task; @@ -257,10 +274,12 @@ typedef struct mrb_task_state { volatile mrb_bool switching; /* Context switch pending flag */ struct mrb_task *main_task; /* Main task wrapper for root context */ uint8_t scheduler_lock; /* Lock counter for synchronous execution */ + mrb_bool loop_running; /* Active mrb_task_run loop flag */ + mrb_bool exception_as_result; /* Return unhandled task exceptions as values */ } mrb_task_state; #endif -typedef struct mrb_state { +struct mrb_state { struct mrb_jmpbuf *jmp; struct mrb_context *c; @@ -297,22 +316,28 @@ typedef struct mrb_state { struct mrb_cache_entry cache[MRB_METHOD_CACHE_SIZE]; #endif +#ifndef MRB_NO_CONST_CACHE + struct mrb_const_cache_entry const_cache[MRB_CONST_CACHE_SIZE]; +#endif + mrb_sym symidx; const char **symtbl; + uint8_t *sym_flags; /* per-symbol flags (SYM_FL_*) */ size_t symcapa; struct mrb_sym_hash_table *symhash; void *sym_pool; + mrb_sym dynamic_sym_count; /* count of dynamic (GC-candidate) symbols */ #ifndef MRB_USE_ALL_SYMBOLS char symbuf[8]; /* buffer for small symbol names */ #endif #ifdef MRB_USE_DEBUG_HOOK - void (*code_fetch_hook)(struct mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs); - void (*debug_op_hook)(struct mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs); + void (*code_fetch_hook)(mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs); + void (*debug_op_hook)(mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs); #endif #ifdef MRB_BYTECODE_DECODE_OPTION - mrb_code (*bytecode_decoder)(struct mrb_state* mrb, mrb_code code); + mrb_code (*bytecode_decoder)(mrb_state* mrb, mrb_code code); #endif struct RClass *eException_class; @@ -339,7 +364,7 @@ typedef struct mrb_state { #ifdef MRB_USE_TASK_SCHEDULER mrb_task_state task; /* Task scheduler state */ #endif -} mrb_state; +}; /** * Defines a new class. @@ -1159,6 +1184,21 @@ MRB_API mrb_value mrb_funcall_id(mrb_state *mrb, mrb_value val, mrb_sym mid, mrb * @see mrb_funcall */ MRB_API mrb_value mrb_funcall_argv(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_int argc, const mrb_value *argv); +/* + * Convenience wrappers for `mrb_funcall_argv` with a fixed argument count. + * Avoids the 16-slot fixed argv buffer used by the variadic `mrb_funcall_id`. + */ +MRB_INLINE mrb_value +mrb_funcall_argv1(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_value a1) +{ + return mrb_funcall_argv(mrb, val, name, 1, &a1); +} +MRB_INLINE mrb_value +mrb_funcall_argv2(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_value a1, mrb_value a2) +{ + const mrb_value argv[] = { a1, a2 }; + return mrb_funcall_argv(mrb, val, name, 2, argv); +} /** * Call existing Ruby functions with a block. */ @@ -1284,6 +1324,11 @@ MRB_API void mrb_method_cache_clear(mrb_state *mrb); #else #define mrb_method_cache_clear(mrb) ((void)0) #endif +#ifndef MRB_NO_CONST_CACHE +MRB_API void mrb_const_cache_clear(mrb_state *mrb); +#else +#define mrb_const_cache_clear(mrb) ((void)0) +#endif /** * Check if mrb_open() failed diff --git a/include/mruby/array.h b/include/mruby/array.h index 0a956a8a99..6e63db557a 100644 --- a/include/mruby/array.h +++ b/include/mruby/array.h @@ -20,7 +20,17 @@ typedef struct mrb_shared_array { mrb_value *ptr; } mrb_shared_array; -#if defined(MRB_32BIT) && defined(MRB_NO_BOXING) && (!defined(MRB_USE_FLOAT32) || defined(MRB_INT64)) && !defined(MRB_ARY_NO_EMBED) +/* On 32-bit platforms whose ABI gives 8-byte members 8-byte alignment + (ARM, MIPS, xtensa, ...), an embedded mrb_value array forces 8-byte + alignment of the inner union, padding the heap-form layout and + inflating struct size past the 5-word RVALUE limit. Disable embedding + whenever mrb_value contains an 8-byte aligned member: nan-boxing + (uint64_t), or no-boxing with int64_t/double inside the union. */ +#if defined(MRB_32BIT) && \ + (defined(MRB_NAN_BOXING) || \ + (defined(MRB_NO_BOXING) && \ + (!defined(MRB_USE_FLOAT32) || defined(MRB_INT64)))) && \ + !defined(MRB_ARY_NO_EMBED) # define MRB_ARY_NO_EMBED #endif @@ -57,7 +67,7 @@ struct RArray { #define ARY_UNSET_EMBED_FLAG(a) (void)0 #define ARY_EMBED_LEN(a) 0 #define ARY_SET_EMBED_LEN(a,len) (void)0 -#define ARY_EMBED_PTR(a) 0 +#define ARY_EMBED_PTR(a) ((mrb_value*)NULL) #else #define MRB_ARY_EMBED_MASK 7 #define ARY_EMBED_P(a) ((a)->flags & MRB_ARY_EMBED_MASK) @@ -71,6 +81,7 @@ struct RArray { #define ARY_PTR(a) (ARY_EMBED_P(a)?ARY_EMBED_PTR(a):(a)->as.heap.ptr) #define RARRAY_LEN(a) ARY_LEN(RARRAY(a)) #define RARRAY_PTR(a) ARY_PTR(RARRAY(a)) +#define RARRAY_GETMEM(a, ptr, len) ARY_GETMEM(RARRAY(a), ptr, len) #define ARY_SET_LEN(a,n) do {\ if (ARY_EMBED_P(a)) {\ mrb_assert((n) <= MRB_ARY_EMBED_LEN_MAX); \ @@ -84,6 +95,17 @@ struct RArray { #define ARY_SHARED_P(a) ((a)->flags & MRB_ARY_SHARED) #define ARY_SET_SHARED_FLAG(a) ((a)->flags |= MRB_ARY_SHARED) #define ARY_UNSET_SHARED_FLAG(a) ((a)->flags &= ~MRB_ARY_SHARED) +#define ARY_GETMEM(a, ptr, len) do { \ + struct RArray *MRB_UNIQNAME(_a_) = (a); \ + if (ARY_EMBED_P(MRB_UNIQNAME(_a_))) { \ + (len) = ARY_EMBED_LEN(MRB_UNIQNAME(_a_)); \ + (ptr) = ARY_EMBED_PTR(MRB_UNIQNAME(_a_)); \ + } \ + else { \ + (len) = MRB_UNIQNAME(_a_)->as.heap.len; \ + (ptr) = MRB_UNIQNAME(_a_)->as.heap.ptr; \ + } \ +} while (0) MRB_API void mrb_ary_modify(mrb_state*, struct RArray*); MRB_API mrb_value mrb_ary_dup(mrb_state*, mrb_value ary); diff --git a/include/mruby/boxing_nan.h b/include/mruby/boxing_nan.h index d76759ab0f..6fcbcf9447 100644 --- a/include/mruby/boxing_nan.h +++ b/include/mruby/boxing_nan.h @@ -103,7 +103,8 @@ mrb_unboxed_type(mrb_value o) { if (!mrb_float_p(o) && mrb_nb_tt(o) == MRB_NANBOX_TT_OBJECT && o.u != 0) { return ((struct RBasic*)(uintptr_t)o.u)->tt; - } else { + } + else { return MRB_TT_FALSE; } } @@ -150,7 +151,7 @@ mrb_nan_boxing_value_int(mrb_value v) #define SET_TRUE_VALUE(r) NANBOX_SET_MISC_VALUE(r, MRB_TT_TRUE, 1) #define SET_BOOL_VALUE(r,b) NANBOX_SET_MISC_VALUE(r, (b) ? MRB_TT_TRUE : MRB_TT_FALSE, 1) #ifdef MRB_INT64 -MRB_API mrb_value mrb_boxing_int_value(struct mrb_state*, mrb_int); +MRB_API mrb_value mrb_boxing_int_value(mrb_state*, mrb_int); #define SET_INT_VALUE(mrb, r, n) ((r) = mrb_boxing_int_value(mrb, n)) #else #define SET_INT_VALUE(mrb, r, n) SET_FIXNUM_VALUE(r, n) diff --git a/include/mruby/boxing_word.h b/include/mruby/boxing_word.h index ffdd177c87..b9bf1a13d4 100644 --- a/include/mruby/boxing_word.h +++ b/include/mruby/boxing_word.h @@ -165,11 +165,11 @@ mrb_val_union(mrb_value v) return x; } -MRB_API mrb_value mrb_word_boxing_cptr_value(struct mrb_state*, void*); +MRB_API mrb_value mrb_word_boxing_cptr_value(mrb_state*, void*); #ifndef MRB_NO_FLOAT -MRB_API mrb_value mrb_word_boxing_float_value(struct mrb_state*, mrb_float); +MRB_API mrb_value mrb_word_boxing_float_value(mrb_state*, mrb_float); #endif -MRB_API mrb_value mrb_boxing_int_value(struct mrb_state*, mrb_int); +MRB_API mrb_value mrb_boxing_int_value(mrb_state*, mrb_int); #if WORDBOX_IMMEDIATE_MASK == 0x3 #define mrb_immediate_p(o) ((o).w & WORDBOX_IMMEDIATE_MASK || (o).w <= MRB_Qundef) @@ -266,9 +266,11 @@ mrb_unboxed_type(mrb_value o) { if (mrb_nil_p(o)) { return MRB_TT_FALSE; - } else if ((o.w & WORDBOX_IMMEDIATE_MASK) == 0) { + } + else if ((o.w & WORDBOX_IMMEDIATE_MASK) == 0) { return mrb_val_union(o).bp->tt; - } else { + } + else { return MRB_TT_FALSE; } } diff --git a/include/mruby/class.h b/include/mruby/class.h index fdac8ef9f1..33c0b90e7f 100644 --- a/include/mruby/class.h +++ b/include/mruby/class.h @@ -100,10 +100,13 @@ void mrb_mc_clear_by_class(mrb_state *mrb, struct RClass* c); typedef int (mrb_mt_foreach_func)(mrb_state*,mrb_sym,mrb_method_t,void*); MRB_API void mrb_mt_foreach(mrb_state*, struct RClass*, mrb_mt_foreach_func*, void*); -/* ROM method table types for static method registration */ +/* ROM method table types for static method registration. + * NOTE: `func` is kept as the first union member so that positional + * aggregate initialization in MRB_MT_ENTRY works without C99 + * designated initializers (required for legacy C++ compilers). */ union mrb_mt_ptr { - const struct RProc *proc; mrb_func_t func; + const struct RProc *proc; }; /* entry combining function pointer, symbol key, and flags */ @@ -128,7 +131,7 @@ typedef struct mrb_mt_tbl { /* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with MRB_MT_PRIVATE. */ #define MRB_MT_ENTRY(fn, sym, flags) \ - { { .func = (fn) }, (sym), (flags) | MRB_MT_FUNC } + { { (fn) }, (sym), (flags) | MRB_MT_FUNC } #define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff)) /* "removed" tombstone: MRB_MT_FUNC flag set with NULL function pointer. diff --git a/include/mruby/compile.h b/include/mruby/compile.h index a4bd374c36..9abc50f579 100644 --- a/include/mruby/compile.h +++ b/include/mruby/compile.h @@ -134,6 +134,7 @@ struct mrb_parser_state { mrb_sym* filename_table; uint16_t filename_table_length; uint16_t current_filename_index; + uint16_t prev_file_lineno; /* saved lineno before partial_hook file switch */ /* Variable-sized node management */ mrb_ast_node *nvars; diff --git a/include/mruby/error.h b/include/mruby/error.h index 63a45ee96a..dbe4bf7584 100644 --- a/include/mruby/error.h +++ b/include/mruby/error.h @@ -37,7 +37,14 @@ MRB_API mrb_value mrb_exc_new_str(mrb_state *mrb, struct RClass* c, mrb_value st #define mrb_exc_new_lit(mrb, c, lit) mrb_exc_new_str(mrb, c, mrb_str_new_lit(mrb, lit)) MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_value args, const char *fmt, ...); -#if defined(MRB_NAN_BOXING) || defined(MRB_WORD_BOXING) || defined(MRB_64BIT) +/* On 32-bit platforms whose ABI gives uint64_t/double 8-byte alignment + (ARM, MIPS, PowerPC, xtensa, ...), embedding mrb_value directly in + RBreak forces 8-byte alignment that pushes the struct past the 5-word + RVALUE budget via padding. Store the value bits as a uint32_t array + (4-byte aligned) to dodge that padding. Word-boxing's mrb_value is + just a uintptr_t with no over-alignment, and 64-bit platforms have no + alignment gap to begin with, so neither needs the workaround. */ +#if defined(MRB_64BIT) || defined(MRB_WORD_BOXING) #undef MRB_USE_RBREAK_VALUE_UNION #else #define MRB_USE_RBREAK_VALUE_UNION 1 @@ -45,7 +52,8 @@ MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_va /* * flags: - * 0..7: enum mrb_vtype (only when defined MRB_USE_RBREAK_VALUE_UNION) + * 0..7: enum mrb_vtype (only when MRB_USE_RBREAK_VALUE_UNION and + * !MRB_NAN_BOXING; nan-boxing encodes the type in the bits) * 8..10: RBREAK_TAGs in src/vm.c (otherwise, set to 0) */ struct RBreak { @@ -53,11 +61,11 @@ struct RBreak { uintptr_t ci_break_index; // The top-level ci index to break. One before the return destination. #ifndef MRB_USE_RBREAK_VALUE_UNION mrb_value val; +#elif defined(MRB_NAN_BOXING) + /* nan-boxing: mrb_value is a single 64-bit word (type encoded in NaN bits) */ + uint32_t value[sizeof(mrb_value) / sizeof(uint32_t)]; #else - /* Store value as uint32_t words instead of union mrb_value_union - to avoid 8-byte alignment of int64_t/double on 32-bit platforms - (e.g., ARM, MIPS, PowerPC) which would inflate struct size beyond - the 5-word RVALUE limit due to padding. */ + /* no-boxing: store only the union bits; tt goes in flags */ uint32_t value[sizeof(union mrb_value_union) / sizeof(uint32_t)]; #endif }; @@ -65,6 +73,19 @@ struct RBreak { #ifndef MRB_USE_RBREAK_VALUE_UNION #define mrb_break_value_get(brk) ((brk)->val) #define mrb_break_value_set(brk, v) ((brk)->val = v) +#elif defined(MRB_NAN_BOXING) +static inline mrb_value +mrb_break_value_get(struct RBreak *brk) +{ + mrb_value val; + memcpy(&val, brk->value, sizeof(val)); + return val; +} +static inline void +mrb_break_value_set(struct RBreak *brk, mrb_value val) +{ + memcpy(brk->value, &val, sizeof(val)); +} #else #define RBREAK_VALUE_TT_MASK ((1 << 8) - 1) static inline mrb_value diff --git a/include/mruby/gc.h b/include/mruby/gc.h index b9602ef52e..43eb46d50e 100644 --- a/include/mruby/gc.h +++ b/include/mruby/gc.h @@ -14,15 +14,12 @@ */ MRB_BEGIN_DECL - -struct mrb_state; - #define MRB_EACH_OBJ_OK 0 #define MRB_EACH_OBJ_BREAK 1 -typedef int (mrb_each_object_callback)(struct mrb_state *mrb, struct RBasic *obj, void *data); -void mrb_objspace_each_objects(struct mrb_state *mrb, mrb_each_object_callback *callback, void *data); +typedef int (mrb_each_object_callback)(mrb_state *mrb, struct RBasic *obj, void *data); +void mrb_objspace_each_objects(mrb_state *mrb, mrb_each_object_callback *callback, void *data); size_t mrb_objspace_page_slot_size(void); -MRB_API void mrb_free_context(struct mrb_state *mrb, struct mrb_context *c); +MRB_API void mrb_free_context(mrb_state *mrb, struct mrb_context *c); #ifndef MRB_GC_ARENA_SIZE #define MRB_GC_ARENA_SIZE 100 @@ -48,7 +45,7 @@ typedef struct mrb_gc { mrb_bool gray_overflow:1; /* gray stack overflowed; needs heap rescan */ size_t live; /* count of live objects */ size_t live_after_mark; /* old generation objects */ - size_t threshold; /* threshold to start GC */ + mrb_int gc_debt; /* <0:credit, >0:needs GC */ size_t oldgen_threshold; /* threshold to kick major GC */ mrb_gc_state state; /* current state of gc */ int interval_ratio; @@ -59,6 +56,9 @@ typedef struct mrb_gc { mrb_bool generational :1; /* generational GC mode */ mrb_bool full :1; /* major GC mode */ mrb_bool out_of_memory :1; /* out-of-memory error occurred */ + size_t step_limit; /* 0=unlimited, >0=absolute step cap */ + size_t malloc_increase; /* malloc bytes since last GC cycle */ + size_t malloc_threshold; /* 0=disabled, >0=bytes to trigger GC */ #ifdef MRB_GC_FIXED_ARENA struct RBasic *arena[MRB_GC_ARENA_SIZE]; /* GC protection array */ @@ -67,10 +67,16 @@ typedef struct mrb_gc { int arena_capa; /* size of protection array */ #endif int arena_idx; + +#ifdef MRB_GC_STATS + uint32_t gc_total_count; /* total GC invocations */ + uint32_t minor_gc_count; /* minor GC count */ + uint32_t major_gc_count; /* major GC count */ +#endif } mrb_gc; -MRB_API mrb_bool mrb_object_dead_p(struct mrb_state *mrb, struct RBasic *object); -MRB_API int mrb_gc_add_region(struct mrb_state *mrb, void *start, size_t size); +MRB_API mrb_bool mrb_object_dead_p(mrb_state *mrb, struct RBasic *object); +MRB_API int mrb_gc_add_region(mrb_state *mrb, void *start, size_t size); #define MRB_GC_RED 7 diff --git a/include/mruby/internal.h b/include/mruby/internal.h index 4b06dd5222..0b5a88af82 100644 --- a/include/mruby/internal.h +++ b/include/mruby/internal.h @@ -279,4 +279,7 @@ mrb_value mrb_bint_abs(mrb_state *mrb, mrb_value x); void mrb_task_mark_all(mrb_state *mrb); #endif +/* Internal object allocation without type validation (gc.c) */ +struct RBasic* mrb_obj_alloc_core(mrb_state*, enum mrb_vtype, struct RClass*); + #endif /* MRUBY_INTERNAL_H */ diff --git a/include/mruby/irep.h b/include/mruby/irep.h index 1719866c80..ab0b125c37 100644 --- a/include/mruby/irep.h +++ b/include/mruby/irep.h @@ -20,7 +20,7 @@ enum irep_pool_type { IREP_TT_SSTR = 2, /* string (static) */ IREP_TT_INT32 = 1, /* 32-bit integer */ IREP_TT_INT64 = 3, /* 64-bit integer */ - IREP_TT_BIGINT = 7, /* big integer (not yet supported) */ + IREP_TT_BIGINT = 7, /* big integer */ IREP_TT_FLOAT = 5, /* float (double/float) */ }; diff --git a/include/mruby/proc.h b/include/mruby/proc.h index 3a8a4054dd..01bc0f75f1 100644 --- a/include/mruby/proc.h +++ b/include/mruby/proc.h @@ -102,6 +102,60 @@ struct RProc { #define MRB_PROC_ALIAS 8192 #define MRB_PROC_ALIAS_P(p) (((p)->flags & MRB_PROC_ALIAS) != 0) +/* Compressed aspec for cfunc procs (13 bits in RProc.flags). + * Uses free bits 0-6 and 14-19 to store a compressed argument spec. + * Layout: block(0) kdict(1) key(2-3) post(4-5) rest(6) opt(14-16) req(17-19) + * Field widths are smaller than the full 24-bit aspec: req/opt max 7, post/key max 3. + * Values exceeding the compressed range are clamped and rest is forced to 1. */ +#define MRB_PROC_CASPEC_MASK 0xfc07fu /* bits 0-6 and 14-19 */ + +static inline uint32_t +mrb_proc_compress_aspec(mrb_aspec aspec) +{ + uint32_t req = MRB_ASPEC_REQ(aspec); + uint32_t opt = MRB_ASPEC_OPT(aspec); + uint32_t rest = MRB_ASPEC_REST(aspec); + uint32_t post = MRB_ASPEC_POST(aspec); + uint32_t key = MRB_ASPEC_KEY(aspec); + uint32_t kdict = MRB_ASPEC_KDICT(aspec); + uint32_t block = MRB_ASPEC_BLOCK(aspec); + + if (req > 7 || opt > 7 || post > 3 || key > 3) { + if (req > 7) req = 7; + if (opt > 7) opt = 7; + if (post > 3) post = 3; + if (key > 3) key = 3; + rest = 1; + } + + return block | (kdict << 1) | (key << 2) | (post << 4) | (rest << 6) + | (opt << 14) | (req << 17); +} + +static inline mrb_aspec +mrb_proc_decompress_caspec(uint32_t flags) +{ + return (((flags >> 17) & 0x7) << 18) /* req */ + | (((flags >> 14) & 0x7) << 13) /* opt */ + | (((flags >> 6) & 0x1) << 12) /* rest */ + | (((flags >> 4) & 0x3) << 7) /* post */ + | (((flags >> 2) & 0x3) << 2) /* key */ + | (((flags >> 1) & 0x1) << 1) /* kdict */ + | (flags & 0x1); /* block */ +} + +static inline void +mrb_proc_set_cfunc_aspec(struct RProc *p, mrb_aspec aspec) +{ + p->flags &= ~(MRB_PROC_NOARG | MRB_PROC_CASPEC_MASK); + if (aspec == 0) { + p->flags |= MRB_PROC_NOARG; + } + else { + p->flags |= mrb_proc_compress_aspec(aspec); + } +} + #define mrb_proc_ptr(v) ((struct RProc*)(mrb_ptr(v))) struct RProc *mrb_proc_new(mrb_state*, const mrb_irep*); diff --git a/include/mruby/string.h b/include/mruby/string.h index b88259452c..22683b89a2 100644 --- a/include/mruby/string.h +++ b/include/mruby/string.h @@ -342,6 +342,16 @@ MRB_API const char *mrb_string_value_cstr(mrb_state *mrb, mrb_value *str); */ MRB_API mrb_value mrb_str_dup(mrb_state *mrb, mrb_value str); +/** + * Returns a frozen string object. + * The string will be duplicated and frozen if it is not already frozen. + * + * @param mrb The current mruby state. + * @param str An original Ruby string. + * @return [mrb_value] Ruby frozen string. + */ +MRB_API mrb_value mrb_str_dup_frozen(mrb_state *mrb, mrb_value str); + /** * Returns a symbol from a passed in Ruby string. * diff --git a/include/mruby/value.h b/include/mruby/value.h index 0bbcfde569..d6cd0a08a2 100644 --- a/include/mruby/value.h +++ b/include/mruby/value.h @@ -55,8 +55,6 @@ typedef uint8_t mrb_bool; # endif #endif -struct mrb_state; - #if defined _MSC_VER && _MSC_VER < 1800 # define PRIo64 "llo" # define PRId64 "lld" @@ -330,7 +328,7 @@ struct RCptr { */ #ifndef MRB_NO_FLOAT MRB_INLINE mrb_value -mrb_float_value(struct mrb_state *mrb, mrb_float f) +mrb_float_value(mrb_state *mrb, mrb_float f) { mrb_value v; (void) mrb; @@ -340,7 +338,7 @@ mrb_float_value(struct mrb_state *mrb, mrb_float f) #endif MRB_INLINE mrb_value -mrb_cptr_value(struct mrb_state *mrb, void *p) +mrb_cptr_value(mrb_state *mrb, void *p) { mrb_value v; (void) mrb; @@ -352,7 +350,7 @@ mrb_cptr_value(struct mrb_state *mrb, void *p) * Returns an integer in Ruby. */ MRB_INLINE mrb_value -mrb_int_value(struct mrb_state *mrb, mrb_int i) +mrb_int_value(mrb_state *mrb, mrb_int i) { mrb_value v; SET_INT_VALUE(mrb, v, i); diff --git a/include/mruby/version.h b/include/mruby/version.h index 85fdaa1491..cdb5efe26a 100644 --- a/include/mruby/version.h +++ b/include/mruby/version.h @@ -80,7 +80,7 @@ MRB_BEGIN_DECL /* * Release year. */ -#define MRUBY_RELEASE_YEAR 2025 +#define MRUBY_RELEASE_YEAR 2026 /* * Release month. diff --git a/lib/mruby/amalgam.rb b/lib/mruby/amalgam.rb index 125f846572..0aec03c75a 100644 --- a/lib/mruby/amalgam.rb +++ b/lib/mruby/amalgam.rb @@ -41,7 +41,7 @@ class Amalgam allocf.c readnum.c readint.c - readfloat.c + fp_uscale.c state.c symbol.c class.c @@ -66,7 +66,6 @@ class Amalgam cdump.c codedump.c print.c - fmt_fp.c debug.c etc.c version.c diff --git a/lib/mruby/build.rb b/lib/mruby/build.rb index d5efea54be..995611a4a7 100644 --- a/lib/mruby/build.rb +++ b/lib/mruby/build.rb @@ -81,7 +81,7 @@ def install_dir include LoadGems attr_accessor :name, :bins, :exts, :file_separator, :build_dir, :gem_clone_dir, :defines, :libdir_name attr_reader :products, :libmruby_core_objs, :libmruby_objs, :gems, :toolchains, :presym, :mrbc_build, :gem_dir_to_repo_url - attr_reader :install_excludes + attr_reader :install_excludes, :port_names alias libmruby libmruby_objs @@ -138,6 +138,7 @@ def initialize(name='host', build_dir=nil, internal: false, &block) @mrbcfile_external = false @internal = internal @toolchains = [] + @port_names = nil @gem_dir_to_repo_url = {} # Add lambda instead of string because libdir_name or lib may be changed by user configuration @@ -183,6 +184,30 @@ def enable_debug @enable_debug = true end + # Set target port names for this build. + # Each gem compiles the first matching ports// directory; + # later names in the list act as fallbacks for gems that don't + # ship a port for the earlier names. + # conf.ports :esp32 + # conf.ports :rp2040, :posix # use rp2040 if available, else posix + def ports(*names) + @port_names = names.map { |n| n.to_s } + end + + # Returns the effective port names for this build. + # If not explicitly set, auto-detects :posix or :win for host builds. + def effective_ports + return @port_names if @port_names + if kind_of?(MRuby::CrossBuild) + [] + elsif ENV['OS'] == 'Windows_NT' || + ('A'..'Z').any? { |v| Dir.exist?("#{v}:") } + ['win'] + else + ['posix'] + end + end + def disable_lock @enable_lock = false end diff --git a/lib/mruby/build/command.rb b/lib/mruby/build/command.rb index 458c166b72..d9b2f3636c 100644 --- a/lib/mruby/build/command.rb +++ b/lib/mruby/build/command.rb @@ -343,16 +343,21 @@ def run(out, infiles, funcname, cdump: true, static: false) opt = @compile_options % {funcname: funcname} opt << " -S" if cdump opt << " -s" if static + # Have mrbc write to a private tempfile (-o) instead of stdout (-o-) + # to avoid pipe-inheritance races with parallel rake on Windows MinGW, + # where unrelated _pp build-progress lines from sibling workers can + # leak into the captured stdout and corrupt the generated C file. + tmpout = "#{out.path}.#{funcname}.mrbcout" + opt = opt.sub(/\s-o-(?=\s|\z)/, %Q[ -o "#{filename tmpout}"]) cmd = %["#{filename @command}" #{opt} #{filename(infiles).map{|f| %["#{f}"]}.join(' ')}] puts cmd if Rake.verbose - IO.popen(cmd, 'r') do |io| - out.puts io.read - end - # if mrbc execution fail, drop the file - unless $?.success? + unless system(cmd) + rm_f tmpout rm_f out.path fail "Command failed with status (#{$?.exitstatus}): [#{cmd[0,42]}...]" end + out.write File.binread(tmpout) + rm_f tmpout end end diff --git a/lib/mruby/gem.rb b/lib/mruby/gem.rb index abb9b71ac2..f1a403a463 100644 --- a/lib/mruby/gem.rb +++ b/lib/mruby/gem.rb @@ -25,6 +25,7 @@ class Specification alias :author= :authors= attr_accessor :rbfiles, :objs + attr_reader :port_objs attr_writer :test_objs, :test_rbfiles attr_accessor :test_args, :test_preload @@ -43,6 +44,7 @@ class Specification def initialize(name, &block) @name = name @initializer = block + @post_user_config = nil @version = "0.0.0" @dependencies = [] @conflicts = [] @@ -59,6 +61,22 @@ def setup @rbfiles = Dir.glob("#{@dir}/mrblib/**/*.rb").sort @objs = srcs_to_objs("src") + # Add platform-specific sources from the first matching + # ports// directory. effective_ports is a fallback + # chain: later names act as defaults for gems that don't ship + # a port for the earlier names. These objs are tracked + # separately so List#resolve_external_hal! can drop them when + # an external HAL provider (gem named hal--*) is loaded. + @port_objs = [] + build.effective_ports.each do |port| + port_dir = "#{@dir}/ports/#{port}" + if File.directory?(port_dir) + @port_objs = srcs_to_objs("ports/#{port}") + @objs += @port_objs + break + end + end + @test_preload = nil # 'test/assert.rb' @test_args = {} @skip_test = false @@ -85,6 +103,7 @@ def setup build.libmruby_objs << @objs instance_eval(&@build_config_initializer) if @build_config_initializer + instance_eval(&@post_user_config) if @post_user_config repo_url = build.gem_dir_to_repo_url[dir] build.locks[repo_url]['version'] = version if repo_url @@ -194,6 +213,19 @@ def srcs_to_objs(src_dir_from_gem_dir) end end + # Register a block that runs after the user's `build.gem` block has + # been processed. Intended for gem authors to fill in defaults that + # depend on user-supplied configuration (e.g. auto-detect a library + # only if the user didn't specify which to use). + # + # Initialization order: + # 1. block in `MRuby::Gem::Specification.new` (gem author) + # 2. block in `build.gem` (user's build_config) + # 3. block in `post_user_config` (gem author, this hook) + def post_user_config(&block) + @post_user_config = block + end + def build_settings(&blk) @build_settings = blk end @@ -430,6 +462,28 @@ def setup(build) self.each(&:setup) gemset = self.setup_dependencies(build).keys.sort end until gemset == gemset_prev + resolve_external_hal! + end + + # A gem named `hal--` is treated as the external + # HAL provider for the gem whose name's last `-`-separated + # segment is (e.g., hal-task-glib provides the HAL for + # mruby-task). The target gem's ports/* sources are dropped + # from its object list -- the matching gem supplies the + # implementation. Two or more matches is a build error. + def resolve_external_hal! + each do |target| + next if target.port_objs.nil? || target.port_objs.empty? + short = target.name.split('-').last + pattern = /\Ahal-#{Regexp.escape(short)}-.+\z/ + overriders = select { |g| g != target && g.name =~ pattern } + next if overriders.empty? + if overriders.size > 1 + fail "Multiple HAL providers for '#{target.name}': " + + overriders.map(&:name).join(", ") + end + target.objs.reject! { |o| target.port_objs.include?(o) } + end end def setup_build diff --git a/mrbgems/hal-posix-dir/mrbgem.rake b/mrbgems/hal-posix-dir/mrbgem.rake deleted file mode 100644 index 877b110cb5..0000000000 --- a/mrbgems/hal-posix-dir/mrbgem.rake +++ /dev/null @@ -1,7 +0,0 @@ -MRuby::Gem::Specification.new('hal-posix-dir') do |spec| - spec.license = 'MIT' - spec.authors = 'mruby developers' - spec.summary = 'POSIX HAL for mruby-dir (Linux, macOS, BSD, Unix)' - - spec.add_dependency 'mruby-dir', core: 'mruby-dir' -end diff --git a/mrbgems/hal-posix-io/mrbgem.rake b/mrbgems/hal-posix-io/mrbgem.rake deleted file mode 100644 index bdd4913d16..0000000000 --- a/mrbgems/hal-posix-io/mrbgem.rake +++ /dev/null @@ -1,8 +0,0 @@ -MRuby::Gem::Specification.new('hal-posix-io') do |spec| - spec.license = 'MIT' - spec.author = 'mruby developers' - spec.summary = 'POSIX HAL for mruby-io (Linux, macOS, BSD, Unix)' - - # HAL gem depends on feature gem - brings in mruby-io automatically - spec.add_dependency 'mruby-io', core: 'mruby-io' -end diff --git a/mrbgems/hal-posix-socket/mrbgem.rake b/mrbgems/hal-posix-socket/mrbgem.rake deleted file mode 100644 index 14dca040a6..0000000000 --- a/mrbgems/hal-posix-socket/mrbgem.rake +++ /dev/null @@ -1,8 +0,0 @@ -MRuby::Gem::Specification.new('hal-posix-socket') do |spec| - spec.license = 'MIT' - spec.author = 'mruby developers' - spec.summary = 'POSIX HAL for mruby-socket (Linux, macOS, BSD, Unix)' - - # HAL gem depends on feature gem - brings in mruby-socket automatically - spec.add_dependency 'mruby-socket', core: 'mruby-socket' -end diff --git a/mrbgems/hal-posix-task/README.md b/mrbgems/hal-posix-task/README.md deleted file mode 100644 index 157ebdba25..0000000000 --- a/mrbgems/hal-posix-task/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# hal-posix-task - -POSIX Hardware Abstraction Layer (HAL) implementation for mruby-task. - -## Description - -Provides timer and interrupt support for the mruby-task cooperative scheduler on POSIX-compliant platforms. Uses `SIGALRM` and `setitimer()` for periodic timer ticks, and `sigprocmask()` for interrupt protection. - -## Supported Platforms - -- Linux -- macOS -- BSD (FreeBSD, OpenBSD, NetBSD) -- Other POSIX-compliant Unix systems - -## Requirements - -- POSIX-compliant operating system -- Signal support (`SIGALRM`, `sigaction`, `sigprocmask`) -- Timer support (`setitimer`, `ITIMER_REAL`) - -## Usage - -### Explicit HAL Selection (Recommended) - -```ruby -MRuby::Build.new do |conf| - # ... other configuration ... - - # Specify POSIX HAL - automatically brings in mruby-task - conf.gem core: 'hal-posix-task' -end -``` - -### Auto-detection (Development) - -```ruby -MRuby::Build.new do |conf| - # ... other configuration ... - - # Auto-detects and selects hal-posix-task on POSIX platforms - conf.gem core: 'mruby-task' -end -``` - -## Implementation Details - -### Timer Mechanism - -- Uses `setitimer(ITIMER_REAL, ...)` to generate periodic `SIGALRM` signals -- Timer interval configured by `MRB_TICK_UNIT` (default: 4ms) -- Signal handler calls `mrb_tick()` for all registered VM instances - -### Interrupt Protection - -- Critical sections protected using `sigprocmask()` to block `SIGALRM` -- Prevents race conditions during task queue modifications -- Supports nested critical sections through signal masking - -### Multi-VM Support - -- Supports up to `MRB_TASK_MAX_VMS` concurrent mruby VM instances (default: 8) -- Single shared timer ticks all registered VMs -- Per-VM task counters optimize timer usage (timer disabled when idle) - -### Timer Optimization - -The implementation dynamically enables/disables the timer based on task state: - -- **Timer enabled** when: Multiple ready tasks OR any waiting tasks exist -- **Timer disabled** when: Single task or all tasks dormant/suspended -- Reduces CPU usage and power consumption when scheduler is idle - -## Configuration - -Override these macros in your build config if needed: - -```ruby -conf.gem core: 'hal-posix-task' do |spec| - # Custom tick interval (10ms instead of default 4ms) - spec.build.defines << 'MRB_TICK_UNIT=10' - - # Custom timeslice (5 ticks instead of default 3) - spec.build.defines << 'MRB_TIMESLICE_TICK_COUNT=5' - - # More concurrent VMs (16 instead of default 8) - spec.build.defines << 'MRB_TASK_MAX_VMS=16' -end -``` - -## Known Limitations - -- `SIGALRM` conflicts with other code using the same signal -- Timer resolution limited by platform (typically 1-10ms) -- Signal delivery may be delayed under heavy system load -- Not suitable for hard real-time requirements - -## See Also - -- `mruby-task` - Core task scheduler -- `hal-win-task` - Windows HAL implementation -- Task scheduler documentation: `mrbgems/mruby-task/README.md` diff --git a/mrbgems/hal-posix-task/mrbgem.rake b/mrbgems/hal-posix-task/mrbgem.rake deleted file mode 100644 index 570161c775..0000000000 --- a/mrbgems/hal-posix-task/mrbgem.rake +++ /dev/null @@ -1,8 +0,0 @@ -MRuby::Gem::Specification.new('hal-posix-task') do |spec| - spec.license = 'MIT' - spec.authors = 'mruby developers' - spec.summary = 'POSIX HAL for mruby-task (Linux, macOS, BSD, Unix)' - - # HAL gem depends on feature gem - brings in mruby-task automatically - spec.add_dependency 'mruby-task', core: 'mruby-task' -end diff --git a/mrbgems/hal-win-dir/mrbgem.rake b/mrbgems/hal-win-dir/mrbgem.rake deleted file mode 100644 index 426829dbb4..0000000000 --- a/mrbgems/hal-win-dir/mrbgem.rake +++ /dev/null @@ -1,7 +0,0 @@ -MRuby::Gem::Specification.new('hal-win-dir') do |spec| - spec.license = 'MIT and MIT-like license' - spec.authors = ['mruby developers', 'Kevlin Henney'] - spec.summary = 'Windows HAL for mruby-dir' - - spec.add_dependency 'mruby-dir', core: 'mruby-dir' -end diff --git a/mrbgems/hal-win-io/mrbgem.rake b/mrbgems/hal-win-io/mrbgem.rake deleted file mode 100644 index 5bad6576ba..0000000000 --- a/mrbgems/hal-win-io/mrbgem.rake +++ /dev/null @@ -1,11 +0,0 @@ -MRuby::Gem::Specification.new('hal-win-io') do |spec| - spec.license = 'MIT' - spec.author = 'mruby developers' - spec.summary = 'Windows HAL for mruby-io (Windows, MinGW)' - - # HAL gem depends on feature gem - brings in mruby-io automatically - spec.add_dependency 'mruby-io', core: 'mruby-io' - - # Link Windows socket library - spec.linker.libraries << "ws2_32" -end diff --git a/mrbgems/hal-win-socket/mrbgem.rake b/mrbgems/hal-win-socket/mrbgem.rake deleted file mode 100644 index d959c87e68..0000000000 --- a/mrbgems/hal-win-socket/mrbgem.rake +++ /dev/null @@ -1,12 +0,0 @@ -MRuby::Gem::Specification.new('hal-win-socket') do |spec| - spec.license = 'MIT' - spec.author = 'mruby developers' - spec.summary = 'Windows HAL for mruby-socket (Windows, MinGW)' - - # HAL gem depends on feature gem - brings in mruby-socket automatically - spec.add_dependency 'mruby-socket', core: 'mruby-socket' - - # Link Windows socket libraries - spec.linker.libraries << "wsock32" - spec.linker.libraries << "ws2_32" -end diff --git a/mrbgems/hal-win-socket/src/socket_hal.c b/mrbgems/hal-win-socket/src/socket_hal.c deleted file mode 100644 index f052a09980..0000000000 --- a/mrbgems/hal-win-socket/src/socket_hal.c +++ /dev/null @@ -1,176 +0,0 @@ -/* -** socket_hal.c - Windows HAL implementation for mruby-socket -** -** See Copyright Notice in mruby.h -** -** Windows implementation for socket operations using Winsock APIs. -** Supported platforms: Windows, MinGW -*/ - -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0501 // need Windows XP or later -#endif - -#include -#include -#include -#include -#include "socket_hal.h" -#include -#include -#include -#include -#include - -/* - * Socket HAL Initialization/Finalization - */ - -void -mrb_hal_socket_init(mrb_state *mrb) -{ - WSADATA wsaData; - int result = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (result != NO_ERROR) { - mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(RuntimeError)), "WSAStartup failed"); - } -} - -void -mrb_hal_socket_final(mrb_state *mrb) -{ - (void)mrb; - WSACleanup(); -} - -/* - * Socket Control Operations - */ - -int -mrb_hal_socket_set_nonblock(mrb_state *mrb, int fd, int nonblock) -{ - (void)mrb; - u_long mode = nonblock ? 1 : 0; - int result = ioctlsocket(fd, FIONBIO, &mode); - if (result != NO_ERROR) { - return -1; - } - return 0; -} - -/* - * Address Conversion Functions - */ - -const char* -mrb_hal_socket_inet_ntop(int af, const void *src, char *dst, size_t size) -{ - if (af == AF_INET) { - struct sockaddr_in in = {0}; - in.sin_family = AF_INET; - memcpy(&in.sin_addr, src, sizeof(struct in_addr)); - if (getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in), - dst, (DWORD)size, NULL, 0, NI_NUMERICHOST) == 0) { - return dst; - } - return NULL; - } - else if (af == AF_INET6) { - struct sockaddr_in6 in = {0}; - in.sin6_family = AF_INET6; - memcpy(&in.sin6_addr, src, sizeof(struct in6_addr)); - if (getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6), - dst, (DWORD)size, NULL, 0, NI_NUMERICHOST) == 0) { - return dst; - } - return NULL; - } - return NULL; -} - -int -mrb_hal_socket_inet_pton(int af, const char *src, void *dst) -{ - struct addrinfo hints = {0}; - hints.ai_family = af; - hints.ai_flags = AI_NUMERICHOST; - - struct addrinfo *res; - if (getaddrinfo(src, NULL, &hints, &res) != 0) { - return 0; /* Invalid address */ - } - - if (res == NULL) { - return 0; - } - - if (af == AF_INET && res->ai_family == AF_INET) { - memcpy(dst, &((struct sockaddr_in*)res->ai_addr)->sin_addr, sizeof(struct in_addr)); - freeaddrinfo(res); - return 1; - } - else if (af == AF_INET6 && res->ai_family == AF_INET6) { - memcpy(dst, &((struct sockaddr_in6*)res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); - freeaddrinfo(res); - return 1; - } - - freeaddrinfo(res); - return 0; -} - -/* - * Platform-Specific Socket Features - */ - -mrb_value -mrb_hal_socket_sockaddr_un(mrb_state *mrb, const char *path, size_t pathlen) -{ - (void)path; - (void)pathlen; - mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(NotImplementedError)), - "sockaddr_un unsupported on Windows"); - return mrb_nil_value(); -} - -int -mrb_hal_socket_socketpair(mrb_state *mrb, int domain, int type, int protocol, int sv[2]) -{ - (void)mrb; - (void)domain; - (void)type; - (void)protocol; - (void)sv; - /* socketpair is not supported on Windows */ - errno = ENOSYS; - return -1; -} - -mrb_value -mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen) -{ - (void)sockaddr; - (void)socklen; - mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(NotImplementedError)), - "unix_path unsupported on Windows"); - return mrb_nil_value(); -} - -/* - * Gem initialization - */ - -void -mrb_hal_win_socket_gem_init(mrb_state *mrb) -{ - (void)mrb; - /* HAL interface functions are called by mruby-socket gem */ -} - -void -mrb_hal_win_socket_gem_final(mrb_state *mrb) -{ - (void)mrb; - /* Cleanup handled by mrb_hal_socket_final called from mruby-socket */ -} diff --git a/mrbgems/hal-win-task/README.md b/mrbgems/hal-win-task/README.md deleted file mode 100644 index b959afdec3..0000000000 --- a/mrbgems/hal-win-task/README.md +++ /dev/null @@ -1,109 +0,0 @@ -# hal-win-task - -Windows Hardware Abstraction Layer (HAL) implementation for mruby-task. - -## Description - -Provides timer and interrupt support for the mruby-task cooperative scheduler on Windows platforms. Uses multimedia timer (`timeSetEvent`/`timeKillEvent`) for periodic timer ticks, and `CRITICAL_SECTION` for interrupt protection. - -## Supported Platforms - -- Windows 7 and later -- Windows Server 2008 R2 and later -- All versions with multimedia timer support - -## Requirements - -- Windows operating system -- Multimedia timer API (`winmm.lib`) -- Visual C++, MinGW, or compatible compiler - -## Usage - -### Explicit HAL Selection (Recommended) - -```ruby -MRuby::Build.new do |conf| - # ... other configuration ... - - # Specify Windows HAL - automatically brings in mruby-task - conf.gem core: 'hal-win-task' -end -``` - -### Auto-detection (Development) - -```ruby -MRuby::Build.new do |conf| - # ... other configuration ... - - # Auto-detects and selects hal-win-task on Windows platforms - conf.gem core: 'mruby-task' -end -``` - -## Implementation Details - -### Timer Mechanism - -- Uses Windows multimedia timer (`timeSetEvent`) for periodic callbacks -- Timer interval configured by `MRB_TICK_UNIT` (default: 4ms) -- `TIME_KILL_SYNCHRONOUS` flag ensures clean timer shutdown -- Requests 1ms timer resolution via `timeBeginPeriod(1)` - -### Interrupt Protection - -- Critical sections protected using `CRITICAL_SECTION` objects -- Prevents race conditions during task queue modifications -- `EnterCriticalSection`/`LeaveCriticalSection` used for mutual exclusion -- Supports nested critical sections (automatic lock counting) - -### Multi-VM Support - -- Supports up to `MRB_TASK_MAX_VMS` concurrent mruby VM instances (default: 8) -- Single shared timer ticks all registered VMs -- Per-VM task counters optimize timer usage (timer disabled when idle) -- Interlocked operations (`InterlockedIncrement`/`InterlockedDecrement`) for thread safety - -### Timer Optimization - -The implementation dynamically enables/disables the timer based on task state: - -- **Timer enabled** when: Multiple ready tasks OR any waiting tasks exist -- **Timer disabled** when: Single task or all tasks dormant/suspended -- Reduces CPU usage and power consumption when scheduler is idle - -## Configuration - -Override these macros in your build config if needed: - -```ruby -conf.gem core: 'hal-win-task' do |spec| - # Custom tick interval (10ms instead of default 4ms) - spec.build.defines << 'MRB_TICK_UNIT=10' - - # Custom timeslice (5 ticks instead of default 3) - spec.build.defines << 'MRB_TIMESLICE_TICK_COUNT=5' - - # More concurrent VMs (16 instead of default 8) - spec.build.defines << 'MRB_TASK_MAX_VMS=16' -end -``` - -## Known Limitations - -- Timer resolution typically limited to 1-2ms even with `timeBeginPeriod(1)` -- Multimedia timers consume system resources (kernel timer objects) -- Timer callbacks execute in separate thread context (handled internally) -- Not suitable for hard real-time requirements -- May interfere with other multimedia applications requesting different timer resolutions - -## See Also - -- `mruby-task` - Core task scheduler -- `hal-posix-task` - POSIX/Unix HAL implementation -- Task scheduler documentation: `mrbgems/mruby-task/README.md` - -## Build Notes - -The `winmm` library is automatically linked by the gem specification. No additional linker configuration is needed. diff --git a/mrbgems/hal-win-task/mrbgem.rake b/mrbgems/hal-win-task/mrbgem.rake deleted file mode 100644 index ea40075e86..0000000000 --- a/mrbgems/hal-win-task/mrbgem.rake +++ /dev/null @@ -1,11 +0,0 @@ -MRuby::Gem::Specification.new('hal-win-task') do |spec| - spec.license = 'MIT' - spec.authors = 'mruby developers' - spec.summary = 'Windows HAL for mruby-task' - - # HAL gem depends on feature gem - brings in mruby-task automatically - spec.add_dependency 'mruby-task', core: 'mruby-task' - - # Windows multimedia timer library - spec.linker.libraries << 'winmm' -end diff --git a/mrbgems/hw-adc/README.md b/mrbgems/hw-adc/README.md new file mode 100644 index 0000000000..c0d2338409 --- /dev/null +++ b/mrbgems/hw-adc/README.md @@ -0,0 +1,74 @@ +# hw-adc - ADC peripheral interface for mruby + +This gem provides the `ADC` class for reading analog input from +mruby. It is designed for embedded platforms such as ESP32 and +RP2040. + +## Architecture + +Platform-specific HAL implementations are in `ports/` directories: + +- `ports/esp32/` - ESP32 using ESP-IDF ADC oneshot driver +- `ports/rp2040/` - RP2040 using Pico SDK ADC hardware + +## Build Configuration + +```ruby +MRuby::CrossBuild.new('esp32') do |conf| + conf.ports :esp32 + conf.gem core: 'hw-adc' +end +``` + +## Ruby API + +### ADC.new + +```ruby +adc = ADC.new(pin) +``` + +- `pin` - ADC-capable GPIO pin number (Integer) +- Raises `ArgumentError` if the pin is not valid for ADC + +### ADC#read / ADC#read_voltage + +Read the analog value as voltage (Float). + +```ruby +voltage = adc.read # => 1.65 +voltage = adc.read_voltage # same +``` + +### ADC#read_raw + +Read the raw ADC value (Integer, typically 0-4095 for 12-bit). + +```ruby +raw = adc.read_raw # => 2048 +``` + +### ADC#input + +Returns the ADC input channel number assigned during initialization. + +## HAL Interface + +To add support for a new platform, create a `ports//` +directory and implement the following C functions declared in +``: + +```c +int mrb_adc_init(uint8_t pin); +uint32_t mrb_adc_read_raw(uint8_t input); +float mrb_adc_read_voltage(uint8_t input); +``` + +- `mrb_adc_init` returns the input channel number (>= 0) on + success, negative on error +- `input` parameter for read functions is the value returned by + `mrb_adc_init` + +## License + +MIT diff --git a/mrbgems/hw-adc/include/mruby/adc.h b/mrbgems/hw-adc/include/mruby/adc.h new file mode 100644 index 0000000000..cc8d974411 --- /dev/null +++ b/mrbgems/hw-adc/include/mruby/adc.h @@ -0,0 +1,19 @@ +#ifndef MRUBY_ADC_H +#define MRUBY_ADC_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* HAL functions - implemented in ports//adc.c */ +int mrb_adc_init(uint8_t pin); +uint32_t mrb_adc_read_raw(uint8_t input); +float mrb_adc_read_voltage(uint8_t input); + +#ifdef __cplusplus +} +#endif + +#endif /* MRUBY_ADC_H */ diff --git a/mrbgems/hw-adc/mrbgem.rake b/mrbgems/hw-adc/mrbgem.rake new file mode 100644 index 0000000000..b96d7b564a --- /dev/null +++ b/mrbgems/hw-adc/mrbgem.rake @@ -0,0 +1,5 @@ +MRuby::Gem::Specification.new('hw-adc') do |spec| + spec.license = 'MIT' + spec.authors = ['HASUMI Hitoshi', 'mruby developers'] + spec.summary = 'ADC peripheral interface' +end diff --git a/mrbgems/hw-adc/mrblib/adc.rb b/mrbgems/hw-adc/mrblib/adc.rb new file mode 100644 index 0000000000..0c983c0905 --- /dev/null +++ b/mrbgems/hw-adc/mrblib/adc.rb @@ -0,0 +1,7 @@ +class ADC + def initialize(pin) + @input = __init(pin) + end + + attr_reader :input +end diff --git a/mrbgems/hw-adc/ports/esp32/adc.c b/mrbgems/hw-adc/ports/esp32/adc.c new file mode 100644 index 0000000000..3b5b7f08e2 --- /dev/null +++ b/mrbgems/hw-adc/ports/esp32/adc.c @@ -0,0 +1,147 @@ +#include +#include "esp_adc/adc_oneshot.h" +#include + +#define VOLTAGE_MAX 3.3f +#define RESOLUTION 4095 +#define UNIT_NUM 2 + +static adc_oneshot_unit_handle_t adc_handles[UNIT_NUM]; +static bool adc_initialized; + +static adc_unit_t +pin_to_unit(uint8_t pin) +{ + switch (pin) { +#if CONFIG_IDF_TARGET_ESP32C3 + case 0: case 1: case 2: case 3: case 4: + return ADC_UNIT_1; + case 5: + return ADC_UNIT_2; +#elif CONFIG_IDF_TARGET_ESP32 + case 32: case 33: case 34: case 35: case 36: case 39: + return ADC_UNIT_1; + case 0: case 2: case 4: case 12: case 13: case 14: case 15: + case 25: case 26: case 27: + return ADC_UNIT_2; +#elif (CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2) + case 1: case 2: case 3: case 4: case 5: + case 6: case 7: case 8: case 9: case 10: + return ADC_UNIT_1; + case 11: case 12: case 13: case 14: case 15: + case 16: case 17: case 18: case 19: case 20: + return ADC_UNIT_2; +#endif + } + return -1; +} + +static adc_channel_t +pin_to_channel(uint8_t pin) +{ + switch (pin) { +#if CONFIG_IDF_TARGET_ESP32C3 + case 0: return ADC_CHANNEL_0; + case 1: return ADC_CHANNEL_1; + case 2: return ADC_CHANNEL_2; + case 3: return ADC_CHANNEL_3; + case 4: return ADC_CHANNEL_4; + case 5: return ADC_CHANNEL_0; +#elif CONFIG_IDF_TARGET_ESP32 + case 36: return ADC_CHANNEL_0; + case 39: return ADC_CHANNEL_3; + case 32: return ADC_CHANNEL_4; + case 33: return ADC_CHANNEL_5; + case 34: return ADC_CHANNEL_6; + case 35: return ADC_CHANNEL_7; + case 4: return ADC_CHANNEL_0; + case 0: return ADC_CHANNEL_1; + case 2: return ADC_CHANNEL_2; + case 15: return ADC_CHANNEL_3; + case 13: return ADC_CHANNEL_4; + case 12: return ADC_CHANNEL_5; + case 14: return ADC_CHANNEL_6; + case 27: return ADC_CHANNEL_7; + case 25: return ADC_CHANNEL_8; + case 26: return ADC_CHANNEL_9; +#elif (CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2) + case 1: return ADC_CHANNEL_0; + case 2: return ADC_CHANNEL_1; + case 3: return ADC_CHANNEL_2; + case 4: return ADC_CHANNEL_3; + case 5: return ADC_CHANNEL_4; + case 6: return ADC_CHANNEL_5; + case 7: return ADC_CHANNEL_6; + case 8: return ADC_CHANNEL_7; + case 9: return ADC_CHANNEL_8; + case 10: return ADC_CHANNEL_9; + case 11: return ADC_CHANNEL_0; + case 12: return ADC_CHANNEL_1; + case 13: return ADC_CHANNEL_2; + case 14: return ADC_CHANNEL_3; + case 15: return ADC_CHANNEL_4; + case 16: return ADC_CHANNEL_5; + case 17: return ADC_CHANNEL_6; + case 18: return ADC_CHANNEL_7; + case 19: return ADC_CHANNEL_8; + case 20: return ADC_CHANNEL_9; +#endif + } + return -1; +} + +static int +init_units(void) +{ + adc_unit_t units[] = { ADC_UNIT_1, ADC_UNIT_2 }; + for (int i = 0; i < UNIT_NUM; i++) { + adc_oneshot_unit_init_cfg_t cfg = { + .unit_id = units[i], + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + if (adc_oneshot_new_unit(&cfg, &adc_handles[i]) != ESP_OK) + return -1; + } + return 0; +} + +int +mrb_adc_init(uint8_t pin) +{ + if (!adc_initialized) { + if (init_units() != 0) return -1; + adc_initialized = true; + } + + int ch = pin_to_channel(pin); + if (ch < 0) return -1; + + adc_unit_t unit = pin_to_unit(pin); + if (unit < 0 || unit >= UNIT_NUM) return -1; + + adc_oneshot_chan_cfg_t cfg = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + if (adc_oneshot_config_channel(adc_handles[unit], ch, &cfg) != ESP_OK) + return -1; + + return (int)pin; +} + +uint32_t +mrb_adc_read_raw(uint8_t input) +{ + adc_unit_t unit = pin_to_unit(input); + int ch = pin_to_channel(input); + int raw = 0; + if (unit >= 0 && unit < UNIT_NUM) + adc_oneshot_read(adc_handles[unit], ch, &raw); + return (uint32_t)raw; +} + +float +mrb_adc_read_voltage(uint8_t input) +{ + return (float)mrb_adc_read_raw(input) * VOLTAGE_MAX / RESOLUTION; +} diff --git a/mrbgems/hw-adc/ports/rp2040/adc.c b/mrbgems/hw-adc/ports/rp2040/adc.c new file mode 100644 index 0000000000..8d317c58c0 --- /dev/null +++ b/mrbgems/hw-adc/ports/rp2040/adc.c @@ -0,0 +1,43 @@ +#include +#include "hardware/adc.h" +#include + +#define VOLTAGE_MAX 3.3f +#define RESOLUTION 4095 +#define TEMP_INPUT 4 + +static bool adc_initialized; + +int +mrb_adc_init(uint8_t pin) +{ + if (!adc_initialized) { + adc_init(); + adc_initialized = true; + } + + uint input; + switch (pin) { + case 26: input = 0; break; + case 27: input = 1; break; + case 28: input = 2; break; + case 29: input = 3; break; + default: return -1; + } + adc_gpio_init(pin); + return (int)input; +} + +uint32_t +mrb_adc_read_raw(uint8_t input) +{ + adc_select_input(input); + return (uint32_t)adc_read(); +} + +float +mrb_adc_read_voltage(uint8_t input) +{ + adc_select_input(input); + return (float)adc_read() * VOLTAGE_MAX / RESOLUTION; +} diff --git a/mrbgems/hw-adc/src/adc.c b/mrbgems/hw-adc/src/adc.c new file mode 100644 index 0000000000..7c12e904d9 --- /dev/null +++ b/mrbgems/hw-adc/src/adc.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +/* ADC#__init(pin) */ +static mrb_value +mrb_adc_m_init(mrb_state *mrb, mrb_value self) +{ + mrb_int pin; + mrb_get_args(mrb, "i", &pin); + int input = mrb_adc_init((uint8_t)pin); + if (input < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid ADC pin"); + } + return mrb_fixnum_value(input); +} + +/* ADC#read_raw */ +static mrb_value +mrb_adc_m_read_raw(mrb_state *mrb, mrb_value self) +{ + mrb_int input = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(input))); + return mrb_fixnum_value(mrb_adc_read_raw((uint8_t)input)); +} + +/* ADC#read_voltage (also aliased as read) */ +static mrb_value +mrb_adc_m_read_voltage(mrb_state *mrb, mrb_value self) +{ + mrb_int input = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(input))); + return mrb_float_value(mrb, (mrb_float)mrb_adc_read_voltage((uint8_t)input)); +} + +void +mrb_hw_adc_gem_init(mrb_state *mrb) +{ + struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(ADC), mrb->object_class); + mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_adc_m_init, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(read_raw), mrb_adc_m_read_raw, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(read_voltage), mrb_adc_m_read_voltage, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_adc_m_read_voltage, MRB_ARGS_NONE()); +} + +void +mrb_hw_adc_gem_final(mrb_state *mrb) +{ +} diff --git a/mrbgems/hw-gpio/README.md b/mrbgems/hw-gpio/README.md new file mode 100644 index 0000000000..f464a792f5 --- /dev/null +++ b/mrbgems/hw-gpio/README.md @@ -0,0 +1,144 @@ +# hw-gpio - GPIO peripheral interface for mruby + +This gem provides the `GPIO` class for controlling general-purpose +input/output pins from mruby. +It is designed for embedded platforms such as ESP32 and RP2040. + +## Architecture + +Platform-specific HAL implementations are in `ports/` directories: + +- `ports/esp32/` - ESP32 using ESP-IDF GPIO driver +- `ports/rp2040/` - RP2040 using Pico SDK + +The build system automatically compiles matching port sources based +on `conf.ports` setting. + +## Build Configuration + +```ruby +# For ESP32 +MRuby::CrossBuild.new('esp32') do |conf| + conf.ports :esp32 + conf.gem core: 'hw-gpio' +end + +# For RP2040 +MRuby::CrossBuild.new('rp2040') do |conf| + conf.ports :rp2040 + conf.gem core: 'hw-gpio' +end +``` + +## Ruby API + +### Constants (direction/mode flags) + +These flags can be combined with bitwise OR. + +| Flag | Value | Description | +| ------------------ | ------ | -------------------------- | +| `GPIO::IN` | `0x01` | Input mode | +| `GPIO::OUT` | `0x02` | Output mode | +| `GPIO::HIGH_Z` | `0x04` | High-impedance (tri-state) | +| `GPIO::PULL_UP` | `0x08` | Enable pull-up resistor | +| `GPIO::PULL_DOWN` | `0x10` | Enable pull-down resistor | +| `GPIO::OPEN_DRAIN` | `0x20` | Enable open-drain output | + +### GPIO.new + +```ruby +gpio = GPIO.new(pin, flags) +``` + +- `pin` - GPIO pin number (Integer) +- `flags` - direction and mode flags (combined with `|`) +- Exactly one of `IN`, `OUT`, or `HIGH_Z` must be specified +- `PULL_UP` and `PULL_DOWN` are mutually exclusive +- Raises `ArgumentError` on invalid flag combinations + +```ruby +# Input with pull-up +button = GPIO.new(2, GPIO::IN | GPIO::PULL_UP) + +# Output +led = GPIO.new(25, GPIO::OUT) +``` + +### Instance Methods + +#### GPIO#read + +Read the current pin value. + +```ruby +val = gpio.read # => 0 or 1 +``` + +#### GPIO#write + +Set the pin output value. + +```ruby +gpio.write(1) # set high +gpio.write(0) # set low +``` + +- Raises `ArgumentError` if value is not 0 or 1 + +#### GPIO#high? / GPIO#low? + +```ruby +gpio.high? # => true if read != 0 +gpio.low? # => true if read == 0 +``` + +#### GPIO#pin + +```ruby +gpio.pin # => pin number passed to new +``` + +#### GPIO#setmode + +Reconfigure pin direction and mode flags after initialization. + +```ruby +gpio.setmode(GPIO::OUT) +``` + +### Class Methods + +These operate directly on pin numbers without creating an instance. + +```ruby +GPIO.read_at(pin) # => 0 or 1 +GPIO.write_at(pin, val) # set pin output (0 or 1) +GPIO.set_dir_at(pin, flags) # set direction (IN/OUT/HIGH_Z) +GPIO.pull_up_at(pin) # enable pull-up +GPIO.pull_down_at(pin) # enable pull-down +GPIO.open_drain_at(pin) # enable open-drain +``` + +## HAL Interface + +To add support for a new platform, create a `ports//` +directory and implement the following C functions declared in +``: + +```c +void mrb_gpio_init(uint8_t pin); +void mrb_gpio_set_dir(uint8_t pin, uint8_t flags); +void mrb_gpio_pull_up(uint8_t pin); +void mrb_gpio_pull_down(uint8_t pin); +void mrb_gpio_open_drain(uint8_t pin); +int mrb_gpio_read(uint8_t pin); +void mrb_gpio_write(uint8_t pin, uint8_t val); +``` + +The port sources are compiled automatically when the build +configuration includes a matching `conf.ports` tag. + +## License + +MIT diff --git a/mrbgems/hw-gpio/include/mruby/gpio.h b/mrbgems/hw-gpio/include/mruby/gpio.h new file mode 100644 index 0000000000..ed1e7c10b3 --- /dev/null +++ b/mrbgems/hw-gpio/include/mruby/gpio.h @@ -0,0 +1,31 @@ +#ifndef MRUBY_GPIO_H +#define MRUBY_GPIO_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* direction/mode flags (bitmask) */ +#define MRB_GPIO_IN 0x01 +#define MRB_GPIO_OUT 0x02 +#define MRB_GPIO_HIGH_Z 0x04 +#define MRB_GPIO_PULL_UP 0x08 +#define MRB_GPIO_PULL_DOWN 0x10 +#define MRB_GPIO_OPEN_DRAIN 0x20 + +/* HAL functions - implemented by hw--gpio gems */ +void mrb_gpio_init(uint8_t pin); +void mrb_gpio_set_dir(uint8_t pin, uint8_t flags); +void mrb_gpio_pull_up(uint8_t pin); +void mrb_gpio_pull_down(uint8_t pin); +void mrb_gpio_open_drain(uint8_t pin); +int mrb_gpio_read(uint8_t pin); +void mrb_gpio_write(uint8_t pin, uint8_t val); + +#ifdef __cplusplus +} +#endif + +#endif /* MRUBY_GPIO_H */ diff --git a/mrbgems/hw-gpio/mrbgem.rake b/mrbgems/hw-gpio/mrbgem.rake new file mode 100644 index 0000000000..f117381d7f --- /dev/null +++ b/mrbgems/hw-gpio/mrbgem.rake @@ -0,0 +1,5 @@ +MRuby::Gem::Specification.new('hw-gpio') do |spec| + spec.license = 'MIT' + spec.authors = ['HASUMI Hitoshi', 'mruby developers'] + spec.summary = 'GPIO peripheral interface' +end diff --git a/mrbgems/hw-gpio/mrblib/gpio.rb b/mrbgems/hw-gpio/mrblib/gpio.rb new file mode 100644 index 0000000000..a9fd46418a --- /dev/null +++ b/mrbgems/hw-gpio/mrblib/gpio.rb @@ -0,0 +1,44 @@ +class GPIO + IN = 0x01 + OUT = 0x02 + HIGH_Z = 0x04 + PULL_UP = 0x08 + PULL_DOWN = 0x10 + OPEN_DRAIN = 0x20 + + attr_reader :pin + + def initialize(pin, flags) + @pin = pin + __init(pin) + setmode(flags) + end + + def setmode(flags) + dir = flags & (IN | OUT | HIGH_Z) + n = (flags & IN != 0 ? 1 : 0) + (flags & OUT != 0 ? 1 : 0) + (flags & HIGH_Z != 0 ? 1 : 0) + if n == 0 + raise ArgumentError, "specify one of IN, OUT, or HIGH_Z" + elsif n > 1 + raise ArgumentError, "IN, OUT, and HIGH_Z are exclusive" + end + GPIO.set_dir_at(@pin, dir) + + pull = flags & (PULL_UP | PULL_DOWN) + if pull == (PULL_UP | PULL_DOWN) + raise ArgumentError, "PULL_UP and PULL_DOWN are exclusive" + end + GPIO.pull_up_at(@pin) if pull == PULL_UP + GPIO.pull_down_at(@pin) if pull == PULL_DOWN + GPIO.open_drain_at(@pin) if flags & OPEN_DRAIN != 0 + nil + end + + def high? + read != 0 + end + + def low? + read == 0 + end +end diff --git a/mrbgems/hw-gpio/ports/esp32/gpio.c b/mrbgems/hw-gpio/ports/esp32/gpio.c new file mode 100644 index 0000000000..e3980c2772 --- /dev/null +++ b/mrbgems/hw-gpio/ports/esp32/gpio.c @@ -0,0 +1,50 @@ +#include "driver/gpio.h" +#include + +void +mrb_gpio_init(uint8_t pin) +{ + gpio_reset_pin(pin); +} + +void +mrb_gpio_set_dir(uint8_t pin, uint8_t flags) +{ + if (flags & MRB_GPIO_IN) { + gpio_set_direction(pin, GPIO_MODE_INPUT); + } + else if (flags & MRB_GPIO_OUT) { + gpio_set_direction(pin, GPIO_MODE_OUTPUT); + } + /* HIGH_Z: not yet implemented */ +} + +void +mrb_gpio_pull_up(uint8_t pin) +{ + gpio_pullup_en(pin); +} + +void +mrb_gpio_pull_down(uint8_t pin) +{ + gpio_pulldown_en(pin); +} + +void +mrb_gpio_open_drain(uint8_t pin) +{ + /* not yet implemented */ +} + +int +mrb_gpio_read(uint8_t pin) +{ + return gpio_get_level(pin); +} + +void +mrb_gpio_write(uint8_t pin, uint8_t val) +{ + gpio_set_level(pin, val); +} diff --git a/mrbgems/hw-gpio/ports/rp2040/gpio.c b/mrbgems/hw-gpio/ports/rp2040/gpio.c new file mode 100644 index 0000000000..7b69fa41e4 --- /dev/null +++ b/mrbgems/hw-gpio/ports/rp2040/gpio.c @@ -0,0 +1,51 @@ +#include +#include "hardware/gpio.h" +#include + +void +mrb_gpio_init(uint8_t pin) +{ + gpio_init(pin); +} + +void +mrb_gpio_set_dir(uint8_t pin, uint8_t flags) +{ + if (flags & MRB_GPIO_IN) { + gpio_set_dir(pin, false); + } + else if (flags & MRB_GPIO_OUT) { + gpio_set_dir(pin, true); + } + /* HIGH_Z: not yet implemented */ +} + +void +mrb_gpio_pull_up(uint8_t pin) +{ + gpio_pull_up(pin); +} + +void +mrb_gpio_pull_down(uint8_t pin) +{ + gpio_pull_down(pin); +} + +void +mrb_gpio_open_drain(uint8_t pin) +{ + /* not yet implemented */ +} + +int +mrb_gpio_read(uint8_t pin) +{ + return gpio_get(pin); +} + +void +mrb_gpio_write(uint8_t pin, uint8_t val) +{ + gpio_put(pin, val == 1); +} diff --git a/mrbgems/hw-gpio/src/gpio.c b/mrbgems/hw-gpio/src/gpio.c new file mode 100644 index 0000000000..92661926d5 --- /dev/null +++ b/mrbgems/hw-gpio/src/gpio.c @@ -0,0 +1,119 @@ +#include +#include +#include +#include + +static mrb_value +mrb_gpio_m_init(mrb_state *mrb, mrb_value self) +{ + mrb_int pin; + mrb_get_args(mrb, "i", &pin); + mrb_gpio_init((uint8_t)pin); + return mrb_nil_value(); +} + +/* GPIO.set_dir_at(pin, flags) */ +static mrb_value +mrb_gpio_s_set_dir_at(mrb_state *mrb, mrb_value klass) +{ + mrb_int pin, flags; + mrb_get_args(mrb, "ii", &pin, &flags); + mrb_gpio_set_dir((uint8_t)pin, (uint8_t)flags); + return mrb_nil_value(); +} + +/* GPIO.pull_up_at(pin) */ +static mrb_value +mrb_gpio_s_pull_up_at(mrb_state *mrb, mrb_value klass) +{ + mrb_int pin; + mrb_get_args(mrb, "i", &pin); + mrb_gpio_pull_up((uint8_t)pin); + return mrb_nil_value(); +} + +/* GPIO.pull_down_at(pin) */ +static mrb_value +mrb_gpio_s_pull_down_at(mrb_state *mrb, mrb_value klass) +{ + mrb_int pin; + mrb_get_args(mrb, "i", &pin); + mrb_gpio_pull_down((uint8_t)pin); + return mrb_nil_value(); +} + +/* GPIO.open_drain_at(pin) */ +static mrb_value +mrb_gpio_s_open_drain_at(mrb_state *mrb, mrb_value klass) +{ + mrb_int pin; + mrb_get_args(mrb, "i", &pin); + mrb_gpio_open_drain((uint8_t)pin); + return mrb_nil_value(); +} + +/* GPIO.read_at(pin) */ +static mrb_value +mrb_gpio_s_read_at(mrb_state *mrb, mrb_value klass) +{ + mrb_int pin; + mrb_get_args(mrb, "i", &pin); + return mrb_fixnum_value(mrb_gpio_read((uint8_t)pin)); +} + +/* GPIO.write_at(pin, val) */ +static mrb_value +mrb_gpio_s_write_at(mrb_state *mrb, mrb_value klass) +{ + mrb_int pin, val; + mrb_get_args(mrb, "ii", &pin, &val); + if (val != 0 && val != 1) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be 0 or 1"); + } + mrb_gpio_write((uint8_t)pin, (uint8_t)val); + return mrb_nil_value(); +} + +/* GPIO#read */ +static mrb_value +mrb_gpio_m_read(mrb_state *mrb, mrb_value self) +{ + mrb_int pin = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(pin))); + return mrb_fixnum_value(mrb_gpio_read((uint8_t)pin)); +} + +/* GPIO#write(val) */ +static mrb_value +mrb_gpio_m_write(mrb_state *mrb, mrb_value self) +{ + mrb_int val; + mrb_get_args(mrb, "i", &val); + if (val != 0 && val != 1) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be 0 or 1"); + } + mrb_int pin = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(pin))); + mrb_gpio_write((uint8_t)pin, (uint8_t)val); + return mrb_nil_value(); +} + +void +mrb_hw_gpio_gem_init(mrb_state *mrb) +{ + struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(GPIO), mrb->object_class); + + mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_gpio_m_init, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_gpio_m_read, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_gpio_m_write, MRB_ARGS_REQ(1)); + + mrb_define_class_method_id(mrb, cls, MRB_SYM(set_dir_at), mrb_gpio_s_set_dir_at, MRB_ARGS_REQ(2)); + mrb_define_class_method_id(mrb, cls, MRB_SYM(pull_up_at), mrb_gpio_s_pull_up_at, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, cls, MRB_SYM(pull_down_at), mrb_gpio_s_pull_down_at, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, cls, MRB_SYM(open_drain_at), mrb_gpio_s_open_drain_at, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, cls, MRB_SYM(read_at), mrb_gpio_s_read_at, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, cls, MRB_SYM(write_at), mrb_gpio_s_write_at, MRB_ARGS_REQ(2)); +} + +void +mrb_hw_gpio_gem_final(mrb_state *mrb) +{ +} diff --git a/mrbgems/hw-i2c/README.md b/mrbgems/hw-i2c/README.md new file mode 100644 index 0000000000..768c07306b --- /dev/null +++ b/mrbgems/hw-i2c/README.md @@ -0,0 +1,162 @@ +# hw-i2c - I2C peripheral interface for mruby + +This gem provides the `I2C` class for communicating with I2C devices from mruby. It is designed for embedded platforms such as ESP32 and RP2040. + +## Architecture + +Platform-specific HAL implementations are in `ports/` directories: + +- `ports/esp32/` - ESP32 using ESP-IDF I2C master driver +- `ports/rp2040/` - RP2040 using Pico SDK + +The build system automatically compiles matching port sources based +on `conf.ports` setting. + +## Build Configuration + +```ruby +# For ESP32 +MRuby::CrossBuild.new('esp32') do |conf| + conf.ports :esp32 + conf.gem core: 'hw-i2c' +end + +# For RP2040 +MRuby::CrossBuild.new('rp2040') do |conf| + conf.ports :rp2040 + conf.gem core: 'hw-i2c' +end +``` + +## Ruby API + +### I2C.new + +```ruby +i2c = I2C.new( + unit: :ESP32_I2C0, # I2C unit name (platform-specific, required) + frequency: 100_000, # bus frequency in Hz (default: 100kHz) + sda_pin: 21, # SDA GPIO pin number (default: -1 for platform default) + scl_pin: 22, # SCL GPIO pin number (default: -1 for platform default) + timeout: 500 # default timeout in ms (default: 500) +) +``` + +#### Unit Names + +| Platform | Available Units | +| -------- | ------------------------------ | +| ESP32 | `:ESP32_I2C0`, `:ESP32_I2C1` | +| RP2040 | `:RP2040_I2C0`, `:RP2040_I2C1` | + +On RP2040, if `sda_pin` or `scl_pin` is -1, the Pico SDK default pins are used. + +### I2C#write + +Write data to an I2C device. + +```ruby +i2c.write(addr, *data, timeout: 500) +``` + +- `addr` - 7-bit I2C device address (Integer) +- `data` - one or more data arguments, each can be: + - **Integer** - a single byte (0-255) + - **Array of Integer** - multiple bytes + - **String** - raw bytes +- `timeout:` - optional timeout in ms (overrides instance default) +- Returns the number of bytes written (Integer) +- Raises `IOError` on failure + +```ruby +# Write a single byte +i2c.write(0x3C, 0x00) + +# Write multiple bytes +i2c.write(0x3C, 0x00, [0xAE, 0xD5, 0x80]) + +# Write a string +i2c.write(0x3C, "hello") + +# Mix data types +i2c.write(0x3C, 0x40, [0x01, 0x02], "data") +``` + +### I2C#read + +Read data from an I2C device. Optionally write data before reading (repeated START). + +```ruby +i2c.read(addr, length, *write_data, timeout: 500) +``` + +- `addr` - 7-bit I2C device address (Integer) +- `length` - number of bytes to read (Integer, must be positive) +- `write_data` - optional data to write before reading (same format as `write`). When provided, the gem performs a write-then-read transaction using I2C repeated START condition. This is the standard way to read from a specific register. +- `timeout:` - optional timeout in ms (overrides instance default) +- Returns the data read (String) +- Raises `IOError` on failure, `ArgumentError` if length <= 0 + +```ruby +# Simple read (2 bytes from device) +data = i2c.read(0x50, 2) + +# Register read: write register address 0x00, then read 2 bytes +data = i2c.read(0x50, 2, 0x00) + +# Multi-byte register address +data = i2c.read(0x50, 4, [0x00, 0x10]) +``` + +### I2C#scan + +Scan the I2C bus for responsive devices. + +```ruby +i2c.scan(timeout: 500) +``` + +- `timeout:` - optional timeout per probe in ms +- Returns an Array of 7-bit addresses (Integer) that responded + +```ruby +found = i2c.scan +# => [0x3C, 0x50, 0x68] +``` + +## HAL Interface + +To add support for a new platform, create a `ports//` +directory and implement the following C functions declared in +``: + +```c +int mrb_i2c_unit_name_to_num(const char *name); +mrb_i2c_status mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl); +int mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len, + uint32_t timeout_us); +int mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len, + uint32_t timeout_us); +int mrb_i2c_write_read(int unit, uint8_t addr, + const uint8_t *src, size_t wlen, + uint8_t *dst, size_t rlen, + uint32_t timeout_us); +``` + +The port sources are compiled automatically when the build +configuration includes a matching `conf.ports` tag. + +### Error Codes + +```c +typedef enum { + MRB_I2C_OK = 0, + MRB_I2C_ERROR_UNIT = -1, /* invalid or uninitialized unit */ + MRB_I2C_ERROR_TIMEOUT = -2, /* communication timeout */ + MRB_I2C_ERROR_NACK = -3, /* device did not acknowledge */ +} mrb_i2c_status; +``` + +## License + +MIT diff --git a/mrbgems/hw-i2c/include/mruby/i2c.h b/mrbgems/hw-i2c/include/mruby/i2c.h new file mode 100644 index 0000000000..d49a55b076 --- /dev/null +++ b/mrbgems/hw-i2c/include/mruby/i2c.h @@ -0,0 +1,31 @@ +#ifndef MRUBY_I2C_H +#define MRUBY_I2C_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MRB_I2C_OK = 0, + MRB_I2C_ERROR_UNIT = -1, + MRB_I2C_ERROR_TIMEOUT = -2, + MRB_I2C_ERROR_NACK = -3, +} mrb_i2c_status; + +/* HAL functions - implemented by hw--i2c gems */ +mrb_i2c_status mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl); +int mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len, uint32_t timeout_us); +int mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len, uint32_t timeout_us); +int mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen, + uint8_t *dst, size_t rlen, uint32_t timeout_us); +int mrb_i2c_unit_name_to_num(const char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* MRUBY_I2C_H */ diff --git a/mrbgems/hw-i2c/mrbgem.rake b/mrbgems/hw-i2c/mrbgem.rake new file mode 100644 index 0000000000..584e2b2a24 --- /dev/null +++ b/mrbgems/hw-i2c/mrbgem.rake @@ -0,0 +1,5 @@ +MRuby::Gem::Specification.new('hw-i2c') do |spec| + spec.license = 'MIT' + spec.authors = ['HASUMI Hitoshi', 'mruby developers'] + spec.summary = 'I2C peripheral interface' +end diff --git a/mrbgems/hw-i2c/mrblib/i2c.rb b/mrbgems/hw-i2c/mrblib/i2c.rb new file mode 100644 index 0000000000..49e506c216 --- /dev/null +++ b/mrbgems/hw-i2c/mrblib/i2c.rb @@ -0,0 +1,21 @@ +class I2C + DEFAULT_FREQUENCY = 100_000 # Hz + DEFAULT_TIMEOUT = 500 # ms + + def initialize(unit:, frequency: DEFAULT_FREQUENCY, sda_pin: -1, scl_pin: -1, timeout: DEFAULT_TIMEOUT) + @timeout = timeout + @unit_num = __init(unit.to_s, frequency, sda_pin, scl_pin) + end + + def scan(timeout: @timeout) + found = [] + (0x08..0x77).each do |addr| + begin + read(addr, 1, timeout: timeout) + found << addr + rescue IOError + end + end + found + end +end diff --git a/mrbgems/hw-i2c/ports/esp32/i2c.c b/mrbgems/hw-i2c/ports/esp32/i2c.c new file mode 100644 index 0000000000..cfb7aedd6f --- /dev/null +++ b/mrbgems/hw-i2c/ports/esp32/i2c.c @@ -0,0 +1,117 @@ +#include +#include "driver/i2c_master.h" +#include + +typedef struct { + i2c_master_bus_handle_t bus; + uint32_t freq; + bool initialized; +} i2c_ctx; + +/* ESP32 supports up to 2 I2C ports */ +static i2c_ctx ctx[2]; + +static bool +valid_unit(int unit) +{ + return unit >= 0 && unit <= 1 && ctx[unit].initialized; +} + +static uint32_t +us_to_ms(uint32_t timeout_us) +{ + uint32_t ms = (timeout_us + 999) / 1000; + return (ms < 10) ? 10 : ms; +} + +static i2c_master_dev_handle_t +add_device(int unit, uint8_t addr) +{ + i2c_device_config_t cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = addr, + .scl_speed_hz = ctx[unit].freq, + }; + i2c_master_dev_handle_t dev; + if (i2c_master_bus_add_device(ctx[unit].bus, &cfg, &dev) != ESP_OK) + return NULL; + return dev; +} + +int +mrb_i2c_unit_name_to_num(const char *name) +{ + if (strcmp(name, "ESP32_I2C0") == 0) return 0; + if (strcmp(name, "ESP32_I2C1") == 0) return 1; + return MRB_I2C_ERROR_UNIT; +} + +mrb_i2c_status +mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl) +{ + if (unit < 0 || unit > 1) return MRB_I2C_ERROR_UNIT; + + if (ctx[unit].initialized) { + i2c_del_master_bus(ctx[unit].bus); + ctx[unit].initialized = false; + } + + i2c_master_bus_config_t cfg = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = unit, + .scl_io_num = scl, + .sda_io_num = sda, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + + esp_err_t err = i2c_new_master_bus(&cfg, &ctx[unit].bus); + if (err != ESP_OK) return MRB_I2C_ERROR_UNIT; + + ctx[unit].initialized = true; + ctx[unit].freq = freq; + return MRB_I2C_OK; +} + +int +mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len, + uint32_t timeout_us) +{ + if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT; + + i2c_master_dev_handle_t dev = add_device(unit, addr); + if (!dev) return -1; + + esp_err_t err = i2c_master_receive(dev, dst, len, us_to_ms(timeout_us)); + i2c_master_bus_rm_device(dev); + return (err == ESP_OK) ? (int)len : -1; +} + +int +mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len, + uint32_t timeout_us) +{ + if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT; + + i2c_master_dev_handle_t dev = add_device(unit, addr); + if (!dev) return -1; + + esp_err_t err = i2c_master_transmit(dev, src, len, us_to_ms(timeout_us)); + i2c_master_bus_rm_device(dev); + return (err == ESP_OK) ? (int)len : -1; +} + +int +mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen, + uint8_t *dst, size_t rlen, uint32_t timeout_us) +{ + if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT; + + i2c_master_dev_handle_t dev = add_device(unit, addr); + if (!dev) return -1; + + esp_err_t err = i2c_master_transmit_receive(dev, src, wlen, dst, rlen, + us_to_ms(timeout_us)); + i2c_master_bus_rm_device(dev); + return (err == ESP_OK) ? (int)rlen : -1; +} diff --git a/mrbgems/hw-i2c/ports/rp2040/i2c.c b/mrbgems/hw-i2c/ports/rp2040/i2c.c new file mode 100644 index 0000000000..a0fac95cca --- /dev/null +++ b/mrbgems/hw-i2c/ports/rp2040/i2c.c @@ -0,0 +1,64 @@ +#include +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include + +#define UNIT_SELECT(u) \ + i2c_inst_t *inst; \ + switch (u) { \ + case 0: inst = i2c0; break; \ + case 1: inst = i2c1; break; \ + default: return MRB_I2C_ERROR_UNIT; \ + } + +int +mrb_i2c_unit_name_to_num(const char *name) +{ + if (strcmp(name, "RP2040_I2C0") == 0) return 0; + if (strcmp(name, "RP2040_I2C1") == 0) return 1; + return MRB_I2C_ERROR_UNIT; +} + +mrb_i2c_status +mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl) +{ + UNIT_SELECT(unit); + i2c_init(inst, freq); + + if (sda < 0) sda = PICO_DEFAULT_I2C_SDA_PIN; + if (scl < 0) scl = PICO_DEFAULT_I2C_SCL_PIN; + gpio_set_function(sda, GPIO_FUNC_I2C); + gpio_set_function(scl, GPIO_FUNC_I2C); + gpio_pull_up(sda); + gpio_pull_up(scl); + + return MRB_I2C_OK; +} + +int +mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len, + uint32_t timeout_us) +{ + UNIT_SELECT(unit); + return i2c_read_timeout_us(inst, addr, dst, len, false, timeout_us); +} + +int +mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len, + uint32_t timeout_us) +{ + UNIT_SELECT(unit); + return i2c_write_timeout_us(inst, addr, src, len, false, timeout_us); +} + +int +mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen, + uint8_t *dst, size_t rlen, uint32_t timeout_us) +{ + UNIT_SELECT(unit); + /* write with nostop=true (no STOP, keeps bus for repeated START) */ + int ret = i2c_write_timeout_us(inst, addr, src, wlen, true, timeout_us); + if (ret < 0) return ret; + /* read with nostop=false (STOP after read) */ + return i2c_read_timeout_us(inst, addr, dst, rlen, false, timeout_us); +} diff --git a/mrbgems/hw-i2c/src/i2c.c b/mrbgems/hw-i2c/src/i2c.c new file mode 100644 index 0000000000..46aab65542 --- /dev/null +++ b/mrbgems/hw-i2c/src/i2c.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include +#include + +#define STACK_BUF_SIZE 256 +#define E_IO_ERROR mrb_exc_get_id(mrb, MRB_SYM(IOError)) + +static size_t +i2c_fill_buf(mrb_state *mrb, uint8_t *buf, mrb_value *args, mrb_int argc) +{ + size_t pos = 0; + for (mrb_int i = 0; i < argc; i++) { + switch (mrb_type(args[i])) { + case MRB_TT_ARRAY: { + mrb_int alen = RARRAY_LEN(args[i]); + const mrb_value *aptr = RARRAY_PTR(args[i]); + for (mrb_int j = 0; j < alen; j++) { + if (!mrb_integer_p(aptr[j])) { + mrb_raise(mrb, E_TYPE_ERROR, "array element must be Integer"); + } + buf[pos++] = (uint8_t)mrb_integer(aptr[j]); + } + break; + } + case MRB_TT_INTEGER: + buf[pos++] = (uint8_t)mrb_integer(args[i]); + break; + case MRB_TT_STRING: + memcpy(&buf[pos], RSTRING_PTR(args[i]), RSTRING_LEN(args[i])); + pos += RSTRING_LEN(args[i]); + break; + default: + break; + } + } + return pos; +} + +static size_t +i2c_calc_size(mrb_state *mrb, mrb_value *args, mrb_int argc) +{ + size_t total = 0; + for (mrb_int i = 0; i < argc; i++) { + switch (mrb_type(args[i])) { + case MRB_TT_ARRAY: + total += RARRAY_LEN(args[i]); + break; + case MRB_TT_INTEGER: + total += 1; + break; + case MRB_TT_STRING: + total += RSTRING_LEN(args[i]); + break; + default: + mrb_raise(mrb, E_TYPE_ERROR, "Integer, Array, or String expected"); + } + } + return total; +} + +/* Allocate write buffer, fill it, return pointer and size. + Caller must free if need_free is set. */ +static uint8_t* +i2c_build_buf(mrb_state *mrb, mrb_value *args, mrb_int argc, + size_t *out_len, uint8_t *sbuf, mrb_bool *need_free) +{ + size_t total = i2c_calc_size(mrb, args, argc); + uint8_t *buf; + if (total <= STACK_BUF_SIZE) { + buf = sbuf; + *need_free = FALSE; + } + else { + buf = (uint8_t*)mrb_malloc(mrb, total); + *need_free = TRUE; + } + i2c_fill_buf(mrb, buf, args, argc); + *out_len = total; + return buf; +} + +static mrb_int +get_timeout(mrb_state *mrb, mrb_value self, mrb_value kw) +{ + if (mrb_undef_p(kw)) { + return mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(timeout))); + } + return mrb_integer(kw); +} + +static mrb_value +mrb_i2c_m_write(mrb_state *mrb, mrb_value self) +{ + mrb_value *args; + mrb_int argc, addr; + const mrb_sym kw_names[] = { MRB_SYM(timeout) }; + mrb_value kw_values[1]; + mrb_kwargs kwargs = { 1, 0, kw_names, kw_values, NULL }; + + mrb_get_args(mrb, "i*:", &addr, &args, &argc, &kwargs); + + mrb_int timeout_ms = get_timeout(mrb, self, kw_values[0]); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + + uint8_t sbuf[STACK_BUF_SIZE]; + size_t wlen; + mrb_bool need_free; + uint8_t *buf = i2c_build_buf(mrb, args, argc, &wlen, sbuf, &need_free); + + int ret = mrb_i2c_write((int)unit, (uint8_t)addr, buf, wlen, + (uint32_t)timeout_ms * 1000); + if (need_free) mrb_free(mrb, buf); + if (ret < 0) { + mrb_raise(mrb, E_IO_ERROR, "I2C write failed"); + } + return mrb_fixnum_value(ret); +} + +static mrb_value +mrb_i2c_m_read(mrb_state *mrb, mrb_value self) +{ + mrb_value *args; + mrb_int argc, addr, len; + const mrb_sym kw_names[] = { MRB_SYM(timeout) }; + mrb_value kw_values[1]; + mrb_kwargs kwargs = { 1, 0, kw_names, kw_values, NULL }; + + mrb_get_args(mrb, "ii*:", &addr, &len, &args, &argc, &kwargs); + + if (len <= 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "read length must be positive"); + } + + mrb_int timeout_ms = get_timeout(mrb, self, kw_values[0]); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + uint32_t timeout_us = (uint32_t)timeout_ms * 1000; + + uint8_t *rxbuf = (uint8_t*)mrb_malloc(mrb, len); + int ret; + + if (argc > 0) { + /* write-then-read (repeated START) */ + uint8_t sbuf[STACK_BUF_SIZE]; + size_t wlen; + mrb_bool need_free; + uint8_t *wbuf = i2c_build_buf(mrb, args, argc, &wlen, sbuf, &need_free); + + ret = mrb_i2c_write_read((int)unit, (uint8_t)addr, + wbuf, wlen, rxbuf, (size_t)len, timeout_us); + if (need_free) mrb_free(mrb, wbuf); + } + else { + ret = mrb_i2c_read((int)unit, (uint8_t)addr, rxbuf, (size_t)len, timeout_us); + } + + if (ret < 0) { + mrb_free(mrb, rxbuf); + mrb_raise(mrb, E_IO_ERROR, "I2C read failed"); + } + mrb_value str = mrb_str_new(mrb, (const char*)rxbuf, ret); + mrb_free(mrb, rxbuf); + return str; +} + +static mrb_value +mrb_i2c_m_init(mrb_state *mrb, mrb_value self) +{ + const char *unit; + mrb_int freq, sda, scl; + + mrb_get_args(mrb, "ziii", &unit, &freq, &sda, &scl); + + int num = mrb_i2c_unit_name_to_num(unit); + if (num < 0) { + mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown I2C unit: %s", unit); + } + mrb_i2c_status st = mrb_i2c_init(num, (uint32_t)freq, (int8_t)sda, (int8_t)scl); + if (st != MRB_I2C_OK) { + mrb_raise(mrb, E_IO_ERROR, "I2C init failed"); + } + return mrb_fixnum_value(num); +} + +void +mrb_hw_i2c_gem_init(mrb_state *mrb) +{ + struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(I2C), mrb->object_class); + mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_i2c_m_init, MRB_ARGS_REQ(4)); + mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_i2c_m_write, MRB_ARGS_REQ(1)|MRB_ARGS_REST()|MRB_ARGS_KEY(1, 0)); + mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_i2c_m_read, MRB_ARGS_REQ(2)|MRB_ARGS_REST()|MRB_ARGS_KEY(1, 0)); +} + +void +mrb_hw_i2c_gem_final(mrb_state *mrb) +{ +} diff --git a/mrbgems/hw-pwm/README.md b/mrbgems/hw-pwm/README.md new file mode 100644 index 0000000000..6a4ff01cea --- /dev/null +++ b/mrbgems/hw-pwm/README.md @@ -0,0 +1,87 @@ +# hw-pwm - PWM peripheral interface for mruby + +This gem provides the `PWM` class for Pulse Width Modulation +output from mruby. It is designed for embedded platforms such as +ESP32 and RP2040. + +## Architecture + +Platform-specific HAL implementations are in `ports/` directories: + +- `ports/esp32/` - ESP32 using LEDC peripheral +- `ports/rp2040/` - RP2040 using Pico SDK PWM hardware + +## Build Configuration + +```ruby +MRuby::CrossBuild.new('esp32') do |conf| + conf.ports :esp32 + conf.gem core: 'hw-pwm' +end +``` + +## Ruby API + +### PWM.new + +```ruby +pwm = PWM.new(pin, frequency: 1000, duty: 50) +``` + +- `pin` - GPIO pin number (Integer) +- `frequency:` - frequency in Hz (default: 0, disabled) +- `duty:` - duty cycle in percent 0-100 (default: 50) + +### PWM#frequency(freq) + +Set frequency in Hz. Returns the frequency. Setting 0 disables +output. + +```ruby +pwm.frequency(1000) # 1 kHz +pwm.frequency(0) # disable +``` + +### PWM#period_us(us) + +Set period in microseconds. Returns the corresponding frequency. + +```ruby +pwm.period_us(1000) # 1ms period = 1 kHz +``` + +### PWM#duty(pct) + +Set duty cycle in percent (0.0-100.0). Clamped to range. + +```ruby +pwm.duty(75.0) +``` + +### PWM#pulse_width_us(us) + +Set pulse width in microseconds. Duty cycle is calculated from +current frequency. + +```ruby +pwm.pulse_width_us(500) # 500us pulse width +``` + +## HAL Interface + +To add support for a new platform, create a `ports//` +directory and implement the following C functions declared in +``: + +```c +void mrb_pwm_init(uint32_t pin); +void mrb_pwm_set_freq_duty(uint32_t pin, float frequency, float duty); +void mrb_pwm_set_enabled(uint32_t pin, bool enabled); +``` + +- `frequency` is in Hz, `duty` is in percent (0-100) +- `mrb_pwm_set_enabled` is called with `false` when frequency is 0 + +## License + +MIT diff --git a/mrbgems/hw-pwm/include/mruby/pwm.h b/mrbgems/hw-pwm/include/mruby/pwm.h new file mode 100644 index 0000000000..f1f25d8004 --- /dev/null +++ b/mrbgems/hw-pwm/include/mruby/pwm.h @@ -0,0 +1,20 @@ +#ifndef MRUBY_PWM_H +#define MRUBY_PWM_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* HAL functions - implemented in ports//pwm.c */ +void mrb_pwm_init(uint32_t pin); +void mrb_pwm_set_freq_duty(uint32_t pin, float frequency, float duty); +void mrb_pwm_set_enabled(uint32_t pin, bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif /* MRUBY_PWM_H */ diff --git a/mrbgems/hw-pwm/mrbgem.rake b/mrbgems/hw-pwm/mrbgem.rake new file mode 100644 index 0000000000..bc3d0f0499 --- /dev/null +++ b/mrbgems/hw-pwm/mrbgem.rake @@ -0,0 +1,5 @@ +MRuby::Gem::Specification.new('hw-pwm') do |spec| + spec.license = 'MIT' + spec.authors = ['HASUMI Hitoshi', 'mruby developers'] + spec.summary = 'PWM peripheral interface' +end diff --git a/mrbgems/hw-pwm/mrblib/pwm.rb b/mrbgems/hw-pwm/mrblib/pwm.rb new file mode 100644 index 0000000000..3e2cf82915 --- /dev/null +++ b/mrbgems/hw-pwm/mrblib/pwm.rb @@ -0,0 +1,9 @@ +class PWM + def initialize(pin, frequency: 0, duty: 50) + @pin = pin + __init(@pin) + @frequency = frequency.to_f + @duty = duty.to_f + frequency(@frequency) + end +end diff --git a/mrbgems/hw-pwm/ports/esp32/pwm.c b/mrbgems/hw-pwm/ports/esp32/pwm.c new file mode 100644 index 0000000000..e91f280fc1 --- /dev/null +++ b/mrbgems/hw-pwm/ports/esp32/pwm.c @@ -0,0 +1,59 @@ +#include "driver/ledc.h" +#include + +#define DUTY_RESOLUTION LEDC_TIMER_14_BIT + +static int8_t channel_for_gpio[GPIO_NUM_MAX]; +static int next_channel; + +void +mrb_pwm_init(uint32_t gpio) +{ + if (gpio >= GPIO_NUM_MAX) return; + if (next_channel >= LEDC_CHANNEL_MAX) return; + + ledc_timer_config_t timer_cfg = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .timer_num = LEDC_TIMER_0, + .duty_resolution = DUTY_RESOLUTION, + .freq_hz = 1000, + .clk_cfg = LEDC_AUTO_CLK, + }; + ledc_timer_config(&timer_cfg); + + ledc_channel_config_t ch_cfg = { + .gpio_num = gpio, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = next_channel, + .timer_sel = LEDC_TIMER_0, + .intr_type = LEDC_INTR_DISABLE, + .duty = 0, + .hpoint = 0, + }; + ledc_channel_config(&ch_cfg); + + channel_for_gpio[gpio] = next_channel++; +} + +void +mrb_pwm_set_freq_duty(uint32_t gpio, float frequency, float duty) +{ + if (gpio >= GPIO_NUM_MAX) return; + + ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, (uint32_t)frequency); + + int8_t ch = channel_for_gpio[gpio]; + uint32_t max_duty = (1 << DUTY_RESOLUTION) - 1; + uint32_t d = (uint32_t)(duty * max_duty / 100.0f); + ledc_set_duty(LEDC_LOW_SPEED_MODE, ch, d); + ledc_update_duty(LEDC_LOW_SPEED_MODE, ch); +} + +void +mrb_pwm_set_enabled(uint32_t gpio, bool enabled) +{ + if (!enabled) { + int8_t ch = channel_for_gpio[gpio]; + ledc_stop(LEDC_LOW_SPEED_MODE, ch, 0); + } +} diff --git a/mrbgems/hw-pwm/ports/rp2040/pwm.c b/mrbgems/hw-pwm/ports/rp2040/pwm.c new file mode 100644 index 0000000000..5800df3f4f --- /dev/null +++ b/mrbgems/hw-pwm/ports/rp2040/pwm.c @@ -0,0 +1,33 @@ +#include "pico/stdlib.h" +#include "hardware/pwm.h" +#include + +#define APB_CLK_FREQ 125000000 +#define CLK_DIV 100.0f + +void +mrb_pwm_init(uint32_t pin) +{ + gpio_set_function(pin, GPIO_FUNC_PWM); + uint slice = pwm_gpio_to_slice_num(pin); + pwm_set_clkdiv(slice, CLK_DIV); +} + +void +mrb_pwm_set_freq_duty(uint32_t pin, float frequency, float duty) +{ + uint slice = pwm_gpio_to_slice_num(pin); + uint channel = pwm_gpio_to_channel(pin); + float period = 1.0f / frequency; + uint16_t wrap = (uint16_t)(period * APB_CLK_FREQ / CLK_DIV); + pwm_set_wrap(slice, wrap); + uint16_t level = (uint16_t)(wrap * duty / 100.0f); + pwm_set_chan_level(slice, channel, level); +} + +void +mrb_pwm_set_enabled(uint32_t pin, bool enabled) +{ + uint slice = pwm_gpio_to_slice_num(pin); + pwm_set_enabled(slice, enabled); +} diff --git a/mrbgems/hw-pwm/src/pwm.c b/mrbgems/hw-pwm/src/pwm.c new file mode 100644 index 0000000000..6cae315553 --- /dev/null +++ b/mrbgems/hw-pwm/src/pwm.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +static mrb_value +mrb_pwm_m_init(mrb_state *mrb, mrb_value self) +{ + mrb_int pin; + mrb_get_args(mrb, "i", &pin); + mrb_pwm_init((uint32_t)pin); + return mrb_nil_value(); +} + +static void +apply_freq_duty(mrb_state *mrb, mrb_value self) +{ + uint32_t pin = (uint32_t)mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(pin))); + mrb_float freq = mrb_as_float(mrb, mrb_iv_get(mrb, self, MRB_IVSYM(frequency))); + mrb_float duty = mrb_as_float(mrb, mrb_iv_get(mrb, self, MRB_IVSYM(duty))); + mrb_pwm_set_freq_duty(pin, (float)freq, (float)duty); + mrb_pwm_set_enabled(pin, freq > 0); +} + +/* PWM#frequency(freq) */ +static mrb_value +mrb_pwm_m_frequency(mrb_state *mrb, mrb_value self) +{ + mrb_float freq; + mrb_get_args(mrb, "f", &freq); + mrb_iv_set(mrb, self, MRB_IVSYM(frequency), mrb_float_value(mrb, freq)); + apply_freq_duty(mrb, self); + return mrb_float_value(mrb, freq); +} + +/* PWM#period_us(us) */ +static mrb_value +mrb_pwm_m_period_us(mrb_state *mrb, mrb_value self) +{ + mrb_int us; + mrb_get_args(mrb, "i", &us); + mrb_float freq = 1000000.0 / us; + mrb_iv_set(mrb, self, MRB_IVSYM(frequency), mrb_float_value(mrb, freq)); + apply_freq_duty(mrb, self); + return mrb_float_value(mrb, freq); +} + +/* PWM#duty(pct) */ +static mrb_value +mrb_pwm_m_duty(mrb_state *mrb, mrb_value self) +{ + mrb_float duty; + mrb_get_args(mrb, "f", &duty); + if (duty < 0.0) duty = 0.0; + if (duty > 100.0) duty = 100.0; + mrb_iv_set(mrb, self, MRB_IVSYM(duty), mrb_float_value(mrb, duty)); + apply_freq_duty(mrb, self); + return mrb_float_value(mrb, duty); +} + +/* PWM#pulse_width_us(us) */ +static mrb_value +mrb_pwm_m_pulse_width_us(mrb_state *mrb, mrb_value self) +{ + mrb_int pw; + mrb_get_args(mrb, "i", &pw); + mrb_float freq = mrb_as_float(mrb, mrb_iv_get(mrb, self, MRB_IVSYM(frequency))); + mrb_float duty = (mrb_float)pw / 10000.0 * freq; + if (duty < 0.0) duty = 0.0; + if (duty > 100.0) duty = 100.0; + mrb_iv_set(mrb, self, MRB_IVSYM(duty), mrb_float_value(mrb, duty)); + apply_freq_duty(mrb, self); + return mrb_float_value(mrb, duty); +} + +void +mrb_hw_pwm_gem_init(mrb_state *mrb) +{ + struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(PWM), mrb->object_class); + mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_pwm_m_init, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(frequency), mrb_pwm_m_frequency, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(period_us), mrb_pwm_m_period_us, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(duty), mrb_pwm_m_duty, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(pulse_width_us), mrb_pwm_m_pulse_width_us, MRB_ARGS_REQ(1)); +} + +void +mrb_hw_pwm_gem_final(mrb_state *mrb) +{ +} diff --git a/mrbgems/hw-spi/README.md b/mrbgems/hw-spi/README.md new file mode 100644 index 0000000000..a29403bc58 --- /dev/null +++ b/mrbgems/hw-spi/README.md @@ -0,0 +1,113 @@ +# hw-spi - SPI peripheral interface for mruby + +This gem provides the `SPI` class for Serial Peripheral Interface +communication from mruby. It is designed for embedded platforms +such as ESP32 and RP2040. + +## Architecture + +Platform-specific HAL implementations are in `ports/` directories: + +- `ports/esp32/` - ESP32 using ESP-IDF SPI master driver +- `ports/rp2040/` - RP2040 using Pico SDK + +The build system automatically compiles matching port sources based +on `conf.ports` setting. + +## Build Configuration + +```ruby +MRuby::CrossBuild.new('esp32') do |conf| + conf.ports :esp32 + conf.gem core: 'hw-spi' +end +``` + +## Ruby API + +### SPI.new + +```ruby +spi = SPI.new( + unit: :RP2040_SPI0, # SPI unit name (required) + frequency: 100_000, # clock frequency in Hz (default: 100kHz) + sck_pin: -1, # SCK GPIO pin (default: platform default) + copi_pin: -1, # COPI/MOSI GPIO pin + cipo_pin: -1, # CIPO/MISO GPIO pin + cs_pin: -1, # CS GPIO pin (-1 for manual control) + mode: 0, # SPI mode 0-3 (default: 0) + first_bit: SPI::MSB_FIRST # bit order (default: MSB_FIRST) +) +``` + +#### Unit Names + +| Platform | Available Units | +| -------- | ------------------------------------------ | +| ESP32 | `:ESP32_SPI2_HOST`, `:ESP32_HSPI_HOST`, | +| | `:ESP32_SPI3_HOST`\*, `:ESP32_VSPI_HOST`\* | +| RP2040 | `:RP2040_SPI0`, `:RP2040_SPI1` | + +\*SPI3 availability depends on ESP32 variant. + +### `SPI#write(*data)` + +Write data to the SPI bus. Data can be Integer, Array, or String. + +```ruby +spi.write(0x01, [0x02, 0x03]) +``` + +### SPI#read(len, tx_value = 0) + +Read `len` bytes. Optionally specify the value to transmit during +read. + +```ruby +data = spi.read(4) # transmits 0x00 while reading +data = spi.read(4, 0xFF) # transmits 0xFF while reading +``` + +### `SPI#transfer(*data, additional_read_bytes: 0)` + +Full-duplex transfer. Sends data and returns received bytes. +Use `additional_read_bytes:` to append zero-filled read bytes. + +```ruby +# Send 1 byte command, read 4 bytes response +rx = spi.transfer(0x9F, additional_read_bytes: 4) +``` + +### SPI#select / SPI#deselect + +Manually control the CS pin (when using GPIO-based chip select). + +```ruby +spi.select do |s| + s.write(0x01) + data = s.read(4) +end # CS automatically deasserted +``` + +## HAL Interface + +To add support for a new platform, create a `ports//` +directory and implement the following C functions declared in +``: + +```c +int mrb_spi_unit_name_to_num(const char *name); +mrb_spi_status mrb_spi_init(mrb_spi_info *info); +int mrb_spi_read(mrb_spi_info *info, uint8_t *dst, size_t len, + uint8_t tx_val); +int mrb_spi_write(mrb_spi_info *info, const uint8_t *src, size_t len); +int mrb_spi_transfer(mrb_spi_info *info, const uint8_t *tx, + uint8_t *rx, size_t len); +``` + +The `mrb_spi_info` struct contains all configuration (unit, pins, +frequency, mode, bit order) and is passed to every HAL call. + +## License + +MIT diff --git a/mrbgems/hw-spi/include/mruby/spi.h b/mrbgems/hw-spi/include/mruby/spi.h new file mode 100644 index 0000000000..9be4fc55b2 --- /dev/null +++ b/mrbgems/hw-spi/include/mruby/spi.h @@ -0,0 +1,44 @@ +#ifndef MRUBY_SPI_H +#define MRUBY_SPI_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MRB_SPI_MSB_FIRST 1 +#define MRB_SPI_LSB_FIRST 0 + +typedef enum { + MRB_SPI_OK = 0, + MRB_SPI_ERROR_UNIT = -1, + MRB_SPI_ERROR_MODE = -2, + MRB_SPI_ERROR_FIRST_BIT = -3, + MRB_SPI_ERROR_INIT = -4, +} mrb_spi_status; + +typedef struct { + uint32_t frequency; + uint8_t unit_num; + int8_t sck_pin; + int8_t copi_pin; + int8_t cipo_pin; + int8_t cs_pin; + uint8_t mode; + uint8_t first_bit; +} mrb_spi_info; + +/* HAL functions - implemented in ports//spi.c */ +int mrb_spi_unit_name_to_num(const char *name); +mrb_spi_status mrb_spi_init(mrb_spi_info *info); +int mrb_spi_read(mrb_spi_info *info, uint8_t *dst, size_t len, uint8_t tx_val); +int mrb_spi_write(mrb_spi_info *info, const uint8_t *src, size_t len); +int mrb_spi_transfer(mrb_spi_info *info, const uint8_t *tx, uint8_t *rx, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* MRUBY_SPI_H */ diff --git a/mrbgems/hw-spi/mrbgem.rake b/mrbgems/hw-spi/mrbgem.rake new file mode 100644 index 0000000000..f2f1149adc --- /dev/null +++ b/mrbgems/hw-spi/mrbgem.rake @@ -0,0 +1,5 @@ +MRuby::Gem::Specification.new('hw-spi') do |spec| + spec.license = 'MIT' + spec.authors = ['HASUMI Hitoshi', 'mruby developers'] + spec.summary = 'SPI peripheral interface' +end diff --git a/mrbgems/hw-spi/mrblib/spi.rb b/mrbgems/hw-spi/mrblib/spi.rb new file mode 100644 index 0000000000..d0d6db5192 --- /dev/null +++ b/mrbgems/hw-spi/mrblib/spi.rb @@ -0,0 +1,20 @@ +class SPI + MSB_FIRST = 1 + LSB_FIRST = 0 + DEFAULT_FREQUENCY = 100_000 + + def select + @cs&.write 0 + if block_given? + begin + yield self + ensure + deselect + end + end + end + + def deselect + @cs&.write 1 + end +end diff --git a/mrbgems/hw-spi/ports/esp32/spi.c b/mrbgems/hw-spi/ports/esp32/spi.c new file mode 100644 index 0000000000..9557d6584f --- /dev/null +++ b/mrbgems/hw-spi/ports/esp32/spi.c @@ -0,0 +1,85 @@ +#include +#include "driver/spi_master.h" +#include + +static spi_device_handle_t handles[SPI_HOST_MAX]; + +int +mrb_spi_unit_name_to_num(const char *name) +{ + if (strcmp(name, "ESP32_SPI2_HOST") == 0) return SPI2_HOST; + if (strcmp(name, "ESP32_HSPI_HOST") == 0) return SPI2_HOST; +#if (SOC_SPI_PERIPH_NUM == 3) + if (strcmp(name, "ESP32_SPI3_HOST") == 0) return SPI3_HOST; + if (strcmp(name, "ESP32_VSPI_HOST") == 0) return SPI3_HOST; +#endif + return MRB_SPI_ERROR_UNIT; +} + +mrb_spi_status +mrb_spi_init(mrb_spi_info *info) +{ + if (handles[info->unit_num] != NULL) return MRB_SPI_OK; + + spi_bus_config_t buscfg = { + .mosi_io_num = info->copi_pin, + .miso_io_num = info->cipo_pin, + .sclk_io_num = info->sck_pin, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + }; + + esp_err_t err = spi_bus_initialize(info->unit_num, &buscfg, SPI_DMA_CH_AUTO); + if (err != ESP_OK) return MRB_SPI_ERROR_INIT; + + spi_device_interface_config_t devcfg = { + .clock_speed_hz = info->frequency, + .mode = info->mode, + .spics_io_num = info->cs_pin, + .queue_size = 7, + }; + + err = spi_bus_add_device(info->unit_num, &devcfg, &handles[info->unit_num]); + if (err != ESP_OK) { + spi_bus_free(info->unit_num); + handles[info->unit_num] = NULL; + return MRB_SPI_ERROR_INIT; + } + return MRB_SPI_OK; +} + +int +mrb_spi_read(mrb_spi_info *info, uint8_t *dst, size_t len, uint8_t tx_val) +{ + spi_transaction_t t = { + .length = len * 8, + .tx_buffer = NULL, + .rx_buffer = dst, + }; + esp_err_t err = spi_device_polling_transmit(handles[info->unit_num], &t); + return (err == ESP_OK) ? (int)len : -1; +} + +int +mrb_spi_write(mrb_spi_info *info, const uint8_t *src, size_t len) +{ + spi_transaction_t t = { + .length = len * 8, + .tx_buffer = src, + .rx_buffer = NULL, + }; + esp_err_t err = spi_device_polling_transmit(handles[info->unit_num], &t); + return (err == ESP_OK) ? (int)len : -1; +} + +int +mrb_spi_transfer(mrb_spi_info *info, const uint8_t *tx, uint8_t *rx, size_t len) +{ + spi_transaction_t t = { + .length = len * 8, + .tx_buffer = tx, + .rx_buffer = rx, + }; + esp_err_t err = spi_device_polling_transmit(handles[info->unit_num], &t); + return (err == ESP_OK) ? (int)len : -1; +} diff --git a/mrbgems/hw-spi/ports/rp2040/spi.c b/mrbgems/hw-spi/ports/rp2040/spi.c new file mode 100644 index 0000000000..9210e1c2a7 --- /dev/null +++ b/mrbgems/hw-spi/ports/rp2040/spi.c @@ -0,0 +1,73 @@ +#include +#include "pico/stdlib.h" +#include "hardware/spi.h" +#include + +#define UNIT_SELECT(info) \ + spi_inst_t *inst; \ + switch ((info)->unit_num) { \ + case 0: inst = spi0; break; \ + case 1: inst = spi1; break; \ + default: return MRB_SPI_ERROR_UNIT; \ + } + +int +mrb_spi_unit_name_to_num(const char *name) +{ + if (strcmp(name, "RP2040_SPI0") == 0) return 0; + if (strcmp(name, "RP2040_SPI1") == 0) return 1; + return MRB_SPI_ERROR_UNIT; +} + +mrb_spi_status +mrb_spi_init(mrb_spi_info *info) +{ + UNIT_SELECT(info); + spi_init(inst, info->frequency); + + if (info->sck_pin < 0) info->sck_pin = PICO_DEFAULT_SPI_SCK_PIN; + if (info->cipo_pin < 0) info->cipo_pin = PICO_DEFAULT_SPI_RX_PIN; + if (info->copi_pin < 0) info->copi_pin = PICO_DEFAULT_SPI_TX_PIN; + + gpio_set_function(info->sck_pin, GPIO_FUNC_SPI); + gpio_set_function(info->cipo_pin, GPIO_FUNC_SPI); + gpio_set_function(info->copi_pin, GPIO_FUNC_SPI); + + if (info->first_bit != MRB_SPI_MSB_FIRST) { + return MRB_SPI_ERROR_FIRST_BIT; + } + + spi_cpol_t cpol; + spi_cpha_t cpha; + switch (info->mode) { + case 0: cpol = 0; cpha = 0; break; + case 1: cpol = 0; cpha = 1; break; + case 2: cpol = 1; cpha = 0; break; + case 3: cpol = 1; cpha = 1; break; + default: return MRB_SPI_ERROR_MODE; + } + spi_set_format(inst, 8, cpol, cpha, info->first_bit); + + return MRB_SPI_OK; +} + +int +mrb_spi_read(mrb_spi_info *info, uint8_t *dst, size_t len, uint8_t tx_val) +{ + UNIT_SELECT(info); + return spi_read_blocking(inst, tx_val, dst, len); +} + +int +mrb_spi_write(mrb_spi_info *info, const uint8_t *src, size_t len) +{ + UNIT_SELECT(info); + return spi_write_blocking(inst, src, len); +} + +int +mrb_spi_transfer(mrb_spi_info *info, const uint8_t *tx, uint8_t *rx, size_t len) +{ + UNIT_SELECT(info); + return spi_write_read_blocking(inst, tx, rx, len); +} diff --git a/mrbgems/hw-spi/src/spi.c b/mrbgems/hw-spi/src/spi.c new file mode 100644 index 0000000000..2b75930c87 --- /dev/null +++ b/mrbgems/hw-spi/src/spi.c @@ -0,0 +1,235 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACK_BUF_SIZE 256 +#define E_IO_ERROR mrb_exc_get_id(mrb, MRB_SYM(IOError)) + +static void +spi_info_free(mrb_state *mrb, void *ptr) +{ + mrb_free(mrb, ptr); +} + +static const struct mrb_data_type spi_info_type = { "SPI", spi_info_free }; + +#define SPI_INFO(self) \ + ((mrb_spi_info*)mrb_data_get_ptr(mrb, self, &spi_info_type)) + +static size_t +spi_calc_size(mrb_state *mrb, mrb_value *args, mrb_int argc) +{ + size_t total = 0; + for (mrb_int i = 0; i < argc; i++) { + switch (mrb_type(args[i])) { + case MRB_TT_ARRAY: + total += RARRAY_LEN(args[i]); + break; + case MRB_TT_INTEGER: + total += 1; + break; + case MRB_TT_STRING: + total += RSTRING_LEN(args[i]); + break; + default: + mrb_raise(mrb, E_TYPE_ERROR, "Integer, Array, or String expected"); + } + } + return total; +} + +static void +spi_fill_buf(mrb_state *mrb, uint8_t *buf, mrb_value *args, mrb_int argc, + size_t pad) +{ + size_t pos = 0; + for (mrb_int i = 0; i < argc; i++) { + switch (mrb_type(args[i])) { + case MRB_TT_ARRAY: { + mrb_int alen = RARRAY_LEN(args[i]); + const mrb_value *aptr = RARRAY_PTR(args[i]); + for (mrb_int j = 0; j < alen; j++) { + if (!mrb_integer_p(aptr[j])) { + mrb_raise(mrb, E_TYPE_ERROR, "array element must be Integer"); + } + buf[pos++] = (uint8_t)mrb_integer(aptr[j]); + } + break; + } + case MRB_TT_INTEGER: + buf[pos++] = (uint8_t)mrb_integer(args[i]); + break; + case MRB_TT_STRING: + memcpy(&buf[pos], RSTRING_PTR(args[i]), RSTRING_LEN(args[i])); + pos += RSTRING_LEN(args[i]); + break; + default: + break; + } + } + memset(&buf[pos], 0, pad); +} + +/* SPI.new(unit:, frequency:, sck_pin:, cipo_pin:, copi_pin:, + cs_pin:, mode:, first_bit:) */ +static mrb_value +mrb_spi_s_new(mrb_state *mrb, mrb_value klass) +{ + const char *unit_name; + mrb_int freq = 100000, sck = -1, cipo = -1, copi = -1, cs = -1; + mrb_int mode = 0, first_bit = MRB_SPI_MSB_FIRST; + + const mrb_sym kw_names[] = { + MRB_SYM(unit), MRB_SYM(frequency), MRB_SYM(sck_pin), + MRB_SYM(cipo_pin), MRB_SYM(copi_pin), MRB_SYM(cs_pin), + MRB_SYM(mode), MRB_SYM(first_bit) + }; + mrb_value kw_values[8]; + mrb_kwargs kwargs = { 8, 7, kw_names, kw_values, NULL }; + mrb_get_args(mrb, ":", &kwargs); + + /* unit is required */ + if (mrb_undef_p(kw_values[0])) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "unit: is required"); + } + unit_name = mrb_str_to_cstr(mrb, mrb_sym_str(mrb, mrb_symbol(kw_values[0]))); + + if (!mrb_undef_p(kw_values[1])) freq = mrb_integer(kw_values[1]); + if (!mrb_undef_p(kw_values[2])) sck = mrb_integer(kw_values[2]); + if (!mrb_undef_p(kw_values[3])) cipo = mrb_integer(kw_values[3]); + if (!mrb_undef_p(kw_values[4])) copi = mrb_integer(kw_values[4]); + if (!mrb_undef_p(kw_values[5])) cs = mrb_integer(kw_values[5]); + if (!mrb_undef_p(kw_values[6])) mode = mrb_integer(kw_values[6]); + if (!mrb_undef_p(kw_values[7])) first_bit = mrb_integer(kw_values[7]); + + int num = mrb_spi_unit_name_to_num(unit_name); + if (num < 0) { + mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown SPI unit: %s", unit_name); + } + + mrb_spi_info *info = (mrb_spi_info*)mrb_malloc(mrb, sizeof(mrb_spi_info)); + info->unit_num = (uint8_t)num; + info->frequency = (uint32_t)freq; + info->sck_pin = (int8_t)sck; + info->cipo_pin = (int8_t)cipo; + info->copi_pin = (int8_t)copi; + info->cs_pin = (int8_t)cs; + info->mode = (uint8_t)mode; + info->first_bit = (uint8_t)first_bit; + + mrb_value self = mrb_obj_value( + Data_Wrap_Struct(mrb, mrb_class_ptr(klass), &spi_info_type, info)); + + mrb_spi_status st = mrb_spi_init(info); + if (st != MRB_SPI_OK) { + mrb_raise(mrb, E_IO_ERROR, "SPI init failed"); + } + return self; +} + +/* SPI#write(*data) */ +static mrb_value +mrb_spi_m_write(mrb_state *mrb, mrb_value self) +{ + mrb_value *args; + mrb_int argc; + mrb_get_args(mrb, "*", &args, &argc); + + mrb_spi_info *info = SPI_INFO(self); + size_t total = spi_calc_size(mrb, args, argc); + if (total == 0) return mrb_fixnum_value(0); + + uint8_t sbuf[STACK_BUF_SIZE]; + uint8_t *buf = sbuf; + mrb_bool need_free = FALSE; + if (total > STACK_BUF_SIZE) { + buf = (uint8_t*)mrb_malloc(mrb, total); + need_free = TRUE; + } + spi_fill_buf(mrb, buf, args, argc, 0); + + int ret = mrb_spi_write(info, buf, total); + if (need_free) mrb_free(mrb, buf); + if (ret < 0) mrb_raise(mrb, E_IO_ERROR, "SPI write failed"); + return mrb_fixnum_value(ret); +} + +/* SPI#read(len, tx_value=0) */ +static mrb_value +mrb_spi_m_read(mrb_state *mrb, mrb_value self) +{ + mrb_int len, tx_val = 0; + mrb_get_args(mrb, "i|i", &len, &tx_val); + if (len <= 0) mrb_raise(mrb, E_ARGUMENT_ERROR, "length must be positive"); + + mrb_spi_info *info = SPI_INFO(self); + uint8_t *buf = (uint8_t*)mrb_malloc(mrb, len); + int ret = mrb_spi_read(info, buf, (size_t)len, (uint8_t)tx_val); + if (ret < 0) { + mrb_free(mrb, buf); + mrb_raise(mrb, E_IO_ERROR, "SPI read failed"); + } + mrb_value str = mrb_str_new(mrb, (const char*)buf, ret); + mrb_free(mrb, buf); + return str; +} + +/* SPI#transfer(*data, additional_read_bytes: 0) */ +static mrb_value +mrb_spi_m_transfer(mrb_state *mrb, mrb_value self) +{ + mrb_value *args; + mrb_int argc, extra = 0; + const mrb_sym kw_names[] = { MRB_SYM(additional_read_bytes) }; + mrb_value kw_values[1]; + mrb_kwargs kwargs = { 1, 0, kw_names, kw_values, NULL }; + mrb_get_args(mrb, "*:", &args, &argc, &kwargs); + + if (!mrb_undef_p(kw_values[0])) extra = mrb_integer(kw_values[0]); + + mrb_spi_info *info = SPI_INFO(self); + size_t total = spi_calc_size(mrb, args, argc) + (size_t)extra; + if (total == 0) return mrb_str_new(mrb, "", 0); + + uint8_t sbuf_tx[STACK_BUF_SIZE], sbuf_rx[STACK_BUF_SIZE]; + uint8_t *tx = sbuf_tx, *rx = sbuf_rx; + mrb_bool need_free = FALSE; + if (total > STACK_BUF_SIZE) { + tx = (uint8_t*)mrb_malloc(mrb, total * 2); + rx = tx + total; + need_free = TRUE; + } + spi_fill_buf(mrb, tx, args, argc, (size_t)extra); + + int ret = mrb_spi_transfer(info, tx, rx, total); + if (ret < 0) { + if (need_free) mrb_free(mrb, tx); + mrb_raise(mrb, E_IO_ERROR, "SPI transfer failed"); + } + mrb_value str = mrb_str_new(mrb, (const char*)rx, ret); + if (need_free) mrb_free(mrb, tx); + return str; +} + +void +mrb_hw_spi_gem_init(mrb_state *mrb) +{ + struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(SPI), mrb->object_class); + MRB_SET_INSTANCE_TT(cls, MRB_TT_CDATA); + + mrb_define_class_method_id(mrb, cls, MRB_SYM(new), mrb_spi_s_new, MRB_ARGS_KEY(8, 1)); + mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_spi_m_write, MRB_ARGS_REST()); + mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_spi_m_read, MRB_ARGS_ARG(1, 1)); + mrb_define_method_id(mrb, cls, MRB_SYM(transfer), mrb_spi_m_transfer, MRB_ARGS_REST()|MRB_ARGS_KEY(1, 0)); +} + +void +mrb_hw_spi_gem_final(mrb_state *mrb) +{ +} diff --git a/mrbgems/hw-uart/README.md b/mrbgems/hw-uart/README.md new file mode 100644 index 0000000000..23769cb3ae --- /dev/null +++ b/mrbgems/hw-uart/README.md @@ -0,0 +1,181 @@ +# hw-uart - UART peripheral interface for mruby + +This gem provides the `UART` class for serial communication from mruby. +It is designed for embedded platforms such as ESP32 and RP2040. + +## Architecture + +Platform-specific HAL implementations are in `ports/` directories: + +- `ports/esp32/` - ESP32 using ESP-IDF UART driver with FreeRTOS + task for RX +- `ports/rp2040/` - RP2040 using Pico SDK with IRQ-driven RX + +Received data is buffered in a ring buffer (allocated by the common +gem) that the platform HAL populates via interrupt or task. The ring +buffer size must be a power of two. + +## Build Configuration + +```ruby +# For ESP32 +MRuby::CrossBuild.new('esp32') do |conf| + conf.ports :esp32 + conf.gem core: 'hw-uart' +end + +# For RP2040 +MRuby::CrossBuild.new('rp2040') do |conf| + conf.ports :rp2040 + conf.gem core: 'hw-uart' +end +``` + +## Ruby API + +### Constants + +| Constant | Value | Description | +| ---------------------------- | ----- | ------------------ | +| `UART::PARITY_NONE` | `0` | No parity | +| `UART::PARITY_EVEN` | `1` | Even parity | +| `UART::PARITY_ODD` | `2` | Odd parity | +| `UART::FLOW_CONTROL_NONE` | `0` | No flow control | +| `UART::FLOW_CONTROL_RTS_CTS` | `1` | Hardware flow ctrl | + +### UART.new + +```ruby +uart = UART.new( + unit: :ESP32_UART1, # UART unit name (required) + tx_pin: 17, # TX GPIO pin (default: -1) + rx_pin: 16, # RX GPIO pin (default: -1) + baudrate: 9600, # baud rate (default: 9600) + data_bits: 8, # 5-8 (default: 8) + stop_bits: 1, # 1-2 (default: 1) + parity: UART::PARITY_NONE, + flow_control: UART::FLOW_CONTROL_NONE, + rx_buffer_size: 256 # must be power of two (default: 256) +) +``` + +#### Unit Names + +| Platform | Available Units | +| -------- | ------------------------------------------------ | +| ESP32 | `:ESP32_UART0`, `:ESP32_UART1`, `:ESP32_UART2`\* | +| RP2040 | `:RP2040_UART0`, `:RP2040_UART1` | + +\*UART2 availability depends on ESP32 variant. + +### Instance Methods + +#### UART#write(str) + +Write a string to the UART. Returns number of bytes written. + +```ruby +uart.write("Hello\r\n") +``` + +#### UART#read(len = nil) + +Read from the RX buffer. Returns `nil` if no data is available. + +- Without argument: returns all available data +- With `len`: returns exactly `len` bytes, or `nil` if fewer are + available + +```ruby +data = uart.read # all available +data = uart.read(10) # exactly 10 bytes or nil +``` + +#### UART#readpartial(maxlen) + +Read up to `maxlen` bytes from the RX buffer. Returns `nil` if empty. + +```ruby +data = uart.readpartial(64) +``` + +#### UART#gets + +Read a line (up to and including `"\n"`). Returns `nil` if no +complete line is available. + +```ruby +line = uart.gets +``` + +#### UART#bytes_available + +Returns the number of bytes in the RX buffer. + +```ruby +n = uart.bytes_available +``` + +#### UART#puts(str) + +Write string with line ending appended (if not already present). + +```ruby +uart.puts("Hello") # writes "Hello\n" +``` + +#### UART#flush + +Wait for all TX data to be sent. + +#### UART#clear_rx_buffer / UART#clear_tx_buffer + +Discard buffered data. + +#### UART#send_break(duration_ms = 100) + +Send a UART break signal for the specified duration. + +#### UART#setmode(baudrate:, data_bits:, stop_bits:, parity:, flow_control:) + +Reconfigure UART parameters after initialization. All parameters are +optional. + +#### UART#baudrate + +Returns the current baud rate. + +#### UART#line_ending=(ending) + +Set the line ending used by `puts`. Must be `"\n"`, `"\r"`, or +`"\r\n"`. + +## HAL Interface + +To add support for a new platform, create a `ports//` +directory and implement the following C functions declared in +``: + +```c +int mrb_uart_unit_name_to_num(const char *name); +mrb_uart_status mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin, + mrb_uart_ringbuf *rxbuf); +uint32_t mrb_uart_set_baudrate(int unit, uint32_t baudrate); +void mrb_uart_set_format(int unit, uint32_t data_bits, + uint32_t stop_bits, uint8_t parity); +void mrb_uart_set_flow_control(int unit, bool cts, bool rts); +void mrb_uart_write(int unit, const uint8_t *src, size_t len); +void mrb_uart_flush(int unit); +void mrb_uart_send_break(int unit, uint32_t duration_ms); +void mrb_uart_clear_rx(int unit); +void mrb_uart_clear_tx(int unit); +``` + +The `rxbuf` parameter passed to `mrb_uart_init` is a ring buffer +allocated by the common gem. The platform must arrange for received +bytes to be pushed into it using `mrb_uart_ringbuf_push()` (e.g., +from an interrupt handler or RTOS task). + +## License + +MIT diff --git a/mrbgems/hw-uart/include/mruby/uart.h b/mrbgems/hw-uart/include/mruby/uart.h new file mode 100644 index 0000000000..d38c2052b1 --- /dev/null +++ b/mrbgems/hw-uart/include/mruby/uart.h @@ -0,0 +1,60 @@ +#ifndef MRUBY_UART_H +#define MRUBY_UART_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MRB_UART_PARITY_NONE 0 +#define MRB_UART_PARITY_EVEN 1 +#define MRB_UART_PARITY_ODD 2 + +#define MRB_UART_FLOW_NONE 0 +#define MRB_UART_FLOW_RTS_CTS 1 + +typedef enum { + MRB_UART_OK = 0, + MRB_UART_ERROR_UNIT = -1, +} mrb_uart_status; + +/* Ring buffer for interrupt-driven RX. + Allocated by common gem, populated by platform interrupt handler. + size must be a power of two. */ +typedef struct { + volatile int head; + volatile int tail; + int mask; + uint8_t data[]; +} mrb_uart_ringbuf; + +/* Ring buffer helpers (implemented in hw-uart/src/ringbuf.c) */ +bool mrb_uart_ringbuf_init(mrb_uart_ringbuf *rb, int size); +bool mrb_uart_ringbuf_push(mrb_uart_ringbuf *rb, uint8_t ch); +int mrb_uart_ringbuf_pop(mrb_uart_ringbuf *rb, uint8_t *dst, int len); +int mrb_uart_ringbuf_available(const mrb_uart_ringbuf *rb); +void mrb_uart_ringbuf_clear(mrb_uart_ringbuf *rb); +int mrb_uart_ringbuf_search(const mrb_uart_ringbuf *rb, uint8_t ch); + +/* HAL functions - implemented by hw--uart gems */ +int mrb_uart_unit_name_to_num(const char *name); +mrb_uart_status mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin, + mrb_uart_ringbuf *rxbuf); +uint32_t mrb_uart_set_baudrate(int unit, uint32_t baudrate); +void mrb_uart_set_format(int unit, uint32_t data_bits, uint32_t stop_bits, + uint8_t parity); +void mrb_uart_set_flow_control(int unit, bool cts, bool rts); +void mrb_uart_write(int unit, const uint8_t *src, size_t len); +void mrb_uart_flush(int unit); +void mrb_uart_send_break(int unit, uint32_t duration_ms); +void mrb_uart_clear_rx(int unit); +void mrb_uart_clear_tx(int unit); + +#ifdef __cplusplus +} +#endif + +#endif /* MRUBY_UART_H */ diff --git a/mrbgems/hw-uart/mrbgem.rake b/mrbgems/hw-uart/mrbgem.rake new file mode 100644 index 0000000000..fe758f757f --- /dev/null +++ b/mrbgems/hw-uart/mrbgem.rake @@ -0,0 +1,5 @@ +MRuby::Gem::Specification.new('hw-uart') do |spec| + spec.license = 'MIT' + spec.authors = ['HASUMI Hitoshi', 'mruby developers'] + spec.summary = 'UART peripheral interface' +end diff --git a/mrbgems/hw-uart/mrblib/uart.rb b/mrbgems/hw-uart/mrblib/uart.rb new file mode 100644 index 0000000000..34a1b44b48 --- /dev/null +++ b/mrbgems/hw-uart/mrblib/uart.rb @@ -0,0 +1,54 @@ +class UART + PARITY_NONE = 0 + PARITY_EVEN = 1 + PARITY_ODD = 2 + FLOW_CONTROL_NONE = 0 + FLOW_CONTROL_RTS_CTS = 1 + + attr_reader :baudrate + + def initialize(unit:, tx_pin: -1, rx_pin: -1, baudrate: 9600, + data_bits: 8, stop_bits: 1, parity: PARITY_NONE, + flow_control: FLOW_CONTROL_NONE, rx_buffer_size: 256) + __open_rx_buffer(rx_buffer_size) + @unit_num = __open_connection(unit.to_s, tx_pin, rx_pin) + @baudrate = __set_baudrate(baudrate) + __set_format(data_bits, stop_bits, parity) + set_flow_control(flow_control) + @line_ending = "\n" + end + + def setmode(baudrate: nil, data_bits: nil, stop_bits: nil, + parity: nil, flow_control: nil) + @baudrate = __set_baudrate(baudrate) if baudrate + __set_format(data_bits || 8, stop_bits || 1, parity || PARITY_NONE) + set_flow_control(flow_control || FLOW_CONTROL_NONE) + self + end + + def line_ending=(ending) + unless ["\n", "\r", "\r\n"].include?(ending) + raise ArgumentError, "invalid line ending" + end + @line_ending = ending + end + + def puts(str) + write str + write @line_ending unless str.end_with?(@line_ending) + nil + end + + private + + def set_flow_control(mode) + case mode + when FLOW_CONTROL_NONE + __set_flow_control(false, false) + when FLOW_CONTROL_RTS_CTS + __set_flow_control(true, true) + else + raise ArgumentError, "invalid flow control mode" + end + end +end diff --git a/mrbgems/hw-uart/ports/esp32/uart.c b/mrbgems/hw-uart/ports/esp32/uart.c new file mode 100644 index 0000000000..e751f8604b --- /dev/null +++ b/mrbgems/hw-uart/ports/esp32/uart.c @@ -0,0 +1,147 @@ +#include +#include +#include "freertos/FreeRTOS.h" +#include "driver/uart.h" +#include + +#define RX_TASK_BUF_SIZE 128 +#define QUEUE_LENGTH 20 +#define TASK_STACK_SIZE 4096 +#define TASK_PRIORITY 12 + +typedef struct { + int unit; + QueueHandle_t queue; + mrb_uart_ringbuf *rxbuf; +} uart_ctx; + +static uart_ctx ctx[UART_NUM_MAX]; + +static void +rx_task(void *arg) +{ + uart_ctx *c = (uart_ctx*)arg; + uart_event_t event; + uint8_t buf[RX_TASK_BUF_SIZE]; + + for (;;) { + if (xQueueReceive(c->queue, &event, portMAX_DELAY)) { + if (event.type == UART_DATA) { + size_t n = event.size > RX_TASK_BUF_SIZE ? RX_TASK_BUF_SIZE : event.size; + uart_read_bytes(c->unit, buf, n, portMAX_DELAY); + for (size_t i = 0; i < n; i++) { + mrb_uart_ringbuf_push(c->rxbuf, buf[i]); + } + } + } + } +} + +int +mrb_uart_unit_name_to_num(const char *name) +{ + if (strcmp(name, "ESP32_UART0") == 0) return UART_NUM_0; + if (strcmp(name, "ESP32_UART1") == 0) return UART_NUM_1; +#ifdef UART_NUM_2 + if (strcmp(name, "ESP32_UART2") == 0) return UART_NUM_2; +#endif + return MRB_UART_ERROR_UNIT; +} + +mrb_uart_status +mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin, + mrb_uart_ringbuf *rxbuf) +{ + if (unit < 0 || unit >= UART_NUM_MAX) return MRB_UART_ERROR_UNIT; + + uart_config_t cfg = { + .baud_rate = 9600, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_DEFAULT, + }; + + int bufsize = (rxbuf->mask + 1); + uart_driver_install(unit, bufsize, 0, QUEUE_LENGTH, &ctx[unit].queue, 0); + uart_param_config(unit, &cfg); + uart_set_pin(unit, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + + ctx[unit].unit = unit; + ctx[unit].rxbuf = rxbuf; + + char name[32]; + snprintf(name, sizeof(name), "uart_rx_%d", unit); + xTaskCreate(rx_task, name, TASK_STACK_SIZE, &ctx[unit], TASK_PRIORITY, NULL); + + return MRB_UART_OK; +} + +uint32_t +mrb_uart_set_baudrate(int unit, uint32_t baudrate) +{ + uart_set_baudrate(unit, baudrate); + return baudrate; +} + +void +mrb_uart_set_format(int unit, uint32_t data_bits, uint32_t stop_bits, + uint8_t parity) +{ + static const uart_word_length_t wl[] = { + UART_DATA_5_BITS, UART_DATA_6_BITS, UART_DATA_7_BITS, UART_DATA_8_BITS + }; + static const uart_stop_bits_t sb[] = { + UART_STOP_BITS_1, UART_STOP_BITS_2 + }; + static const uart_parity_t pr[] = { + UART_PARITY_DISABLE, UART_PARITY_EVEN, UART_PARITY_ODD + }; + if (data_bits >= 5 && data_bits <= 8) + uart_set_word_length(unit, wl[data_bits - 5]); + if (stop_bits >= 1 && stop_bits <= 2) + uart_set_stop_bits(unit, sb[stop_bits - 1]); + if (parity <= 2) + uart_set_parity(unit, pr[parity]); +} + +void +mrb_uart_set_flow_control(int unit, bool cts, bool rts) +{ + uart_hw_flowcontrol_t mode = UART_HW_FLOWCTRL_DISABLE; + if (cts && rts) mode = UART_HW_FLOWCTRL_CTS_RTS; + else if (cts) mode = UART_HW_FLOWCTRL_CTS; + else if (rts) mode = UART_HW_FLOWCTRL_RTS; + uart_set_hw_flow_ctrl(unit, mode, 122); +} + +void +mrb_uart_write(int unit, const uint8_t *src, size_t len) +{ + uart_write_bytes(unit, (const char*)src, len); +} + +void +mrb_uart_flush(int unit) +{ + uart_wait_tx_done(unit, 100); +} + +void +mrb_uart_send_break(int unit, uint32_t duration_ms) +{ + uart_write_bytes_with_break(unit, NULL, 0, duration_ms); +} + +void +mrb_uart_clear_rx(int unit) +{ + uart_flush_input(unit); +} + +void +mrb_uart_clear_tx(int unit) +{ + /* not supported on ESP-IDF */ +} diff --git a/mrbgems/hw-uart/ports/rp2040/uart.c b/mrbgems/hw-uart/ports/rp2040/uart.c new file mode 100644 index 0000000000..8c2ba17ea3 --- /dev/null +++ b/mrbgems/hw-uart/ports/rp2040/uart.c @@ -0,0 +1,136 @@ +#include +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/uart.h" +#include "hardware/irq.h" +#include + +#define UNIT_SELECT(u) \ + uart_inst_t *inst; \ + switch (u) { \ + case 0: inst = uart0; break; \ + case 1: inst = uart1; break; \ + default: return MRB_UART_ERROR_UNIT; \ + } + +/* void-returning variant for functions that can't return error */ +#define UNIT_SELECT_V(u) \ + uart_inst_t *inst; \ + switch (u) { \ + case 0: inst = uart0; break; \ + case 1: inst = uart1; break; \ + default: return; \ + } + +static mrb_uart_ringbuf *rx_bufs[2]; + +static void +on_uart0_rx(void) +{ + while (uart_is_readable(uart0)) { + mrb_uart_ringbuf_push(rx_bufs[0], uart_getc(uart0)); + } +} + +static void +on_uart1_rx(void) +{ + while (uart_is_readable(uart1)) { + mrb_uart_ringbuf_push(rx_bufs[1], uart_getc(uart1)); + } +} + +int +mrb_uart_unit_name_to_num(const char *name) +{ + if (strcmp(name, "RP2040_UART0") == 0) return 0; + if (strcmp(name, "RP2040_UART1") == 0) return 1; + return MRB_UART_ERROR_UNIT; +} + +mrb_uart_status +mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin, + mrb_uart_ringbuf *rxbuf) +{ + UNIT_SELECT(unit); + uart_init(inst, 9600); + + gpio_set_function(tx_pin, GPIO_FUNC_UART); + gpio_set_function(rx_pin, GPIO_FUNC_UART); + + rx_bufs[unit] = rxbuf; + + uint irq; + if (unit == 0) { + irq = UART0_IRQ; + irq_set_exclusive_handler(irq, on_uart0_rx); + } + else { + irq = UART1_IRQ; + irq_set_exclusive_handler(irq, on_uart1_rx); + } + irq_set_enabled(irq, true); + uart_set_irq_enables(inst, true, false); + + return MRB_UART_OK; +} + +uint32_t +mrb_uart_set_baudrate(int unit, uint32_t baudrate) +{ + UNIT_SELECT(unit); + return uart_set_baudrate(inst, baudrate); +} + +void +mrb_uart_set_format(int unit, uint32_t data_bits, uint32_t stop_bits, + uint8_t parity) +{ + UNIT_SELECT_V(unit); + uart_set_format(inst, data_bits, stop_bits, (uart_parity_t)parity); +} + +void +mrb_uart_set_flow_control(int unit, bool cts, bool rts) +{ + UNIT_SELECT_V(unit); + uart_set_hw_flow(inst, cts, rts); +} + +void +mrb_uart_write(int unit, const uint8_t *src, size_t len) +{ + UNIT_SELECT_V(unit); + uart_write_blocking(inst, src, len); +} + +void +mrb_uart_flush(int unit) +{ + UNIT_SELECT_V(unit); + uart_tx_wait_blocking(inst); +} + +void +mrb_uart_send_break(int unit, uint32_t duration_ms) +{ + UNIT_SELECT_V(unit); + uart_set_break(inst, true); + sleep_ms(duration_ms); + uart_set_break(inst, false); +} + +void +mrb_uart_clear_rx(int unit) +{ + UNIT_SELECT_V(unit); + while (uart_is_readable(inst)) { + uart_getc(inst); + } +} + +void +mrb_uart_clear_tx(int unit) +{ + /* not supported on RP2040 */ +} diff --git a/mrbgems/hw-uart/src/ringbuf.c b/mrbgems/hw-uart/src/ringbuf.c new file mode 100644 index 0000000000..a67e324e61 --- /dev/null +++ b/mrbgems/hw-uart/src/ringbuf.c @@ -0,0 +1,59 @@ +#include + +bool +mrb_uart_ringbuf_init(mrb_uart_ringbuf *rb, int size) +{ + /* size must be a power of two */ + if (size <= 0 || (size & (size - 1)) != 0) return false; + rb->head = 0; + rb->tail = 0; + rb->mask = size - 1; + return true; +} + +bool +mrb_uart_ringbuf_push(mrb_uart_ringbuf *rb, uint8_t ch) +{ + int next = (rb->head + 1) & rb->mask; + if (next == rb->tail) return false; /* full */ + rb->data[rb->head] = ch; + rb->head = next; + return true; +} + +int +mrb_uart_ringbuf_pop(mrb_uart_ringbuf *rb, uint8_t *dst, int len) +{ + int i; + for (i = 0; i < len; i++) { + if (rb->tail == rb->head) break; /* empty */ + dst[i] = rb->data[rb->tail]; + rb->tail = (rb->tail + 1) & rb->mask; + } + return i; +} + +int +mrb_uart_ringbuf_available(const mrb_uart_ringbuf *rb) +{ + return (rb->head - rb->tail) & rb->mask; +} + +void +mrb_uart_ringbuf_clear(mrb_uart_ringbuf *rb) +{ + rb->tail = rb->head; +} + +int +mrb_uart_ringbuf_search(const mrb_uart_ringbuf *rb, uint8_t ch) +{ + int pos = rb->tail; + int i = 0; + while (pos != rb->head) { + if (rb->data[pos] == ch) return i; + pos = (pos + 1) & rb->mask; + i++; + } + return -1; +} diff --git a/mrbgems/hw-uart/src/uart.c b/mrbgems/hw-uart/src/uart.c new file mode 100644 index 0000000000..139479a557 --- /dev/null +++ b/mrbgems/hw-uart/src/uart.c @@ -0,0 +1,236 @@ +#include +#include +#include +#include +#include +#include +#include + +#define E_IO_ERROR mrb_exc_get_id(mrb, MRB_SYM(IOError)) + +#define DEFAULT_RX_BUF_SIZE 256 + +static void +rxbuf_free(mrb_state *mrb, void *ptr) +{ + mrb_free(mrb, ptr); +} + +static const struct mrb_data_type rxbuf_type = { "UART", rxbuf_free }; + +/* UART#__open_rx_buffer(size) */ +static mrb_value +mrb_uart_m_open_rxbuf(mrb_state *mrb, mrb_value self) +{ + mrb_int size; + mrb_get_args(mrb, "i", &size); + if (size <= 0) size = DEFAULT_RX_BUF_SIZE; + + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_malloc(mrb, + sizeof(mrb_uart_ringbuf) + sizeof(uint8_t) * size); + if (!mrb_uart_ringbuf_init(rb, (int)size)) { + mrb_free(mrb, rb); + mrb_raise(mrb, E_ARGUMENT_ERROR, "rx_buffer_size must be a power of two"); + } + DATA_PTR(self) = rb; + DATA_TYPE(self) = &rxbuf_type; + return mrb_nil_value(); +} + +/* UART#__open_connection(unit_name, tx_pin, rx_pin) */ +static mrb_value +mrb_uart_m_open_conn(mrb_state *mrb, mrb_value self) +{ + const char *name; + mrb_int tx_pin, rx_pin; + mrb_get_args(mrb, "zii", &name, &tx_pin, &rx_pin); + + int num = mrb_uart_unit_name_to_num(name); + if (num < 0) { + mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown UART unit: %s", name); + } + + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + mrb_uart_status st = mrb_uart_init(num, (uint32_t)tx_pin, (uint32_t)rx_pin, rb); + if (st != MRB_UART_OK) { + mrb_raise(mrb, E_IO_ERROR, "UART init failed"); + } + return mrb_fixnum_value(num); +} + +/* UART#__set_baudrate(baud) */ +static mrb_value +mrb_uart_m_set_baudrate(mrb_state *mrb, mrb_value self) +{ + mrb_int baud; + mrb_get_args(mrb, "i", &baud); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + uint32_t actual = mrb_uart_set_baudrate((int)unit, (uint32_t)baud); + return mrb_fixnum_value(actual); +} + +/* UART#__set_format(data_bits, stop_bits, parity) */ +static mrb_value +mrb_uart_m_set_format(mrb_state *mrb, mrb_value self) +{ + mrb_int data_bits, stop_bits, parity; + mrb_get_args(mrb, "iii", &data_bits, &stop_bits, &parity); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_set_format((int)unit, (uint32_t)data_bits, (uint32_t)stop_bits, (uint8_t)parity); + return mrb_nil_value(); +} + +/* UART#__set_flow_control(cts, rts) */ +static mrb_value +mrb_uart_m_set_flow(mrb_state *mrb, mrb_value self) +{ + mrb_bool cts, rts; + mrb_get_args(mrb, "bb", &cts, &rts); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_set_flow_control((int)unit, cts, rts); + return mrb_nil_value(); +} + +/* UART#write(str) */ +static mrb_value +mrb_uart_m_write(mrb_state *mrb, mrb_value self) +{ + mrb_value str; + mrb_get_args(mrb, "S", &str); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + size_t len = RSTRING_LEN(str); + mrb_uart_write((int)unit, (const uint8_t*)RSTRING_PTR(str), len); + return mrb_fixnum_value(len); +} + +/* UART#read(len=nil) */ +static mrb_value +mrb_uart_m_read(mrb_state *mrb, mrb_value self) +{ + mrb_int len = -1; + mrb_get_args(mrb, "|i", &len); + + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + int avail = mrb_uart_ringbuf_available(rb); + if (avail == 0) return mrb_nil_value(); + + if (len >= 0) { + if (avail < len) return mrb_nil_value(); + avail = (int)len; + } + + uint8_t *buf = (uint8_t*)mrb_malloc(mrb, avail); + int n = mrb_uart_ringbuf_pop(rb, buf, avail); + mrb_value str = mrb_str_new(mrb, (const char*)buf, n); + mrb_free(mrb, buf); + return str; +} + +/* UART#readpartial(maxlen) */ +static mrb_value +mrb_uart_m_readpartial(mrb_state *mrb, mrb_value self) +{ + mrb_int maxlen; + mrb_get_args(mrb, "i", &maxlen); + + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + int avail = mrb_uart_ringbuf_available(rb); + if (avail == 0) return mrb_nil_value(); + if (avail > maxlen) avail = (int)maxlen; + + uint8_t *buf = (uint8_t*)mrb_malloc(mrb, avail); + int n = mrb_uart_ringbuf_pop(rb, buf, avail); + mrb_value str = mrb_str_new(mrb, (const char*)buf, n); + mrb_free(mrb, buf); + return str; +} + +/* UART#bytes_available */ +static mrb_value +mrb_uart_m_bytes_available(mrb_state *mrb, mrb_value self) +{ + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + return mrb_fixnum_value(mrb_uart_ringbuf_available(rb)); +} + +/* UART#gets */ +static mrb_value +mrb_uart_m_gets(mrb_state *mrb, mrb_value self) +{ + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + int pos = mrb_uart_ringbuf_search(rb, (uint8_t)'\n'); + if (pos < 0) return mrb_nil_value(); + int len = pos + 1; + uint8_t *buf = (uint8_t*)mrb_malloc(mrb, len); + mrb_uart_ringbuf_pop(rb, buf, len); + mrb_value str = mrb_str_new(mrb, (const char*)buf, len); + mrb_free(mrb, buf); + return str; +} + +/* UART#flush */ +static mrb_value +mrb_uart_m_flush(mrb_state *mrb, mrb_value self) +{ + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_flush((int)unit); + return self; +} + +/* UART#clear_tx_buffer */ +static mrb_value +mrb_uart_m_clear_tx(mrb_state *mrb, mrb_value self) +{ + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_clear_tx((int)unit); + return self; +} + +/* UART#clear_rx_buffer */ +static mrb_value +mrb_uart_m_clear_rx(mrb_state *mrb, mrb_value self) +{ + mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type); + mrb_uart_ringbuf_clear(rb); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_clear_rx((int)unit); + return self; +} + +/* UART#send_break(duration_ms=100) */ +static mrb_value +mrb_uart_m_send_break(mrb_state *mrb, mrb_value self) +{ + mrb_int ms = 100; + mrb_get_args(mrb, "|i", &ms); + mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num))); + mrb_uart_send_break((int)unit, (uint32_t)ms); + return self; +} + +void +mrb_hw_uart_gem_init(mrb_state *mrb) +{ + struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(UART), mrb->object_class); + MRB_SET_INSTANCE_TT(cls, MRB_TT_CDATA); + + mrb_define_method_id(mrb, cls, MRB_SYM(__open_rx_buffer), mrb_uart_m_open_rxbuf, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(__open_connection), mrb_uart_m_open_conn, MRB_ARGS_REQ(3)); + mrb_define_method_id(mrb, cls, MRB_SYM(__set_baudrate), mrb_uart_m_set_baudrate, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(__set_format), mrb_uart_m_set_format, MRB_ARGS_REQ(3)); + mrb_define_method_id(mrb, cls, MRB_SYM(__set_flow_control), mrb_uart_m_set_flow, MRB_ARGS_REQ(2)); + mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_uart_m_write, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_uart_m_read, MRB_ARGS_OPT(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(readpartial), mrb_uart_m_readpartial, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, cls, MRB_SYM(bytes_available), mrb_uart_m_bytes_available, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(gets), mrb_uart_m_gets, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(flush), mrb_uart_m_flush, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(clear_tx_buffer), mrb_uart_m_clear_tx, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(clear_rx_buffer), mrb_uart_m_clear_rx, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, cls, MRB_SYM(send_break), mrb_uart_m_send_break, MRB_ARGS_OPT(1)); +} + +void +mrb_hw_uart_gem_final(mrb_state *mrb) +{ +} diff --git a/mrbgems/mruby-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb index bcfdc1ddc2..35abf95b40 100644 --- a/mrbgems/mruby-array-ext/mrblib/array.rb +++ b/mrbgems/mruby-array-ext/mrblib/array.rb @@ -450,27 +450,7 @@ def dig(idx,*args) # a.permutation(0).to_a #=> [[]] # one permutation of length 0 # a.permutation(4).to_a #=> [] # no permutations of length 4 def permutation(n=self.size, &block) - n = n.__to_int - return to_enum(:permutation, n) unless block - size = self.size - if n == 0 - yield [] - elsif 0 < n && n <= size - i = 0 - while i 0 - ary = self[0...i] + self[i+1..-1] - ary.permutation(n-1) do |c| - yield result + c - end - else - yield result - end - i += 1 - end - end - self + __combination(:permutation, n, &block) end ## @@ -497,28 +477,7 @@ def permutation(n=self.size, &block) # a.combination(5).to_a #=> [] # no combinations of length 5 def combination(n, &block) - n = n.__to_int - return to_enum(:combination, n) unless block - size = self.size - if n == 0 - yield [] - elsif n == 1 - i = 0 - while i [[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]] def repeated_combination(n, &block) - raise TypeError, "no implicit conversion into Integer" unless 0 <=> n - return to_enum(:repeated_combination, n) unless block - __repeated_combination(n, false, &block) + __combination(:repeated_combination, n, &block) end ## @@ -667,36 +624,27 @@ def repeated_combination(n, &block) # a = [1, 2] # a.repeated_permutation(2).to_a #=> [[1,1],[1,2],[2,1],[2,2]] def repeated_permutation(n, &block) - n = n.__to_int - raise TypeError, "no implicit conversion into Integer" unless 0 <=> n - return to_enum(:repeated_permutation, n) unless block - __repeated_combination(n, true, &block) + __combination(:repeated_permutation, n, &block) end - def __repeated_combination(n, permutation, &block) - n = n.__to_int - case n + def __combination(mode, k, &block) + k = k.__to_int + return to_enum(mode, k) unless block + + case k when 0 yield [] when 1 - # Keep fast Ruby path for n=1 + # Keep fast Ruby path for k=1 i = 0 while i < self.size yield [self[i]] i += 1 end else - if n > 0 + if state = __combination_init(mode, k) # Use C iterator for complex cases - state = __combination_init(n, permutation) - while (indices = __combination_next(state)) - # Convert indices to elements in Ruby - tmp = [nil] * n - i = 0 - while i < n - tmp[i] = self[indices[i]] - i += 1 - end + while tmp = __combination_next(state) yield tmp end end diff --git a/mrbgems/mruby-array-ext/src/array.c b/mrbgems/mruby-array-ext/src/array.c index 70473c087a..62a731c363 100644 --- a/mrbgems/mruby-array-ext/src/array.c +++ b/mrbgems/mruby-array-ext/src/array.c @@ -32,10 +32,8 @@ typedef khash_t(ary_set) ary_set_t; /* Combination state structure for repeated_combination optimization */ struct mrb_combination_state { mrb_int *indices; - mrb_int n; - mrb_int array_size; - mrb_bool permutation; - mrb_bool finished; + mrb_int n, k; /* nPk, nCk */ + int mode; }; static void @@ -1533,6 +1531,13 @@ ary_deconstruct(mrb_state *mrb, mrb_value ary) return ary; } +enum { + comb_finished = 0, + comb_repeated_permutation = 1, + comb_repeated_combination = 2, + comb_permutation = 3, + comb_combination = 4 +}; /* * Internal method to initialize combination state. @@ -1541,33 +1546,74 @@ ary_deconstruct(mrb_state *mrb, mrb_value ary) static mrb_value ary_combination_init(mrb_state *mrb, mrb_value self) { - mrb_int n; - mrb_bool permutation; + mrb_int k; + mrb_sym mode_sym; - mrb_get_args(mrb, "ib", &n, &permutation); + mrb_get_args(mrb, "ni", &mode_sym, &k); #if MRB_INT_MAX > SIZE_MAX - if (n > SIZE_MAX) { + if (k > SIZE_MAX) { mrb_raise(mrb, E_ARGUMENT_ERROR, "number too large"); } #endif + if (k < 1 || RARRAY_LEN(self) < 1) { + return mrb_nil_value(); + } + + int mode; + switch (mode_sym) { + case MRB_SYM(repeated_permutation): + mode = comb_repeated_permutation; + break; + case MRB_SYM(repeated_combination): + mode = comb_repeated_combination; + break; + case MRB_SYM(permutation): + if (k > RARRAY_LEN(self)) { + return mrb_nil_value(); + } + mode = comb_permutation; + break; + case MRB_SYM(combination): + if (k > RARRAY_LEN(self)) { + return mrb_nil_value(); + } + mode = comb_combination; + break; + default: + mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong mode"); + } + struct RData *d; struct mrb_combination_state *state; Data_Make_Struct(mrb, mrb->object_class, struct mrb_combination_state, &mrb_combination_state_type, state, d); - state->n = n; - state->array_size = RARRAY_LEN(self); - state->permutation = permutation; - state->finished = (n <= 0 && n != 0); + state->k = k; + state->n = RARRAY_LEN(self); + state->mode = mode; + state->indices = (mrb_int*)mrb_calloc(mrb, k, sizeof(mrb_int)); - if (n > 0) { - state->indices = (mrb_int*)mrb_calloc(mrb, n, sizeof(mrb_int)); + if (mode == comb_permutation || mode == comb_combination) { + for (mrb_int i = 0; i < k; i++) { + state->indices[i] = i; + } } return mrb_obj_value(d); } +static void +adjust_next_permutation_index(struct mrb_combination_state *state, mrb_int i) +{ + for (mrb_int j = i - 1; j >= 0; j--) { + if (state->indices[i] == state->indices[j]) { + state->indices[i]++; + j = i; + } + } +} + /* * Internal method to get next combination as index array. * Returns array of indices or nil when iteration is complete. @@ -1575,68 +1621,87 @@ ary_combination_init(mrb_state *mrb, mrb_value self) static mrb_value ary_combination_next(mrb_state *mrb, mrb_value self) { - mrb_value state_obj; - mrb_get_args(mrb, "o", &state_obj); - struct mrb_combination_state *state; - - /* Validate state object type and get data */ - state = (struct mrb_combination_state*)mrb_data_check_and_get(mrb, state_obj, &mrb_combination_state_type); - if (!state) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid combination state"); - } + mrb_get_args(mrb, "d", &state, &mrb_combination_state_type); /* Check if iteration is complete */ - if (state->finished) return mrb_nil_value(); + if (state->mode == comb_finished) return mrb_nil_value(); /* Validate array hasn't been modified during iteration */ - if (RARRAY_LEN(self) != state->array_size) { + if (RARRAY_LEN(self) != state->n) { mrb_raise(mrb, E_RUNTIME_ERROR, "array modified during iteration"); } - /* Edge case: empty array */ - if (state->array_size == 0) { - state->finished = TRUE; - return mrb_nil_value(); - } - /* Validate current indices are still in bounds */ - for (mrb_int i = 0; i < state->n; i++) { - if (state->indices[i] >= state->array_size) { - state->finished = TRUE; + for (mrb_int i = 0; i < state->k; i++) { + if (state->indices[i] >= state->n) { + state->mode = comb_finished; + mrb_free(mrb, state->indices); + state->indices = NULL; return mrb_nil_value(); } } - /* Build current combination indices */ - mrb_value result = mrb_ary_new_capa(mrb, state->n); - for (mrb_int i = 0; i < state->n; i++) { - mrb_ary_push(mrb, result, mrb_fixnum_value(state->indices[i])); - } - - mrb_int pos = state->n - 1; - - while (pos >= 0) { - state->indices[pos]++; - if (state->indices[pos] < state->array_size) break; - pos--; + /* Build current combination */ + mrb_value result = mrb_ary_new_capa(mrb, state->k); + const mrb_value *p = RARRAY_PTR(self); + for (mrb_int i = 0; i < state->k; i++) { + mrb_ary_push(mrb, result, p[state->indices[i]]); } - if (pos < 0) { - state->finished = TRUE; - } - else { - /* Reset dependent indices */ - for (mrb_int i = pos + 1; i < state->n; i++) { - if (state->permutation) { - state->indices[i] = 0; + switch (state->mode) { + case comb_repeated_permutation: + case comb_repeated_combination: + for (mrb_int i = state->k - 1; i >= 0; i--) { + state->indices[i]++; + if (state->indices[i] < state->n) { + /* Reset dependent indices */ + mrb_int reset = (state->mode == comb_repeated_permutation) ? 0 : state->indices[i]; + for (i++; i < state->k; i++) { + state->indices[i] = reset; + } + return result; } - else { - state->indices[i] = state->indices[i - 1]; + } + break; + case comb_permutation: + for (mrb_int i = state->k - 1; i >= 0; i--) { + state->indices[i]++; + + // adjust so that it does not overlap with the leading index + adjust_next_permutation_index(state, i); + + if (state->indices[i] < state->n) { + // adjust all trailing indexes to complete the function + for (i++; i < state->k; i++) { + state->indices[i] = 0; + adjust_next_permutation_index(state, i); + } + return result; + } + } + break; + case comb_combination: + for (mrb_int i = state->k - 1; i >= 0; i--) { + state->indices[i]++; + + if (state->indices[i] <= state->n - state->k + i) { + // replace each overflowed indices with an index incremented by 1 from the previous one + for (i++; i < state->k; i++) { + state->indices[i] = state->indices[i - 1] + 1; + } + return result; } } + break; + default: // it probably won’t happen, but just in case + result = mrb_nil_value(); + break; } + state->mode = comb_finished; + mrb_free(mrb, state->indices); + state->indices = NULL; return result; } diff --git a/mrbgems/mruby-bigint/core/bigint.c b/mrbgems/mruby-bigint/core/bigint.c index 28c1fa678e..839798b2e0 100644 --- a/mrbgems/mruby-bigint/core/bigint.c +++ b/mrbgems/mruby-bigint/core/bigint.c @@ -50,11 +50,15 @@ typedef struct mpz_context { mpz_pool_t *pool; /* NULL for heap-only operations */ } mpz_ctx_t; -/* Convenience macros for context creation */ +/* Convenience macros for context creation. + * Uses positional aggregate initialization instead of a C99 compound + * literal with designated initializers, so the file compiles as C++ + * on legacy toolchains (pre-C++20). Member order must match the + * mpz_context struct declaration above. */ #define MPZ_CTX_INIT(mrb_ptr, ctx, pool_ptr) \ mpz_pool_t pool ## _storage = {{0}};\ mpz_pool_t *pool_ptr = &pool ## _storage;\ - mpz_ctx_t ctx ## _struct = ((mpz_ctx_t){.mrb = (mrb_ptr), .pool = (pool_ptr)}); \ + mpz_ctx_t ctx ## _struct = { (mrb_ptr), (pool_ptr) }; \ mpz_ctx_t *ctx = &(ctx ## _struct); /* Access macros for readability */ @@ -344,6 +348,14 @@ mpz_move(mpz_ctx_t *ctx, mpz_t *y, mpz_t *x) x->sz = 0; } +static inline void +mpz_swap(mpz_t *a, mpz_t *b) +{ + mpz_t tmp = *a; + *a = *b; + *b = tmp; +} + static size_t digits(mpz_t *x) { @@ -361,6 +373,8 @@ trim(mpz_t *x) while (x->sz && x->p[x->sz-1] == 0) { x->sz--; } + /* Maintain invariant: sz == 0 implies sn == 0 (zero is canonical). */ + if (x->sz == 0) x->sn = 0; } /* z = x + y, without regard for sign */ @@ -1236,7 +1250,8 @@ mpn_add_var(mp_limb *rp, const mp_limb *ap, size_t an, rp[i] = LOW(sum); carry = HIGH(sum); } - } else { + } + else { for (; i < bn; i++) { mp_dbl_limb sum = (mp_dbl_limb)bp[i] + carry; rp[i] = LOW(sum); @@ -1560,7 +1575,8 @@ mpz_mul_toom3(mpz_ctx_t *ctx, mp_limb *result, mpn_add(t2, t2, w_len, w0, w_len); mpn_add(t2, t2, w_len, winf, w_len); mpn_neg(t2, t2, w_len); - } else { + } + else { mpn_sub(t2, t2, w_len, w0, w_len); mpn_sub(t2, t2, w_len, winf, w_len); } @@ -3133,8 +3149,11 @@ mpz_mod(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mpz_t *y) return; } - /* Barrett reduction for moderate-sized moduli (>= 4 limbs where setup is worthwhile) */ - if (y->sz >= 4 && y->sz <= 16 && x->sz >= y->sz + 2) { + /* Barrett reduction for moderate-sized moduli (>= 4 limbs where setup is worthwhile). + * Barrett's precondition is x < 2^(2*bits(m)); inputs beyond ~2*m.sz limbs + * violate it and the algorithm silently truncates high limbs. Fall through + * to general division for those. */ + if (y->sz >= 4 && y->sz <= 16 && x->sz >= y->sz + 2 && x->sz <= 2 * y->sz) { mpz_t mu; mpz_init_temp(ctx, &mu, y->sz + 1); mpz_barrett_mu(ctx, &mu, y); @@ -4030,7 +4049,7 @@ mpz_get_str(mpz_ctx_t *ctx, char *s, mrb_int sz, mrb_int base, mpz_t *x) } // convert to character - for (mp_limb b=b2; b>=base; b/=(mp_limb)base) { + for (mp_limb b=b2; b>=(mp_limb)base; b/=(mp_limb)base) { char a0 = (char)(a % base); if (a0 < 10) a0 += '0'; else a0 += 'a' - 10; @@ -4817,7 +4836,11 @@ mpz_power_of_2_p(mpz_t *x) return (limb != 0) && ((limb & (limb - 1)) == 0); } -/* Binary GCD algorithm (Stein's algorithm) - faster than Euclidean GCD */ +/* Binary GCD (Stein's algorithm): factor out common powers of 2, + then iterate on odd operands with subtract + trailing-zero shift. + For heavily unbalanced pairs (one operand has at least two more + limbs than the other) a single Euclidean step via mpz_mod replaces + many Stein subtracts. */ static void mpz_gcd(mpz_ctx_t *ctx, mpz_t *gg, mpz_t *aa, mpz_t *bb) { @@ -4889,14 +4912,29 @@ mpz_gcd(mpz_ctx_t *ctx, mpz_t *gg, mpz_t *aa, mpz_t *bb) mpz_div_2exp(ctx, &a, &a, a_zeros); mpz_div_2exp(ctx, &b, &b, b_zeros); - /* Euclidean algorithm for multi-limb numbers */ + /* Stein main loop. Invariant: a and b are positive and odd. + Euclidean fallback when b has >=2 more limbs than a. */ while (!zero_p(&b)) { - mpz_t temp; - mpz_init_temp(ctx, &temp, a.sz); - mpz_mod(ctx, &temp, &a, &b); - mpz_move(ctx, &a, &b); - mpz_move(ctx, &b, &temp); - mpz_clear(ctx, &temp); + if (mpz_cmp(ctx, &a, &b) > 0) { + mpz_swap(&a, &b); + } + if (b.sz >= a.sz + 2) { + mpz_t temp; + mpz_init_temp(ctx, &temp, a.sz); + mpz_mod(ctx, &temp, &b, &a); + mpz_move(ctx, &b, &temp); + mpz_clear(ctx, &temp); + if (zero_p(&b)) break; + size_t bz = mpz_trailing_zeros(&b); + if (bz > 0) + mpz_div_2exp(ctx, &b, &b, bz); + } + else { + mpz_sub(ctx, &b, &b, &a); + if (zero_p(&b)) break; + size_t bz = mpz_trailing_zeros(&b); + mpz_div_2exp(ctx, &b, &b, bz); + } } mpz_mul_2exp(ctx, gg, &a, shift); mpz_clear(ctx, &a); @@ -5215,12 +5253,19 @@ mpz_powm_montgomery(mpz_ctx_t *ctx, mpz_t *result, mpz_init(ctx, &one_mont); mpz_montgomery_reduce(ctx, &one_mont, &R2, n, rho); - /* Convert base to Montgomery form: base_mont = base * R mod n = REDC(base * R^2) */ - mpz_t base_mont, temp; + /* Convert base to Montgomery form: base_mont = base * R mod n = REDC(base * R^2). + * REDC requires its input T to satisfy T < R*N. If `base` is not already + * reduced (e.g. base >= n), `base * R^2` can exceed R*N and REDC produces + * a wrong result. Pre-reduce base modulo n via mpz_mmod (the general + * division path) -- both operands are non-negative here so this is + * semantically equivalent to mpz_mod. */ + mpz_t base_mont, base_reduced, temp; mpz_init(ctx, &base_mont); + mpz_init(ctx, &base_reduced); mpz_init_temp(ctx, &temp, n->sz * 4); - mpz_mul(ctx, &temp, (mpz_t*)base, &R2); + mpz_mmod(ctx, &base_reduced, (mpz_t*)base, (mpz_t*)n); + mpz_mul(ctx, &temp, &base_reduced, &R2); mpz_montgomery_reduce(ctx, &base_mont, &temp, n, rho); /* Initialize accumulator to 1 in Montgomery form */ @@ -5252,6 +5297,7 @@ mpz_powm_montgomery(mpz_ctx_t *ctx, mpz_t *result, mpz_clear(ctx, &R2); mpz_clear(ctx, &one_mont); mpz_clear(ctx, &base_mont); + mpz_clear(ctx, &base_reduced); mpz_clear(ctx, &temp); mpz_clear(ctx, &acc); pool_restore(ctx, pool_state); diff --git a/mrbgems/mruby-bigint/mrbgem.rake b/mrbgems/mruby-bigint/mrbgem.rake index a1039bf57c..edd910b6e9 100644 --- a/mrbgems/mruby-bigint/mrbgem.rake +++ b/mrbgems/mruby-bigint/mrbgem.rake @@ -4,6 +4,8 @@ MRuby::Gem::Specification.new('mruby-bigint') do |spec| spec.summary = 'Integer class extension to multiple-precision' spec.build.defines << "MRB_USE_BIGINT" + spec.add_test_dependency('mruby-numeric-ext', :core => 'mruby-numeric-ext') + spec.build.libmruby_core_objs << Dir.glob(File.join(__dir__, "core/**/*.c")).map { |fn| objfile(fn.relative_path_from(__dir__).pathmap("#{spec.build_dir}/%X")) } diff --git a/mrbgems/mruby-bigint/test/bigint.rb b/mrbgems/mruby-bigint/test/bigint.rb index 07f6c783eb..4f8827edd6 100644 --- a/mrbgems/mruby-bigint/test/bigint.rb +++ b/mrbgems/mruby-bigint/test/bigint.rb @@ -150,8 +150,75 @@ # assert_equal(-1041439304, n.pow(n, -1234567890)) end +assert 'Bigint Integer#pow(e, m) - Montgomery path' do + # Regression: mpz_powm_montgomery() failed to pre-reduce base mod n, + # producing wrong results when base >= n. Also trim() must restore + # the canonical sn=0 when sz becomes 0, otherwise an inconsistent + # zero bignum (sn!=0, sz=0) propagates through the squaring loop. + m = (2**40) + 1 + assert_equal 1, (2**160).pow(2, m) + assert_equal 1, (2**320).pow(2, m) + assert_equal 8, ((2**160) + 1).pow(3, m) + m2 = (2**100) + 3 + assert_equal (3**500) % m2, (3**500).pow(1, m2) + assert_equal ((5**300) ** 7) % m2, (5**300).pow(7, m2) +end + +assert 'Bigint Integer#remainder large operand' do + # Regression: mpz_mod's Barrett path didn't enforce its precondition + # x < 2^(2*bits(m)), so it silently truncated high limbs when x was + # much larger than m^2, producing the wrong remainder. Integer#% + # took the udiv path and worked, but Integer#remainder went through + # mpz_mod and was broken. + m = (2**100) + 3 + assert_equal (3**500) % m, (3**500).remainder(m) + assert_equal (5**500) % ((2**150) + 1), (5**500).remainder((2**150) + 1) + assert_equal (2**400) % ((2**130) + 1), (2**400).remainder((2**130) + 1) +end + assert 'Bigint abs' do n = 1<<65 assert_equal 36893488147419103232, n.abs assert_equal 36893488147419103232, (-n).abs end + +assert 'Bigint gcd' do + # zero cases + assert_equal 0, 0.gcd(0) + n = 1 << 200 + assert_equal n, n.gcd(0) + assert_equal n, 0.gcd(n) + + # power-of-2 fast path + assert_equal 1 << 100, (1 << 200).gcd(1 << 100) + assert_equal 1 << 100, (1 << 100).gcd(1 << 200) + assert_equal 1 << 40, (10 ** 50).gcd(1 << 40) + + # negative operands: result is the positive GCD + a = 1 << 200 + b = 3 << 200 + assert_equal a, a.gcd(b) + assert_equal a, (-a).gcd(b) + assert_equal a, a.gcd(-b) + assert_equal a, (-a).gcd(-b) + + # balanced multi-limb with known common factor + fib1000 = (1..1000).inject([0, 1]) { |(x, y), _| [y, x + y] }[0] + common = fib1000 + k, m = 1_000_003, 1_000_033 # small coprime primes + assert_equal common, (common * k).gcd(common * m) + assert_equal common, (common * m).gcd(common * k) + + # unbalanced: small coprime vs large + big = common * k + assert_equal 1, big.gcd(m) + assert_equal 1, m.gcd(big) + + # Fibonacci neighbors are always coprime + f100 = (1..100).inject([0, 1]) { |(x, y), _| [y, x + y] }[0] + f101 = (1..101).inject([0, 1]) { |(x, y), _| [y, x + y] }[0] + assert_equal 1, f100.gcd(f101) + + # Euclidean fallback path: operand sizes differ by several limbs + assert_equal 7, (7 * (1 << 4000)).gcd(7 * 13) +end diff --git a/mrbgems/mruby-bin-config/mrbgem.rake b/mrbgems/mruby-bin-config/mrbgem.rake index 4ab90203f4..3ee4b0e4d1 100644 --- a/mrbgems/mruby-bin-config/mrbgem.rake +++ b/mrbgems/mruby-bin-config/mrbgem.rake @@ -28,7 +28,7 @@ MRuby::Gem::Specification.new('mruby-bin-config') do |spec| if iscross build.products << mruby_config_path else - build.bins << mruby_config + build.products << build.define_installer(mruby_config_path) end directory mruby_config_dir diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c b/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c index 942b9c4cdd..6eb4973f17 100644 --- a/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/apibreak.c @@ -239,6 +239,7 @@ mrb_debug_set_break_method(mrb_state *mrb, mrb_debug_context *dbg, const char *c set_method = mrdb_strdup(mrb, method_name); if (set_method == NULL) { mrb_free(mrb, set_class); + return MRB_DEBUG_NOBUF; } index = alloc_breakpoint(dbg, MRB_DEBUG_BPTYPE_METHOD); diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c b/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c index 6376daaef8..c78f3b6289 100644 --- a/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/apiprint.c @@ -34,7 +34,7 @@ mrdb_check_syntax(mrb_state *mrb, mrb_debug_context *dbg, const char *expr, size mrb_value mrb_debug_eval(mrb_state *mrb, mrb_debug_context *dbg, const char *expr, size_t len, mrb_bool *exc, int direct_eval) { - void (*tmp)(struct mrb_state*, const struct mrb_irep*, const mrb_code*, mrb_value*); + void (*tmp)(mrb_state*, const struct mrb_irep*, const mrb_code*, mrb_value*); mrb_value ruby_code; mrb_value s; mrb_value v; diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c b/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c index 511e0ce7d9..481dc4a353 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c @@ -28,6 +28,7 @@ #ifdef _WIN32 #include #define isatty(fd) _isatty(fd) +#define fileno(fd) _fileno(fd) #else #include #endif diff --git a/mrbgems/mruby-bin-mrb/README.md b/mrbgems/mruby-bin-mrb/README.md new file mode 100644 index 0000000000..7169b91833 --- /dev/null +++ b/mrbgems/mruby-bin-mrb/README.md @@ -0,0 +1,86 @@ +# mruby-bin-mrb + +`mrb` is a lightweight mruby runtime that executes only precompiled +RiteBinary (.mrb) files. Unlike the full `mruby` command, it does not +depend on `mruby-compiler`, resulting in a significantly smaller binary. + +This gem is intended for embedded deployments where Ruby scripts are +precompiled on a development machine and only the runtime is needed on +the target device. + +## Size comparison + +By excluding `mruby-compiler` (and gems that depend on it such as +`mruby-eval`, `mruby-binding`, and `mruby-bin-mirb`), the text segment +can be reduced by approximately 300KB or more, depending on the build +configuration. + +## Usage + +``` +mrb [switches] programfile.mrb [arguments] +``` + +### Options + +- `-d` - set debugging flags (set `$DEBUG` to true) +- `-r library` - load a library (.mrb) before executing your script +- `-v` - print version number, then run in verbose mode +- `--verbose` - run in verbose mode +- `--version` - print the version +- `--copyright` - print the copyright + +## Workflow + +```bash +# On the development machine (with full mruby + mrbc): +mrbc -o program.mrb program.rb + +# On the target device (with mrb only): +mrb program.mrb + +# Load a precompiled library before the main program: +mrb -r lib.mrb program.mrb + +# Pass arguments to the script: +mrb program.mrb arg1 arg2 +``` + +## Build configuration example + +To build a minimal mruby with only the `mrb` runtime: + +```ruby +# build_config/runtime.rb +MRuby::Build.new do |conf| + conf.toolchain + + # Use a gembox that does not pull in the compiler. + # For example, the default gembox does not require it. + conf.gembox 'default' + + # The runtime-only executor (no compiler dependency) + conf.gem :core => 'mruby-bin-mrb' + + # Do NOT include these (they require mruby-compiler): + # conf.gem :core => 'mruby-bin-mruby' + # conf.gem :core => 'mruby-bin-mirb' + # conf.gem :core => 'mruby-eval' + # conf.gem :core => 'mruby-binding' +end +``` + +## Differences from `mruby` command + +| Feature | `mruby` | `mrb` | +| ----------------------- | ------- | -------------------------- | +| Execute .rb files | yes | no | +| Execute .mrb files | yes | yes | +| `-e` inline code | yes | no | +| `-c` syntax check | yes | no | +| `-b` force binary mode | yes | not needed (always binary) | +| Requires mruby-compiler | yes | **no** | + +## License + +MIT License - see the mruby LICENSE file. diff --git a/mrbgems/mruby-bin-mrb/bintest/mrb.rb b/mrbgems/mruby-bin-mrb/bintest/mrb.rb new file mode 100644 index 0000000000..afd4d99877 --- /dev/null +++ b/mrbgems/mruby-bin-mrb/bintest/mrb.rb @@ -0,0 +1,73 @@ +require 'tempfile' +require 'open3' + +def assert_mrb(exp_out, exp_err, exp_success, args) + out, err, stat = Open3.capture3(*(cmd_list("mrb") + args)) + assert "assert_mrb" do + assert_operator(exp_out, :===, out, "standard output") + assert_operator(exp_err, :===, err, "standard error") + assert_equal(exp_success, stat.success?, "exit success?") + end +end + +assert('mrb can execute .mrb files') do + script = Tempfile.new(['test', '.rb']) + bin = Tempfile.new(['test', '.mrb']) + File.write(script.path, 'print "hello from mrb"') + system("#{cmd('mrbc')} -o #{bin.path} #{script.path}") + o = `#{cmd('mrb')} #{bin.path}`.strip + assert_equal 'hello from mrb', o +end + +assert('mrb $0 value') do + script = Tempfile.new(['test', '.rb']) + bin = Tempfile.new(['test', '.mrb']) + File.write(script.path, 'print $0') + system("#{cmd('mrbc')} -o #{bin.path} #{script.path}") + o = `#{cmd('mrb')} #{bin.path}`.strip + assert_equal bin.path, o +end + +assert('mrb ARGV value') do + script = Tempfile.new(['test', '.rb']) + bin = Tempfile.new(['test', '.mrb']) + File.write(script.path, 'p ARGV') + system("#{cmd('mrbc')} -o #{bin.path} #{script.path}") + o = `#{cmd('mrb')} #{bin.path} foo bar`.strip + assert_equal '["foo", "bar"]', o +end + +assert('mrb with no arguments prints error') do + assert_mrb("", /no program file given/, false, []) +end + +assert('mrb --version') do + assert_mrb(/\Amruby \d+\.\d+/, "", true, %w[--version]) +end + +assert('mrb -r option loads library') do + lib = Tempfile.new(['lib', '.rb']) + main = Tempfile.new(['main', '.rb']) + lib_mrb = Tempfile.new(['lib', '.mrb']) + main_mrb = Tempfile.new(['main', '.mrb']) + + File.write(lib.path, '$lib_loaded = true') + File.write(main.path, 'print $lib_loaded') + system("#{cmd('mrbc')} -o #{lib_mrb.path} #{lib.path}") + system("#{cmd('mrbc')} -o #{main_mrb.path} #{main.path}") + o = `#{cmd('mrb')} -r #{lib_mrb.path} #{main_mrb.path}`.strip + assert_equal 'true', o +end + +assert('mrb -d sets $DEBUG') do + script = Tempfile.new(['test', '.rb']) + bin = Tempfile.new(['test', '.mrb']) + File.write(script.path, 'print $DEBUG') + system("#{cmd('mrbc')} -o #{bin.path} #{script.path}") + o = `#{cmd('mrb')} -d #{bin.path}`.strip + assert_equal 'true', o +end + +assert('mrb nonexistent file') do + assert_mrb("", /Cannot open/, false, %w[nonexistent.mrb]) +end diff --git a/mrbgems/mruby-bin-mrb/mrbgem.rake b/mrbgems/mruby-bin-mrb/mrbgem.rake new file mode 100644 index 0000000000..20d6a85139 --- /dev/null +++ b/mrbgems/mruby-bin-mrb/mrbgem.rake @@ -0,0 +1,19 @@ +MRuby::Gem::Specification.new('mruby-bin-mrb') do |spec| + spec.license = 'MIT' + spec.author = 'mruby developers' + spec.summary = 'mruby runtime command (compiler-free)' + spec.bins = %w(mrb) + + # NOTE: Unlike mruby-bin-mruby, this gem does NOT depend on + # mruby-compiler. This makes it suitable for builds where the + # compiler is excluded to reduce binary size. + # + # To use this gem in your build_config.rb: + # + # MRuby::Build.new do |conf| + # conf.toolchain + # conf.gem :core => 'mruby-bin-mrb' + # # Do NOT include mruby-bin-mruby or mruby-compiler + # # unless other gems require them. + # end +end diff --git a/mrbgems/mruby-bin-mrb/tools/mrb/mrb.c b/mrbgems/mruby-bin-mrb/tools/mrb/mrb.c new file mode 100644 index 0000000000..670b66b703 --- /dev/null +++ b/mrbgems/mruby-bin-mrb/tools/mrb/mrb.c @@ -0,0 +1,311 @@ +/* +** mrb - mruby runtime executor (compiler-free) +** +** This is a lightweight alternative to the `mruby` command that only +** executes precompiled RiteBinary (.mrb) files. It does not depend on +** mruby-compiler, making it suitable for embedded deployments where +** binary size matters. +** +** Typical workflow: +** +** # On the development machine (with full mruby + compiler): +** mrbc -o program.mrb program.rb +** +** # On the target device (with mrb only, no compiler): +** mrb program.mrb +** +** By excluding mruby-compiler (and gems that depend on it such as +** mruby-eval, mruby-binding, mruby-bin-mirb), the resulting binary +** can be significantly smaller (~300KB+ savings on the text segment). +*/ + +#include + +#ifdef MRB_NO_STDIO +# error mruby-bin-mrb conflicts 'MRB_NO_STDIO' in your build configuration +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +# include +# include +#endif + +struct mrb_args { + FILE *rfp; + char *cmdline; + mrb_bool verbose : 1; + mrb_bool version : 1; + mrb_bool debug : 1; + int argc; + char **argv; + int libc; + char **libv; +}; + +static void +usage(const char *name) +{ + static const char *const usage_msg[] = { + "switches:", + "-d set debugging flags (set $DEBUG to true)", + "-r library load the library (.mrb) before executing your script", + "-v print version number, then run in verbose mode", + "--verbose run in verbose mode", + "--version print the version", + "--copyright print the copyright", + NULL + }; + const char *const *p = usage_msg; + + printf("Usage: %s [switches] programfile.mrb [arguments]\n", name); + while (*p) + printf(" %s\n", *p++); +} + +struct options { + int argc; + char **argv; + char *program; + char *opt; + char short_opt[2]; +}; + +static void +options_init(struct options *opts, int argc, char **argv) +{ + opts->argc = argc; + opts->argv = argv; + opts->program = *argv; + *opts->short_opt = 0; +} + +static const char * +options_opt(struct options *opts) +{ + /* concatenated short options (e.g. `-dv`) */ + if (*opts->short_opt && *++opts->opt) { + opts->short_opt[0] = *opts->opt; + opts->short_opt[1] = 0; + return opts->short_opt; + } + + while (++opts->argv, --opts->argc) { + opts->opt = *opts->argv; + + /* not start with `-` or just `-` */ + if (opts->opt[0] != '-' || !opts->opt[1]) return NULL; + + if (opts->opt[1] == '-') { + /* `--` */ + if (!opts->opt[2]) { + opts->argv++, opts->argc--; + return NULL; + } + /* long option */ + opts->opt += 2; + *opts->short_opt = 0; + return opts->opt; + } + else { + /* short option */ + opts->opt++; + opts->short_opt[0] = *opts->opt; + opts->short_opt[1] = 0; + return opts->short_opt; + } + } + return NULL; +} + +static const char * +options_arg(struct options *opts) +{ + if (*opts->short_opt && opts->opt[1]) { + /* concatenated short option and argument (e.g. `-rlibrary`) */ + *opts->short_opt = 0; + return opts->opt + 1; + } + --opts->argc, ++opts->argv; + return opts->argc ? *opts->argv : NULL; +} + +static char * +dup_arg_item(mrb_state *mrb, const char *item) +{ + size_t buflen = strlen(item) + 1; + char *buf = (char*)mrb_malloc(mrb, buflen); + memcpy(buf, item, buflen); + return buf; +} + +static int +parse_args(mrb_state *mrb, int argc, char **argv, struct mrb_args *args) +{ + static const struct mrb_args args_zero = { 0 }; + struct options opts[1]; + const char *opt, *item; + + *args = args_zero; + options_init(opts, argc, argv); + while ((opt = options_opt(opts))) { + if (strcmp(opt, "d") == 0) { + args->debug = TRUE; + } + else if (strcmp(opt, "h") == 0) { + usage(opts->program); + exit(EXIT_SUCCESS); + } + else if (strcmp(opt, "r") == 0) { + if ((item = options_arg(opts))) { + if (args->libc == 0) { + args->libv = (char**)mrb_malloc(mrb, sizeof(char*)); + } + else { + args->libv = (char**)mrb_realloc(mrb, args->libv, sizeof(char*) * (args->libc + 1)); + } + args->libv[args->libc++] = dup_arg_item(mrb, item); + } + else { + fprintf(stderr, "%s: No library specified for -r\n", opts->program); + return EXIT_FAILURE; + } + } + else if (strcmp(opt, "v") == 0) { + if (!args->verbose) { + mrb_show_version(mrb); + args->version = TRUE; + } + args->verbose = TRUE; + } + else if (strcmp(opt, "version") == 0) { + mrb_show_version(mrb); + exit(EXIT_SUCCESS); + } + else if (strcmp(opt, "verbose") == 0) { + args->verbose = TRUE; + } + else if (strcmp(opt, "copyright") == 0) { + mrb_show_copyright(mrb); + exit(EXIT_SUCCESS); + } + else { + fprintf(stderr, "%s: invalid option %s%s (-h will show valid options)\n", + opts->program, opt[1] ? "--" : "-", opt); + return EXIT_FAILURE; + } + } + + argc = opts->argc; argv = opts->argv; + if (*argv == NULL) { + if (args->version) exit(EXIT_SUCCESS); + fprintf(stderr, "%s: no program file given (only .mrb files are supported)\n", + opts->program); + return EXIT_FAILURE; + } + args->rfp = strcmp(argv[0], "-") == 0 ? + stdin : fopen(argv[0], "rb"); + if (args->rfp == NULL) { + fprintf(stderr, "%s: Cannot open program file: %s\n", opts->program, argv[0]); + return EXIT_FAILURE; + } + args->cmdline = argv[0]; + argc--; argv++; + +#if defined(_WIN32) + if (args->rfp == stdin) { + _setmode(_fileno(stdin), O_BINARY); + } +#endif + args->argv = (char **)mrb_realloc(mrb, args->argv, sizeof(char*) * (argc + 1)); + memcpy(args->argv, argv, (argc+1) * sizeof(char*)); + args->argc = argc; + + return EXIT_SUCCESS; +} + +static void +cleanup(mrb_state *mrb, struct mrb_args *args) +{ + if (args->rfp && args->rfp != stdin) + fclose(args->rfp); + mrb_free(mrb, args->argv); + if (args->libc) { + while (args->libc--) { + mrb_free(mrb, args->libv[args->libc]); + } + mrb_free(mrb, args->libv); + } + mrb_close(mrb); +} + +int +main(int argc, char **argv) +{ + mrb_state *mrb = mrb_open(); + int n = -1; + struct mrb_args args; + mrb_value ARGV; + mrb_value v; + + if (MRB_OPEN_FAILURE(mrb)) { + mrb_print_error(mrb); + mrb_close(mrb); + return EXIT_FAILURE; + } + + n = parse_args(mrb, argc, argv, &args); + if (n == EXIT_FAILURE || args.rfp == NULL) { + cleanup(mrb, &args); + return n; + } + + int ai = mrb_gc_arena_save(mrb); + ARGV = mrb_ary_new_capa(mrb, args.argc); + for (int i = 0; i < args.argc; i++) { + char* utf8 = mrb_utf8_from_locale(args.argv[i], -1); + if (utf8) { + mrb_ary_push(mrb, ARGV, mrb_str_new_cstr(mrb, utf8)); + mrb_utf8_free(utf8); + } + } + mrb_define_global_const(mrb, "ARGV", ARGV); + mrb_gv_set(mrb, mrb_intern_lit(mrb, "$DEBUG"), mrb_bool_value(args.debug)); + + /* Set $0 */ + const char *cmdline = args.cmdline ? args.cmdline : "-"; + mrb_gv_set(mrb, mrb_intern_lit(mrb, "$0"), mrb_str_new_cstr(mrb, cmdline)); + + /* Load libraries (.mrb only) */ + for (int i = 0; i < args.libc; i++) { + FILE *lfp = fopen(args.libv[i], "rb"); + if (lfp == NULL) { + fprintf(stderr, "%s: Cannot open library file: %s\n", cmdline, args.libv[i]); + cleanup(mrb, &args); + return EXIT_FAILURE; + } + mrb_load_irep_file(mrb, lfp); + fclose(lfp); + } + + /* Load and execute program (.mrb only) */ + v = mrb_load_irep_file(mrb, args.rfp); + + mrb_gc_arena_restore(mrb, ai); + if (mrb->exc) { + MRB_EXC_CHECK_EXIT(mrb, mrb->exc); + if (!mrb_undef_p(v)) { + mrb_print_error(mrb); + } + n = EXIT_FAILURE; + } + + cleanup(mrb, &args); + return n; +} diff --git a/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb b/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb index 90bbd123fd..4e3aea1685 100644 --- a/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb +++ b/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb @@ -28,3 +28,25 @@ assert_equal "#{a.path}:3:0: embedded document meets end of file", result.chomp assert_equal 1, $?.exitstatus end + +assert('debug info preserves line/filename across multiple inputs. #1316') do + # Verifying mrbc's debug info requires running the compiled output through + # the mruby binary; skip when bin-mruby isn't built. Kernel#puts only + # exists when mruby-io is loaded, so the script uses print. + skip "mruby command not built" unless File.exist?(cmd_bin("mruby")) + + a = Tempfile.new(['a', '.rb']) + b = Tempfile.new(['b', '.rb']) + out = Tempfile.new(['out', '.mrb']) + a.write("# line 1\n# line 2\nprint \"from a\"\n# line 4\nundefined_in_a\n") + a.flush + b.write("# b line 1\nprint \"from b\"\n") + b.flush + `#{cmd('mrbc')} -g -o #{out.path} #{a.path} #{b.path}` + assert_equal 0, $?.exitstatus + result = `#{cmd('mruby')} -b #{out.path} 2>&1` + # Error should point at a.rb line 5 (the `undefined_in_a` line), + # not b.rb or a different line within a.rb. + assert_include result, "#{a.path}:5:" + assert_not_include result, b.path +end diff --git a/mrbgems/mruby-bin-mruby/bintest/mruby.rb b/mrbgems/mruby-bin-mruby/bintest/mruby.rb index 27283d3d95..1310eb0bd0 100644 --- a/mrbgems/mruby-bin-mruby/bintest/mruby.rb +++ b/mrbgems/mruby-bin-mruby/bintest/mruby.rb @@ -176,3 +176,21 @@ def hoge assert_mruby("[1, -2, 3]\n5\n6\n", "", true, ["-r", crb.path, drb.path]) assert_mruby("[1, -2, 3]\n5\n6\n", "", true, ["-b", "-r", cmrb.path, dmrb.path]) end + +assert('String#split still works when mruby-regexp is loaded') do + # Only meaningful when mruby-regexp is built in; skip otherwise. + _, _, stat = Open3.capture3(*(cmd_list("mruby") + ["-e", "Regexp"])) + skip "mruby-regexp not loaded" unless stat.success? + + # The regexp-aware override in mruby-regexp/mrblib/string_regexp.rb used to + # replace the C-defined String#split, leaving its `return super if ...` + # fast paths with no method to delegate to (NoMethodError). Now the + # override delegates via `__split`, an alias of the original C method + # installed in mrb_mruby_regexp_gem_init before mrblib runs. + assert_mruby(%Q(["a", "b", "c"]\n), "", true, + ["-e", 'p "a,b,c".split(",")']) + assert_mruby(%Q(["abc", "abc", "abc"]\n), "", true, + ["-e", 'p "abc abc abc".split']) + assert_mruby(%Q(["hello", "world"]\n), "", true, + ["-e", 'p "hello world".split(/\s+/)']) +end diff --git a/mrbgems/mruby-compiler/core/codegen.c b/mrbgems/mruby-compiler/core/codegen.c index 48cae65691..f041a2c997 100644 --- a/mrbgems/mruby-compiler/core/codegen.c +++ b/mrbgems/mruby-compiler/core/codegen.c @@ -1457,19 +1457,13 @@ gen_addsub(codegen_scope *s, uint8_t op, uint16_t dst) struct mrb_insn_data data0 = mrb_decode_insn(mrb_prev_pc(s, data.addr)); mrb_int n0; if (addr_pc(s, data.addr) == s->lastlabel || !get_int_operand(s, &data0, &n0)) { - /* OP_ADDI/OP_SUBI takes upto 8bits */ - if (n > UINT8_MAX || n < -UINT8_MAX) goto normal; + /* Fold to OP_ADDI/OP_SUBI only for non-negative 8-bit n; flipping op + for negative n would change the method sent on user override (#2557). */ + if (n < 0 || n > UINT8_MAX) goto normal; rewind_pc(s); if (n == 0) return; - if (n > 0) { - if (op == OP_ADD) genop_2(s, OP_ADDI, dst, (uint16_t)n); - else genop_2(s, OP_SUBI, dst, (uint16_t)n); - } - else { /* n < 0 */ - n = -n; - if (op == OP_ADD) genop_2(s, OP_SUBI, dst, (uint16_t)n); - else genop_2(s, OP_ADDI, dst, (uint16_t)n); - } + if (op == OP_ADD) genop_2(s, OP_ADDI, dst, (uint16_t)n); + else genop_2(s, OP_SUBI, dst, (uint16_t)n); return; } if (op == OP_ADD) { @@ -3980,7 +3974,13 @@ codegen_if(codegen_scope *s, node *varnode, int val) mrb_sym sym_nil_p = MRB_SYM_Q(nil); if (call_n->method_name == sym_nil_p && callargs_empty(call_n->args)) { nil_p = TRUE; - codegen(s, call_n->receiver, VAL); + if (call_n->receiver) { + codegen(s, call_n->receiver, VAL); + } + else { + /* implicit receiver: bare `nil?` means `self.nil?` */ + gen_load_op1(s, OP_LOADSELF, VAL); + } } } @@ -4028,16 +4028,16 @@ codegen_if(codegen_scope *s, node *varnode, int val) } } +/* Shared codegen for while/until pre-tested loops. + is_until: FALSE for while (exit on false), TRUE for until (exit on true) */ static void -codegen_while(codegen_scope *s, node *varnode, int val) +codegen_loop(codegen_scope *s, node *varnode, int val, mrb_bool is_until) { - struct mrb_ast_while_node *while_n = while_node(varnode); - node *condition = while_n->condition; - node *body = while_n->body; + struct mrb_ast_while_node *loop_n = while_node(varnode); + node *condition = loop_n->condition; + node *body = loop_n->body; - /* Check for constant conditions first */ - if (true_always(condition)) { - /* while true - infinite loop, don't generate condition check */ + if (is_until ? false_always(condition) : true_always(condition)) { struct loopinfo *lp = loop_push(s, LOOP_NORMAL); if (!val) lp->reg = -1; lp->pc0 = new_label(s); @@ -4048,57 +4048,12 @@ codegen_while(codegen_scope *s, node *varnode, int val) loop_pop(s, val); return; } - if (false_always(condition)) { - /* while false - never execute, just return nil */ - if (val) { - gen_load_nil(s, 1); - } - return; - } - - struct loopinfo *lp = loop_push(s, LOOP_NORMAL); - uint32_t pos; - - if (!val) lp->reg = -1; - lp->pc0 = new_label(s); - codegen(s, condition, VAL); - pop(); - pos = genjmp2_0(s, OP_JMPNOT, cursp(), NOVAL); - lp->pc1 = new_label(s); - genop_0(s, OP_NOP); /* for redo */ - codegen(s, body, NOVAL); - genjmp(s, OP_JMP, lp->pc0); - dispatch(s, pos); - loop_pop(s, val); -} - -static void -codegen_until(codegen_scope *s, node *varnode, int val) -{ - struct mrb_ast_until_node *until_n = until_node(varnode); - node *condition = until_n->condition; - node *body = until_n->body; - - /* Check for constant conditions first */ - if (true_always(condition)) { - /* until true - never execute, just return nil */ + if (is_until ? true_always(condition) : false_always(condition)) { if (val) { gen_load_nil(s, 1); } return; } - if (false_always(condition)) { - /* until false - infinite loop, don't generate condition check */ - struct loopinfo *lp = loop_push(s, LOOP_NORMAL); - if (!val) lp->reg = -1; - lp->pc0 = new_label(s); - lp->pc1 = new_label(s); - genop_0(s, OP_NOP); /* for redo */ - codegen(s, body, NOVAL); - genjmp(s, OP_JMP, lp->pc0); - loop_pop(s, val); - return; - } struct loopinfo *lp = loop_push(s, LOOP_NORMAL); uint32_t pos; @@ -4107,79 +4062,32 @@ codegen_until(codegen_scope *s, node *varnode, int val) lp->pc0 = new_label(s); codegen(s, condition, VAL); pop(); - pos = genjmp2_0(s, OP_JMPIF, cursp(), NOVAL); - lp->pc1 = new_label(s); - genop_0(s, OP_NOP); /* for redo */ - codegen(s, body, NOVAL); - genjmp(s, OP_JMP, lp->pc0); - dispatch(s, pos); - loop_pop(s, val); -} - -static void -codegen_while_mod(codegen_scope *s, node *varnode, int val) -{ - struct mrb_ast_while_node *while_n = while_node(varnode); - node *condition = while_n->condition; - node *body = while_n->body; - - /* Handle special constant cases for post-tested loops */ - if (false_always(condition)) { - /* begin...end while false - execute once then exit */ - codegen(s, body, val); - if (val) push(); - return; - } - if (true_always(condition)) { - /* begin...end while true - infinite loop after first execution */ - struct loopinfo *lp = loop_push(s, LOOP_NORMAL); - if (!val) lp->reg = -1; - - uint32_t pos0 = genjmp_0(s, OP_JMP); - lp->pc0 = new_label(s); - lp->pc1 = new_label(s); - genop_0(s, OP_NOP); /* for redo */ - dispatch(s, pos0); - codegen(s, body, NOVAL); - genjmp(s, OP_JMP, lp->pc0); - loop_pop(s, val); - return; - } - - /* Normal post-tested while loop */ - struct loopinfo *lp = loop_push(s, LOOP_NORMAL); - if (!val) lp->reg = -1; - - uint32_t pos0 = genjmp_0(s, OP_JMP); - lp->pc0 = new_label(s); - codegen(s, condition, VAL); - pop(); - uint32_t pos = genjmp2_0(s, OP_JMPNOT, cursp(), NOVAL); + pos = genjmp2_0(s, is_until ? OP_JMPIF : OP_JMPNOT, cursp(), NOVAL); lp->pc1 = new_label(s); genop_0(s, OP_NOP); /* for redo */ - dispatch(s, pos0); codegen(s, body, NOVAL); genjmp(s, OP_JMP, lp->pc0); dispatch(s, pos); loop_pop(s, val); } +/* Shared codegen for while/until post-tested (modifier) loops. + is_until: FALSE for while (exit on false), TRUE for until (exit on true) */ static void -codegen_until_mod(codegen_scope *s, node *varnode, int val) +codegen_loop_mod(codegen_scope *s, node *varnode, int val, mrb_bool is_until) { - struct mrb_ast_until_node *until_n = until_node(varnode); - node *condition = until_n->condition; - node *body = until_n->body; + struct mrb_ast_while_node *loop_n = while_node(varnode); + node *condition = loop_n->condition; + node *body = loop_n->body; - /* Handle special constant cases for post-tested loops */ - if (true_always(condition)) { - /* begin...end until true - execute once then exit */ + if (is_until ? true_always(condition) : false_always(condition)) { + /* execute body once then exit */ codegen(s, body, val); if (val) push(); return; } - if (false_always(condition)) { - /* begin...end until false - infinite loop after first execution */ + if (is_until ? false_always(condition) : true_always(condition)) { + /* infinite loop after first execution */ struct loopinfo *lp = loop_push(s, LOOP_NORMAL); if (!val) lp->reg = -1; @@ -4194,7 +4102,6 @@ codegen_until_mod(codegen_scope *s, node *varnode, int val) return; } - /* Normal post-tested until loop */ struct loopinfo *lp = loop_push(s, LOOP_NORMAL); if (!val) lp->reg = -1; @@ -4202,7 +4109,7 @@ codegen_until_mod(codegen_scope *s, node *varnode, int val) lp->pc0 = new_label(s); codegen(s, condition, VAL); pop(); - uint32_t pos = genjmp2_0(s, OP_JMPIF, cursp(), NOVAL); + uint32_t pos = genjmp2_0(s, is_until ? OP_JMPIF : OP_JMPNOT, cursp(), NOVAL); lp->pc1 = new_label(s); genop_0(s, OP_NOP); /* for redo */ dispatch(s, pos0); @@ -4384,6 +4291,22 @@ codegen_case(codegen_scope *s, node *varnode, int val) */ static void codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos, int known_array_len); +/* Return the static element count of an array literal AST node, or -1 if any + * element is a splat (whose runtime length is unknown). + */ +static int +array_literal_known_len(node *value) +{ + if (node_type(value) != NODE_ARRAY) return -1; + struct mrb_ast_array_node *arr = array_node(value); + int len = 0; + for (node *elem = arr->elements; elem; elem = elem->cdr) { + if (is_splat_node(elem->car)) return -1; + len++; + } + return len; +} + /* Pattern matching case/in expression */ static void codegen_case_match(codegen_scope *s, node *varnode, int val) @@ -4397,13 +4320,7 @@ codegen_case_match(codegen_scope *s, node *varnode, int val) uint32_t tmp; /* Check if value is an array literal - allows optimizations in pattern matching */ - int known_array_len = -1; - if (node_type(value) == NODE_ARRAY) { - struct mrb_ast_array_node *arr = array_node(value); - node *elem; - known_array_len = 0; - for (elem = arr->elements; elem; elem = elem->cdr) known_array_len++; - } + int known_array_len = array_literal_known_len(value); /* Generate code for the case value */ codegen(s, value, VAL); @@ -5746,7 +5663,7 @@ codegen_str(codegen_scope *s, node *varnode, int val) } static void -codegen_dot2(codegen_scope *s, node *varnode, int val) +codegen_range(codegen_scope *s, node *varnode, int val, mrb_bool exclusive) { node *left = dot2_node(varnode)->left; node *right = dot2_node(varnode)->right; @@ -5755,21 +5672,7 @@ codegen_dot2(codegen_scope *s, node *varnode, int val) codegen(s, right, val); if (!val) return; pop(); pop(); - genop_1(s, OP_RANGE_INC, cursp()); - push(); -} - -static void -codegen_dot3(codegen_scope *s, node *varnode, int val) -{ - node *left = dot3_node(varnode)->left; - node *right = dot3_node(varnode)->right; - - codegen(s, left, val); - codegen(s, right, val); - if (!val) return; - pop(); pop(); - genop_1(s, OP_RANGE_EXC, cursp()); + genop_1(s, exclusive ? OP_RANGE_EXC : OP_RANGE_INC, cursp()); push(); } @@ -6607,11 +6510,11 @@ codegen(codegen_scope *s, node *tree, int val) break; case NODE_WHILE: - codegen_while(s, tree, val); + codegen_loop(s, tree, val, FALSE); break; case NODE_UNTIL: - codegen_until(s, tree, val); + codegen_loop(s, tree, val, TRUE); break; case NODE_FOR: @@ -6665,16 +6568,15 @@ codegen(codegen_scope *s, node *tree, int val) /* Optimize: array literal => array pattern with matching sizes */ if (node_type(mp->value) == NODE_ARRAY && node_type(mp->pattern) == NODE_PAT_ARRAY) { - struct mrb_ast_array_node *arr = array_node(mp->value); struct mrb_ast_pat_array_node *pat = pat_array_node(mp->pattern); /* Only optimize for exact match (no rest, no post) */ if (pat->rest == 0 && pat->post == NULL) { - /* Count array elements and pattern pre elements */ - int arr_len = 0, pat_len = 0; + /* Count array elements (bail if splat present) and pattern pre elements */ + int arr_len = array_literal_known_len(mp->value); + int pat_len = 0; node *e; - for (e = arr->elements; e; e = e->cdr) arr_len++; for (e = pat->pre; e; e = e->cdr) pat_len++; - if (arr_len == pat_len) { + if (arr_len >= 0 && arr_len == pat_len) { /* Sizes match - skip deconstruct and size check */ int arr_reg = cursp(); int i = 0; @@ -6713,12 +6615,7 @@ codegen(codegen_scope *s, node *tree, int val) head = cursp(); /* Check if value is array literal for optimization */ - if (node_type(mp->value) == NODE_ARRAY) { - struct mrb_ast_array_node *arr = array_node(mp->value); - node *elem; - known_array_len = 0; - for (elem = arr->elements; elem; elem = elem->cdr) known_array_len++; - } + known_array_len = array_literal_known_len(mp->value); /* Evaluate the value */ codegen(s, mp->value, VAL); @@ -6869,11 +6766,11 @@ codegen(codegen_scope *s, node *tree, int val) break; case NODE_DOT2: - codegen_dot2(s, tree, val); + codegen_range(s, tree, val, FALSE); break; case NODE_DOT3: - codegen_dot3(s, tree, val); + codegen_range(s, tree, val, TRUE); break; case NODE_FLOAT: @@ -6925,11 +6822,11 @@ codegen(codegen_scope *s, node *tree, int val) break; case NODE_WHILE_MOD: - codegen_while_mod(s, tree, val); + codegen_loop_mod(s, tree, val, FALSE); break; case NODE_UNTIL_MOD: - codegen_until_mod(s, tree, val); + codegen_loop_mod(s, tree, val, TRUE); break; case NODE_XSTR: diff --git a/mrbgems/mruby-compiler/core/parse.y b/mrbgems/mruby-compiler/core/parse.y index 74727feefe..a10a6df9cc 100644 --- a/mrbgems/mruby-compiler/core/parse.y +++ b/mrbgems/mruby-compiler/core/parse.y @@ -151,9 +151,13 @@ init_var_header(struct mrb_ast_var_header *header, parser_state *p, enum node_ty header->filename_index = p->current_filename_index; header->node_type = (uint8_t)type; - /* Handle file boundary edge case */ + /* Handle file boundary edge case: this node is reduced from a token that + was buffered by bison lookahead before partial_hook switched the file, + so attribute it to the previous file at its last known lineno rather + than the new file at lineno=0. */ if (p->lineno == 0 && p->current_filename_index > 0) { header->filename_index--; + header->lineno = p->prev_file_lineno; } } @@ -3983,9 +3987,9 @@ p_value : p_var { $$ = new_pat_value(p, $1); } - | tSTRING + | string { - $$ = new_pat_value(p, new_str(p, list1($1))); + $$ = new_pat_value(p, new_str(p, $1)); } | keyword_nil { @@ -7658,6 +7662,10 @@ mrb_parser_set_filename(struct mrb_parser_state *p, const char *f) sym = mrb_intern_cstr(p->mrb, f); p->filename_sym = sym; + /* Save current lineno so that AST nodes produced from a bison lookahead + across the file boundary (in partial_hook) can recover the correct + line in init_var_header instead of recording lineno=0. */ + p->prev_file_lineno = p->lineno; p->lineno = (p->filename_table_length > 0)? 0 : 1; for (i = 0; i < p->filename_table_length; i++) { @@ -8690,7 +8698,8 @@ dump_node(mrb_state *mrb, node *tree, int offset) node *item = list->car; if (item->car == 0 && item->cdr == 0) { /* Skip separator (0 . 0) */ - } else if (item->car && item->cdr) { + } + else if (item->car && item->cdr) { /* String item: (len . str) */ dump_prefix(offset+1, lineno); int len = node_to_int(item->car); diff --git a/mrbgems/mruby-compiler/core/y.tab.c b/mrbgems/mruby-compiler/core/y.tab.c index 098cad5e3f..e847aff084 100644 --- a/mrbgems/mruby-compiler/core/y.tab.c +++ b/mrbgems/mruby-compiler/core/y.tab.c @@ -213,9 +213,13 @@ init_var_header(struct mrb_ast_var_header *header, parser_state *p, enum node_ty header->filename_index = p->current_filename_index; header->node_type = (uint8_t)type; - /* Handle file boundary edge case */ + /* Handle file boundary edge case: this node is reduced from a token that + was buffered by bison lookahead before partial_hook switched the file, + so attribute it to the previous file at its last known lineno rather + than the new file at lineno=0. */ if (p->lineno == 0 && p->current_filename_index > 0) { header->filename_index--; + header->lineno = p->prev_file_lineno; } } @@ -2057,7 +2061,7 @@ prohibit_literals(parser_state *p, node *n) /* xxx ----------------------------- */ -#line 2061 "mrbgems/mruby-compiler/core/y.tab.c" +#line 2065 "mrbgems/mruby-compiler/core/y.tab.c" # ifndef YY_CAST # ifdef __cplusplus @@ -2228,7 +2232,7 @@ extern int yydebug; #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED union YYSTYPE { -#line 2004 "mrbgems/mruby-compiler/core/parse.y" +#line 2008 "mrbgems/mruby-compiler/core/parse.y" node *nd; mrb_sym id; @@ -2236,7 +2240,7 @@ union YYSTYPE stack_type stack; const struct vtable *vars; -#line 2240 "mrbgems/mruby-compiler/core/y.tab.c" +#line 2244 "mrbgems/mruby-compiler/core/y.tab.c" }; typedef union YYSTYPE YYSTYPE; @@ -2948,7 +2952,7 @@ union yyalloc /* YYFINAL -- State number of the termination state. */ #define YYFINAL 106 /* YYLAST -- Last index in YYTABLE. */ -#define YYLAST 13757 +#define YYLAST 13819 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 149 @@ -3018,76 +3022,76 @@ static const yytype_uint8 yytranslate[] = /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ static const yytype_int16 yyrline[] = { - 0, 2178, 2178, 2178, 2188, 2194, 2198, 2202, 2206, 2212, - 2214, 2213, 2227, 2253, 2259, 2263, 2267, 2271, 2277, 2277, - 2281, 2285, 2289, 2293, 2302, 2311, 2315, 2320, 2321, 2325, - 2329, 2333, 2337, 2340, 2344, 2348, 2352, 2356, 2360, 2365, - 2369, 2378, 2387, 2396, 2405, 2412, 2413, 2417, 2420, 2421, - 2425, 2429, 2433, 2437, 2437, 2443, 2443, 2449, 2452, 2462, - 2461, 2476, 2485, 2486, 2489, 2490, 2497, 2496, 2511, 2515, - 2520, 2524, 2529, 2533, 2538, 2542, 2546, 2550, 2554, 2560, - 2564, 2570, 2571, 2577, 2581, 2585, 2589, 2593, 2597, 2601, - 2605, 2609, 2613, 2619, 2620, 2626, 2630, 2636, 2640, 2646, - 2650, 2654, 2658, 2662, 2666, 2672, 2678, 2685, 2689, 2693, - 2697, 2701, 2705, 2711, 2717, 2722, 2728, 2732, 2735, 2739, - 2743, 2750, 2751, 2752, 2753, 2758, 2765, 2766, 2769, 2773, - 2773, 2779, 2780, 2781, 2782, 2783, 2784, 2785, 2786, 2787, - 2788, 2789, 2790, 2791, 2792, 2793, 2794, 2795, 2796, 2797, - 2798, 2799, 2800, 2801, 2802, 2803, 2804, 2805, 2806, 2807, - 2808, 2811, 2811, 2811, 2812, 2812, 2813, 2813, 2813, 2814, - 2814, 2814, 2814, 2815, 2815, 2815, 2816, 2816, 2816, 2817, - 2817, 2817, 2817, 2818, 2818, 2818, 2818, 2819, 2819, 2819, - 2819, 2820, 2820, 2820, 2820, 2821, 2821, 2821, 2821, 2822, - 2822, 2825, 2829, 2833, 2837, 2841, 2845, 2849, 2854, 2859, - 2864, 2868, 2872, 2876, 2880, 2884, 2888, 2892, 2896, 2900, - 2904, 2908, 2912, 2916, 2920, 2924, 2928, 2932, 2936, 2940, - 2944, 2948, 2952, 2956, 2960, 2964, 2968, 2972, 2976, 2980, - 2984, 2988, 2992, 2996, 3000, 3004, 3008, 3012, 3021, 3030, - 3039, 3048, 3054, 3055, 3059, 3063, 3069, 3073, 3080, 3084, - 3093, 3110, 3111, 3114, 3115, 3116, 3120, 3124, 3130, 3135, - 3139, 3143, 3147, 3153, 3153, 3164, 3168, 3174, 3178, 3184, - 3187, 3192, 3196, 3200, 3205, 3209, 3215, 3220, 3224, 3230, - 3231, 3235, 3239, 3240, 3241, 3242, 3243, 3248, 3247, 3259, - 3263, 3258, 3268, 3268, 3272, 3276, 3280, 3284, 3288, 3292, - 3296, 3300, 3304, 3308, 3312, 3313, 3319, 3326, 3318, 3339, - 3347, 3355, 3355, 3355, 3362, 3362, 3362, 3369, 3375, 3379, - 3388, 3397, 3407, 3409, 3406, 3418, 3416, 3434, 3439, 3432, - 3456, 3454, 3470, 3480, 3491, 3495, 3499, 3503, 3509, 3516, - 3517, 3518, 3521, 3522, 3525, 3526, 3534, 3535, 3541, 3545, - 3548, 3552, 3556, 3560, 3565, 3569, 3573, 3577, 3583, 3582, - 3592, 3596, 3600, 3604, 3610, 3615, 3620, 3624, 3628, 3632, - 3636, 3640, 3644, 3648, 3652, 3656, 3660, 3664, 3668, 3672, - 3676, 3682, 3687, 3694, 3694, 3698, 3703, 3709, 3713, 3719, - 3720, 3723, 3728, 3731, 3735, 3741, 3745, 3752, 3751, 3768, - 3773, 3777, 3782, 3789, 3793, 3797, 3801, 3805, 3809, 3813, - 3817, 3821, 3828, 3827, 3842, 3841, 3857, 3865, 3874, 3879, - 3883, 3883, 3888, 3888, 3893, 3893, 3903, 3904, 3908, 3912, - 3916, 3920, 3924, 3929, 3934, 3942, 3946, 3953, 3957, 3963, - 3964, 3970, 3971, 3977, 3978, 3982, 3986, 3990, 3994, 3998, - 4002, 4006, 4007, 4008, 4015, 4019, 4026, 4031, 4036, 4041, - 4046, 4051, 4059, 4063, 4070, 4074, 4082, 4086, 4090, 4097, - 4101, 4108, 4112, 4116, 4123, 4127, 4136, 4141, 4149, 4153, - 4158, 4165, 4171, 4178, 4181, 4185, 4186, 4189, 4193, 4196, - 4200, 4203, 4204, 4205, 4206, 4209, 4210, 4216, 4221, 4226, - 4231, 4237, 4238, 4244, 4250, 4249, 4261, 4265, 4271, 4275, - 4281, 4290, 4301, 4304, 4305, 4308, 4314, 4320, 4321, 4324, - 4331, 4330, 4345, 4349, 4357, 4361, 4373, 4380, 4387, 4388, - 4389, 4390, 4391, 4395, 4401, 4405, 4413, 4414, 4415, 4419, - 4425, 4429, 4433, 4437, 4441, 4447, 4451, 4457, 4461, 4465, - 4469, 4473, 4477, 4481, 4489, 4496, 4502, 4503, 4507, 4511, - 4510, 4527, 4528, 4531, 4537, 4541, 4547, 4548, 4552, 4556, - 4562, 4568, 4576, 4582, 4589, 4595, 4602, 4606, 4612, 4616, - 4622, 4623, 4626, 4630, 4636, 4640, 4644, 4648, 4654, 4658, - 4663, 4668, 4672, 4676, 4680, 4684, 4688, 4692, 4696, 4700, - 4704, 4708, 4712, 4716, 4720, 4725, 4731, 4736, 4741, 4746, - 4751, 4758, 4762, 4769, 4774, 4773, 4785, 4789, 4795, 4803, - 4811, 4819, 4823, 4829, 4833, 4839, 4840, 4843, 4848, 4855, - 4856, 4859, 4863, 4867, 4873, 4877, 4881, 4887, 4893, 4893, - 4900, 4901, 4907, 4911, 4917, 4923, 4928, 4932, 4937, 4942, - 4958, 4963, 4969, 4970, 4971, 4974, 4975, 4976, 4977, 4980, - 4981, 4982, 4985, 4986, 4989, 4993, 4999, 5000, 5006, 5007, - 5010, 5011, 5014, 5017, 5018, 5019, 5022, 5023, 5026, 5031, - 5034, 5035, 5039 + 0, 2182, 2182, 2182, 2192, 2198, 2202, 2206, 2210, 2216, + 2218, 2217, 2231, 2257, 2263, 2267, 2271, 2275, 2281, 2281, + 2285, 2289, 2293, 2297, 2306, 2315, 2319, 2324, 2325, 2329, + 2333, 2337, 2341, 2344, 2348, 2352, 2356, 2360, 2364, 2369, + 2373, 2382, 2391, 2400, 2409, 2416, 2417, 2421, 2424, 2425, + 2429, 2433, 2437, 2441, 2441, 2447, 2447, 2453, 2456, 2466, + 2465, 2480, 2489, 2490, 2493, 2494, 2501, 2500, 2515, 2519, + 2524, 2528, 2533, 2537, 2542, 2546, 2550, 2554, 2558, 2564, + 2568, 2574, 2575, 2581, 2585, 2589, 2593, 2597, 2601, 2605, + 2609, 2613, 2617, 2623, 2624, 2630, 2634, 2640, 2644, 2650, + 2654, 2658, 2662, 2666, 2670, 2676, 2682, 2689, 2693, 2697, + 2701, 2705, 2709, 2715, 2721, 2726, 2732, 2736, 2739, 2743, + 2747, 2754, 2755, 2756, 2757, 2762, 2769, 2770, 2773, 2777, + 2777, 2783, 2784, 2785, 2786, 2787, 2788, 2789, 2790, 2791, + 2792, 2793, 2794, 2795, 2796, 2797, 2798, 2799, 2800, 2801, + 2802, 2803, 2804, 2805, 2806, 2807, 2808, 2809, 2810, 2811, + 2812, 2815, 2815, 2815, 2816, 2816, 2817, 2817, 2817, 2818, + 2818, 2818, 2818, 2819, 2819, 2819, 2820, 2820, 2820, 2821, + 2821, 2821, 2821, 2822, 2822, 2822, 2822, 2823, 2823, 2823, + 2823, 2824, 2824, 2824, 2824, 2825, 2825, 2825, 2825, 2826, + 2826, 2829, 2833, 2837, 2841, 2845, 2849, 2853, 2858, 2863, + 2868, 2872, 2876, 2880, 2884, 2888, 2892, 2896, 2900, 2904, + 2908, 2912, 2916, 2920, 2924, 2928, 2932, 2936, 2940, 2944, + 2948, 2952, 2956, 2960, 2964, 2968, 2972, 2976, 2980, 2984, + 2988, 2992, 2996, 3000, 3004, 3008, 3012, 3016, 3025, 3034, + 3043, 3052, 3058, 3059, 3063, 3067, 3073, 3077, 3084, 3088, + 3097, 3114, 3115, 3118, 3119, 3120, 3124, 3128, 3134, 3139, + 3143, 3147, 3151, 3157, 3157, 3168, 3172, 3178, 3182, 3188, + 3191, 3196, 3200, 3204, 3209, 3213, 3219, 3224, 3228, 3234, + 3235, 3239, 3243, 3244, 3245, 3246, 3247, 3252, 3251, 3263, + 3267, 3262, 3272, 3272, 3276, 3280, 3284, 3288, 3292, 3296, + 3300, 3304, 3308, 3312, 3316, 3317, 3323, 3330, 3322, 3343, + 3351, 3359, 3359, 3359, 3366, 3366, 3366, 3373, 3379, 3383, + 3392, 3401, 3411, 3413, 3410, 3422, 3420, 3438, 3443, 3436, + 3460, 3458, 3474, 3484, 3495, 3499, 3503, 3507, 3513, 3520, + 3521, 3522, 3525, 3526, 3529, 3530, 3538, 3539, 3545, 3549, + 3552, 3556, 3560, 3564, 3569, 3573, 3577, 3581, 3587, 3586, + 3596, 3600, 3604, 3608, 3614, 3619, 3624, 3628, 3632, 3636, + 3640, 3644, 3648, 3652, 3656, 3660, 3664, 3668, 3672, 3676, + 3680, 3686, 3691, 3698, 3698, 3702, 3707, 3713, 3717, 3723, + 3724, 3727, 3732, 3735, 3739, 3745, 3749, 3756, 3755, 3772, + 3777, 3781, 3786, 3793, 3797, 3801, 3805, 3809, 3813, 3817, + 3821, 3825, 3832, 3831, 3846, 3845, 3861, 3869, 3878, 3883, + 3887, 3887, 3892, 3892, 3897, 3897, 3907, 3908, 3912, 3916, + 3920, 3924, 3928, 3933, 3938, 3946, 3950, 3957, 3961, 3967, + 3968, 3974, 3975, 3981, 3982, 3986, 3990, 3994, 3998, 4002, + 4006, 4010, 4011, 4012, 4019, 4023, 4030, 4035, 4040, 4045, + 4050, 4055, 4063, 4067, 4074, 4078, 4086, 4090, 4094, 4101, + 4105, 4112, 4116, 4120, 4127, 4131, 4140, 4145, 4153, 4157, + 4162, 4169, 4175, 4182, 4185, 4189, 4190, 4193, 4197, 4200, + 4204, 4207, 4208, 4209, 4210, 4213, 4214, 4220, 4225, 4230, + 4235, 4241, 4242, 4248, 4254, 4253, 4265, 4269, 4275, 4279, + 4285, 4294, 4305, 4308, 4309, 4312, 4318, 4324, 4325, 4328, + 4335, 4334, 4349, 4353, 4361, 4365, 4377, 4384, 4391, 4392, + 4393, 4394, 4395, 4399, 4405, 4409, 4417, 4418, 4419, 4423, + 4429, 4433, 4437, 4441, 4445, 4451, 4455, 4461, 4465, 4469, + 4473, 4477, 4481, 4485, 4493, 4500, 4506, 4507, 4511, 4515, + 4514, 4531, 4532, 4535, 4541, 4545, 4551, 4552, 4556, 4560, + 4566, 4572, 4580, 4586, 4593, 4599, 4606, 4610, 4616, 4620, + 4626, 4627, 4630, 4634, 4640, 4644, 4648, 4652, 4658, 4662, + 4667, 4672, 4676, 4680, 4684, 4688, 4692, 4696, 4700, 4704, + 4708, 4712, 4716, 4720, 4724, 4729, 4735, 4740, 4745, 4750, + 4755, 4762, 4766, 4773, 4778, 4777, 4789, 4793, 4799, 4807, + 4815, 4823, 4827, 4833, 4837, 4843, 4844, 4847, 4852, 4859, + 4860, 4863, 4867, 4871, 4877, 4881, 4885, 4891, 4897, 4897, + 4904, 4905, 4911, 4915, 4921, 4927, 4932, 4936, 4941, 4946, + 4962, 4967, 4973, 4974, 4975, 4978, 4979, 4980, 4981, 4984, + 4985, 4986, 4989, 4990, 4993, 4997, 5003, 5004, 5010, 5011, + 5014, 5015, 5018, 5021, 5022, 5023, 5026, 5027, 5030, 5035, + 5038, 5039, 5043 }; #endif @@ -3177,7 +3181,7 @@ yysymbol_name (yysymbol_kind_t yysymbol) } #endif -#define YYPACT_NINF (-975) +#define YYPACT_NINF (-981) #define yypact_value_is_default(Yyn) \ ((Yyn) == YYPACT_NINF) @@ -3191,127 +3195,127 @@ yysymbol_name (yysymbol_kind_t yysymbol) STATE-NUM. */ static const yytype_int16 yypact[] = { - -975, 3710, 183, 8854, 10978, 11320, 7162, -975, 10624, 10624, - -975, -975, 11092, 8344, 6778, 9090, 9090, -975, -975, 9090, - 4231, 3370, -975, -975, -975, -975, 54, 8344, -975, 66, - -975, -975, -975, 7304, 3823, -975, -975, 7446, -975, -975, - -975, -975, -975, -975, -975, 210, 10742, 10742, 10742, 10742, - 109, 5891, 6010, 9562, 9916, 8626, -975, 8062, 1236, 893, - 314, 1249, 1252, -975, 447, 10860, 10742, -975, 857, -975, - 745, -975, 569, 2754, 2754, -975, -975, 173, 89, -975, - 106, 11206, -975, 136, 1909, 631, 667, 130, 41, -975, - 341, -975, -975, -975, -975, -975, -975, -975, -975, -975, - 211, 195, -975, 214, 87, -975, -975, -975, -975, -975, - -975, 180, 180, 54, 125, 498, -975, 10624, 187, 6129, - 556, 2225, 2225, -975, 215, -975, 691, -975, -975, 87, - -975, -975, -975, -975, -975, -975, -975, -975, -975, -975, - -975, -975, -975, -975, -975, -975, -975, -975, -975, -975, - -975, -975, -975, -975, -975, -975, -975, -975, 77, 143, - 149, 153, -975, -975, -975, -975, -975, -975, 164, 181, - 233, 247, -975, 248, -975, -975, -975, -975, -975, -975, - -975, -975, -975, -975, -975, -975, -975, -975, -975, -975, - -975, -975, -975, -975, -975, -975, -975, -975, -975, -975, - -975, -975, -975, -975, -975, -975, -975, -975, -975, 264, - 4923, 351, 569, 2754, 2754, 179, 261, 701, 220, 310, - 221, 179, 10624, 10624, 759, 365, -975, -975, 823, 404, - 21, 58, -975, -975, -975, -975, -975, -975, -975, -975, - -975, 8203, -975, -975, 298, -975, -975, -975, -975, -975, - -975, 857, -975, 951, -975, 439, -975, -975, 857, 3959, - 129, 10742, 10742, 10742, 10742, -975, 3465, -975, -975, 322, - 409, 322, -975, -975, -975, 9208, -975, -975, 9090, -975, - -975, -975, -975, 6778, 7016, -975, 334, 6248, -975, 891, - 379, 13606, 13606, 279, 8972, 5891, 355, 1572, 745, 857, - 402, -975, 6129, 857, 345, 1350, 1350, -975, 3465, 405, - 1350, -975, 491, 11434, 408, 950, 952, 981, 1713, -975, - -975, -975, -975, -975, 1327, -975, -975, -975, -975, -975, - -975, 966, 1331, -975, -975, 1244, -975, 872, -975, 1338, - -975, 1341, 452, 458, -975, -975, -975, -975, 6659, 10624, - 10624, 10624, 10624, 8972, 10624, 10624, 124, -975, -975, -975, - -975, 509, 857, -975, -975, -975, -975, -975, -975, -975, - 2841, 451, 459, 4923, 10742, -975, 441, 540, 453, -975, - 857, -975, -975, -975, 455, 10742, -975, 462, 549, 467, - 283, -975, -975, 516, 4923, -975, -975, 10034, -975, 5891, - 8740, 504, 10034, -975, 10742, 10742, 10742, 10742, 10742, 10742, - 10742, 10742, 10742, 10742, 10742, 10742, 10742, 10742, -975, 10742, + -981, 3710, 87, 8854, 10978, 11320, 7162, -981, 10624, 10624, + -981, -981, 11092, 8344, 6778, 9090, 9090, -981, -981, 9090, + 4231, 3370, -981, -981, -981, -981, -46, 8344, -981, 23, + -981, -981, -981, 7304, 3823, -981, -981, 7446, -981, -981, + -981, -981, -981, -981, -981, 232, 10742, 10742, 10742, 10742, + 111, 5891, 6010, 9562, 9916, 8626, -981, 8062, 1248, 1022, + 365, 1325, 1340, -981, 259, 10860, 10742, -981, 985, -981, + 834, -981, 507, 2969, 2969, -981, -981, 85, 76, -981, + -3, 11206, -981, 78, 3465, 631, 667, 143, 45, -981, + 209, -981, -981, -981, -981, -981, -981, -981, -981, -981, + 214, 109, -981, 279, 66, -981, -981, -981, -981, -981, + -981, 88, 88, -46, 498, 748, -981, 10624, 389, 6129, + 447, 2225, 2225, -981, 114, -981, 701, -981, -981, 66, + -981, -981, -981, -981, -981, -981, -981, -981, -981, -981, + -981, -981, -981, -981, -981, -981, -981, -981, -981, -981, + -981, -981, -981, -981, -981, -981, -981, -981, 77, 82, + 91, 130, -981, -981, -981, -981, -981, -981, 155, 156, + 165, 229, -981, 233, -981, -981, -981, -981, -981, -981, + -981, -981, -981, -981, -981, -981, -981, -981, -981, -981, + -981, -981, -981, -981, -981, -981, -981, -981, -981, -981, + -981, -981, -981, -981, -981, -981, -981, -981, -981, 248, + 4923, 202, 507, 2969, 2969, 179, 210, 823, 244, 158, + 263, 179, 10624, 10624, 855, 287, -981, -981, 898, 309, + 58, 89, -981, -981, -981, -981, -981, -981, -981, -981, + -981, 8203, -981, -981, 237, -981, -981, -981, -981, -981, + -981, 985, -981, 549, -981, 384, -981, -981, 985, 3959, + 106, 10742, 10742, 10742, 10742, -981, 13544, -981, -981, 318, + 407, 318, -981, -981, -981, 9208, -981, -981, 9090, -981, + -981, -981, -981, 6778, 7016, -981, 334, 6248, -981, 929, + 379, 13668, 13668, 291, 8972, 5891, 341, 1458, 834, 985, + 402, -981, 6129, 985, 359, 1229, 1229, -981, 13544, 400, + 1229, -981, 491, 11434, 408, 934, 950, 952, 2307, -981, + -981, -981, -981, -981, 1353, -981, -981, -981, -981, -981, + -981, 576, 1398, -981, -981, 1181, -981, 892, -981, 1401, + -981, 1416, 452, 458, -981, -981, -981, -981, 6659, 10624, + 10624, 10624, 10624, 8972, 10624, 10624, 54, -981, -981, -981, + -981, 515, 985, -981, -981, -981, -981, -981, -981, -981, + 2406, 462, 470, 4923, 10742, -981, 455, 553, 465, -981, + 985, -981, -981, -981, 480, 10742, -981, 492, 586, 504, + 51, -981, -981, 479, 4923, -981, -981, 10034, -981, 5891, + 8740, 511, 10034, -981, 10742, 10742, 10742, 10742, 10742, 10742, + 10742, 10742, 10742, 10742, 10742, 10742, 10742, 10742, -981, 10742, 10742, 10742, 10742, 10742, 10742, 10742, 10742, 10742, 10742, 10742, - 10742, 11712, -975, 9090, -975, 11798, -975, -975, 13002, -975, - -975, -975, -975, 10860, 10860, -975, 541, -975, 569, -975, - 1001, -975, -975, -975, -975, -975, -975, 11884, 9090, 11970, - 4923, 10624, -975, -975, -975, 635, 640, 244, 535, 547, - -975, 5069, 646, 10742, 12056, 9090, 12142, 10742, 10742, 5507, - 750, 750, 84, 12228, 9090, 12314, -975, 614, -975, 6248, - 435, -975, -975, 10152, 668, -975, 10742, 10742, 13544, 13544, - 13544, 10742, -975, -975, 9326, -975, 10742, -975, 9680, 6897, - 550, 857, 322, 322, -975, -975, 914, 571, -975, -975, - -975, 8344, 5626, 546, 12056, 12142, 10742, 745, 857, -975, - -975, 6540, 559, -975, -975, -975, 9798, -975, 857, 9916, - -975, -975, -975, 1001, 106, 11434, -975, 11434, 12400, 9090, - 12486, 2165, -975, -975, 590, -975, 1398, 6248, 966, -975, - -975, -975, -975, -975, -975, -975, 10742, 10742, -975, -975, - -975, -975, -975, -975, -975, -975, -975, -975, -975, -975, - 1453, 857, 857, 595, 10860, 727, 13544, 578, -975, -975, - -975, 59, -975, -975, 2966, -975, 13544, 2165, -975, -975, - 1242, -975, -975, -975, 10860, 729, 35, 10742, -975, 13260, - 322, -975, 857, 11434, 606, -975, -975, -975, 702, 628, - 2901, -975, -975, 1024, 263, 2044, 3556, 3556, 3556, 3556, - 1659, 1659, 13624, 3121, 3556, 3556, 13606, 13606, 1564, 1564, - 2044, 379, 13544, 1659, 1659, 1814, 1814, 1470, 542, 542, - 379, 379, 379, 4367, 7802, 4639, 7920, -975, 180, -975, - 611, 322, 291, -975, 342, -975, -975, 4095, -975, -975, - 2566, 35, 35, -975, 13074, -975, -975, -975, -975, -975, - 857, 10624, 4923, 757, 161, -975, 180, 624, 180, 751, - 914, 8485, -975, 10270, 755, -975, 10742, 10742, 604, -975, - 7564, 7683, 634, 346, 347, 755, -975, -975, -975, -975, - 55, 63, 641, 86, 105, 10624, 8344, 651, 2044, 779, - 13544, 327, -975, 13544, 13544, 13544, 1208, 10742, 3465, -975, - 322, 13544, -975, -975, -975, -975, 9444, 9680, -975, -975, - -975, 656, -975, -975, 131, 745, 857, 1350, 504, -975, - 757, 161, 659, 936, 1105, -975, 61, 2165, -975, 658, - -975, 379, 379, -975, -975, 336, 857, 666, -975, -975, - 2986, 765, 13136, -975, 753, 509, -975, 453, -975, 857, - -975, -975, 671, 678, 682, -975, 690, 753, 682, 789, - 13198, -975, -975, 2165, 4923, -975, -975, 13331, 10388, -975, - -975, 11434, 8972, 10860, 10742, 12572, 9090, 12658, -975, -975, - -975, 777, -975, -975, 782, 1383, 107, 800, 393, 801, - -975, 2304, 713, 46, -975, -975, 716, 763, -975, 728, - -975, -975, -975, -975, -975, -975, 797, 10860, 10860, -975, - 541, 468, 9326, 10860, 10860, -975, 541, 41, 173, 4923, - 6248, 35, -975, 857, 859, -975, -975, -975, -975, 13260, - -975, 784, -975, 5772, 858, -975, 10624, 863, -975, 10742, - 10742, 349, 10742, 10742, 871, 6394, 6394, 113, 750, -975, - -975, 536, -975, 10506, 5215, 13544, -975, 6897, 322, -975, - -975, -975, 615, 743, 989, 4923, 6248, -975, -975, -975, - 758, -975, 1560, 857, 10742, 10742, -975, -975, 2165, -975, - 1242, -975, 1242, -975, 1242, -975, -975, 10742, 10742, -975, - -975, -975, 11548, -975, 760, 453, 761, 11548, -975, 766, - 769, -975, 902, 10742, 13402, -975, -975, 13544, 4503, 4775, - 790, 360, 361, 2410, -975, -975, -975, -975, 791, 776, - 783, 777, -975, 799, 798, -975, -975, -975, -975, -975, - 807, 813, -975, 890, 2410, 2410, 901, 50, 10742, 10742, - -975, -975, -975, -975, -975, 10860, -975, -975, -975, -975, - -975, -975, -975, 945, 826, 6248, 4923, -975, -975, 11662, - 179, -975, -975, 6394, -975, -975, 179, -975, 10742, -975, - 953, 956, -975, 10624, 10624, 5361, 13544, 300, -975, 9680, - -975, 1608, 958, 827, 1582, 1582, 838, -975, 13544, 13544, - 682, 850, 682, 682, 13544, 13544, 869, 874, 947, 1064, - 578, -975, -975, 1963, -975, 1064, 2165, -975, 1242, -975, - -975, 13473, 469, -975, -975, 2304, 2410, -975, 50, -975, - 2410, -975, -975, 866, -975, -975, -975, -975, 13544, 13544, - -975, -975, -975, -975, 875, 996, 963, -975, 1097, 952, - 981, 4923, -975, 5069, -975, -975, 6394, 179, 179, 389, - -975, -975, -975, -975, 877, -975, -975, -975, -975, 882, - 882, 1582, 886, -975, 1242, -975, -975, -975, -975, -975, - -975, 12744, -975, 453, 578, -975, -975, 888, 892, 897, - -975, 903, 897, -975, 916, 922, -975, 866, 2410, -975, - -975, 1001, 12830, 9090, 12916, 640, 604, 1005, 5361, 5361, - 2044, -975, 1041, 1608, 1208, 1582, 882, 1582, 682, 921, - 923, -975, 2165, -975, 1242, -975, 1242, -975, 1242, -975, - -975, 2410, 2304, -975, 757, 161, 946, 711, 725, -975, - -975, -975, 389, 389, 594, -975, -975, 882, -975, 897, - 961, 897, 897, 967, -975, 615, 1074, 1079, 179, 1055, - 1059, -975, 1242, -975, -975, -975, 2410, -975, -975, 5361, - 10624, 10624, 897, 389, 179, 179, -975, -975, 5361, 5361, - 389, 389, -975, -975 + 10742, 11712, -981, 9090, -981, 11798, -981, -981, 13002, -981, + -981, -981, -981, 10860, 10860, -981, 541, -981, 507, -981, + 1063, -981, -981, -981, -981, -981, -981, 11884, 9090, 11970, + 4923, 10624, -981, -981, -981, 650, 649, 286, 556, 562, + -981, 5069, 673, 10742, 12056, 9090, 12142, 10742, 10742, 5507, + 327, 327, 105, 12228, 9090, 12314, -981, 638, -981, 6248, + 381, -981, -981, 10152, 697, -981, 10742, 10742, 13606, 13606, + 13606, 10742, -981, -981, 9326, -981, 10742, -981, 9680, 6897, + 571, 985, 318, 318, -981, -981, 943, 587, -981, -981, + -981, 8344, 5626, 598, 12056, 12142, 10742, 834, 985, -981, + -981, 6540, 600, -981, -981, -981, 9798, -981, 985, 9916, + -981, -981, -981, 1063, -3, 11434, -981, 11434, 12400, 9090, + 12486, 2459, -981, -981, 602, -981, 1447, 6248, 576, -981, + -981, -981, -981, -981, -981, -981, 10742, 10742, -981, -981, + -981, -981, -981, -981, -981, -981, -981, -981, -981, -981, + 1453, 985, 985, 604, 10860, 737, 13606, 578, -981, -981, + -981, 311, -981, -981, 2734, -981, 13606, 2459, -981, -981, + 1251, -981, -981, -981, 10860, 754, 95, 10742, -981, 13260, + 318, -981, 985, 11434, 628, -981, -981, -981, 729, 654, + 1535, -981, -981, 1076, 290, 2835, 3556, 3556, 3556, 3556, + 1656, 1656, 13686, 3121, 3556, 3556, 13668, 13668, 337, 337, + 2835, 379, 13606, 1656, 1656, 1702, 1702, 1587, 542, 542, + 379, 379, 379, 4367, 7802, 4639, 7920, -981, 88, -981, + 636, 318, 310, -981, 342, -981, -981, 4095, -981, -981, + 2566, 95, 95, -981, 13074, -981, -981, -981, -981, -981, + 985, 10624, 4923, 759, 161, -981, 88, 640, 88, 767, + 943, 8485, -981, 10270, 765, -981, 10742, 10742, 597, -981, + 7564, 7683, 645, 469, 474, 765, -981, -981, -981, -981, + 63, 84, 646, 123, 129, 10624, 8344, 655, 2835, 773, + 13606, 195, -981, 13606, 13606, 13606, 857, 10742, 13544, -981, + 318, 13606, -981, -981, -981, -981, 9444, 9680, -981, -981, + -981, 656, -981, -981, 190, 834, 985, 1229, 511, -981, + 759, 161, 648, 764, 811, -981, 122, 2459, -981, 657, + -981, 379, 379, -981, -981, 336, 985, 658, -981, -981, + 2754, 753, 13136, -981, 751, 515, -981, 465, -981, 985, + -981, -981, 668, 671, 678, -981, 689, 751, 678, 780, + 13198, -981, -981, 2459, 4923, -981, -981, 13331, 10388, -981, + -981, 11434, 8972, 10860, 10742, 12572, 9090, 12658, -981, -981, + -981, 752, -981, 777, 2044, 113, 782, 191, 783, -981, + 2438, 700, 194, -981, -981, 705, 761, -981, 710, -981, + -981, -981, 209, -981, -981, -981, 1139, 10860, 10860, -981, + 541, 468, 9326, 10860, 10860, -981, 541, 45, 85, 4923, + 6248, 95, -981, 985, 840, -981, -981, -981, -981, 13260, + -981, 769, -981, 5772, 844, -981, 10624, 850, -981, 10742, + 10742, 488, 10742, 10742, 853, 6394, 6394, 154, 327, -981, + -981, 536, -981, 10506, 5215, 13606, -981, 6897, 318, -981, + -981, -981, 615, 727, 1002, 4923, 6248, -981, -981, -981, + 728, -981, 1600, 985, 10742, 10742, -981, -981, 2459, -981, + 1251, -981, 1251, -981, 1251, -981, -981, 10742, 10742, -981, + -981, -981, 11548, -981, 747, 465, 749, 11548, -981, 756, + 758, -981, 879, 10742, 13402, -981, -981, 13606, 4503, 4775, + 755, 514, 529, 2921, -981, -981, -981, -981, 771, 768, + 775, 752, -981, 776, 778, -981, -981, -981, -981, -981, + 789, 790, -981, 842, 2921, 2921, 852, 135, 10742, 10742, + -981, -981, -981, -981, -981, 10860, -981, -981, -981, -981, + -981, -981, -981, 903, 786, 6248, 4923, -981, -981, 11662, + 179, -981, -981, 6394, -981, -981, 179, -981, 10742, -981, + 919, 921, -981, 10624, 10624, 5361, 13606, 81, -981, 9680, + -981, 1341, 922, 799, 1642, 1642, 977, -981, 13606, 13606, + 678, 798, 678, 678, 13606, 13606, 815, 826, 907, 1097, + 578, -981, -981, 2165, -981, 1097, 2459, -981, 1251, -981, + -981, 13473, 564, -981, -981, 2438, 2921, -981, 135, -981, + 2921, -981, -981, 824, -981, -981, -981, -981, 13606, 13606, + -981, -981, -981, -981, 829, 956, 910, -981, 1144, 950, + 952, 4923, -981, 5069, -981, -981, 6394, 179, 179, 430, + -981, -981, -981, -981, 831, -981, -981, -981, -981, 847, + 847, 1642, 854, -981, 1251, -981, -981, -981, -981, -981, + -981, 12744, -981, 465, 578, -981, -981, 858, 868, 869, + -981, 872, 869, -981, 876, 884, -981, 824, 2921, -981, + -981, 1063, 12830, 9090, 12916, 649, 597, 1005, 5361, 5361, + 2835, -981, 1014, 1341, 857, 1642, 847, 1642, 678, 848, + 897, -981, 2459, -981, 1251, -981, 1251, -981, 1251, -981, + -981, 2921, 2438, -981, 759, 161, 915, 711, 725, -981, + -981, -981, 430, 430, 682, -981, -981, 847, -981, 869, + 918, 869, 869, 923, -981, 615, 1051, 1052, 179, 1031, + 1039, -981, 1251, -981, -981, -981, 2921, -981, -981, 5361, + 10624, 10624, 869, 430, 179, 179, -981, -981, 5361, 5361, + 430, 430, -981, -981 }; /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. @@ -3400,9 +3404,9 @@ static const yytype_int16 yydefact[] = 598, 627, 600, 600, 600, 634, 600, 622, 600, 42, 249, 343, 395, 393, 0, 392, 391, 288, 0, 94, 88, 0, 0, 0, 0, 0, 692, 0, 457, 458, - 459, 491, 476, 456, 0, 0, 0, 475, 490, 0, - 56, 0, 436, 449, 451, 461, 440, 460, 462, 442, - 484, 444, 453, 455, 454, 54, 0, 0, 0, 415, + 459, 491, 476, 0, 0, 0, 475, 490, 0, 56, + 0, 436, 449, 451, 461, 440, 460, 462, 442, 484, + 444, 453, 456, 455, 454, 54, 0, 0, 0, 415, 72, 421, 265, 0, 0, 414, 70, 410, 65, 0, 0, 692, 338, 0, 0, 421, 341, 649, 60, 494, 495, 692, 496, 0, 692, 356, 0, 0, 354, 0, @@ -3445,26 +3449,26 @@ static const yytype_int16 yydefact[] = /* YYPGOTO[NTERM-NUM]. */ static const yytype_int16 yypgoto[] = { - -975, -975, -975, 583, -975, 14, -975, -267, 357, -975, - -975, 65, -318, -327, -5, -975, -975, 878, -975, 1448, - 18, -45, -975, -975, -692, 29, 1098, -224, 4, -38, - -298, -482, -19, 1858, -97, 1106, 20, -20, -975, -975, - 12, -975, 2730, -975, 780, 73, 24, -384, 83, -975, - -9, -454, -269, 17, 118, -379, 26, -975, -975, -975, - -975, -975, -975, -975, -975, -975, -975, -975, -975, -975, - -975, -975, -975, 141, -151, -468, -14, -590, -975, -975, - -975, 324, 497, -975, -637, -975, -975, -256, -975, -18, - -975, -975, -975, 271, -975, -975, -975, -80, -975, -479, - -975, -569, -975, -975, -975, -599, -975, 70, -276, -975, - 159, -975, -975, -914, -729, -975, -975, -975, 317, -882, - -738, -975, 15, -975, -975, -975, -975, -975, 276, 62, - -159, -975, -975, -975, -975, -975, -302, -975, 885, -975, - -975, 421, 1, -975, -975, 679, 1875, 2479, 1141, 1627, - -975, -975, -23, 601, 13, 146, 562, 120, -975, -975, - -975, -69, 67, -219, -242, -974, -723, -554, -975, 1045, - -769, -575, -931, 122, -538, -975, -534, -975, 230, -368, - -975, -975, -975, 43, -475, 699, -356, -975, -975, -81, - -975, 75, -25, 647, -249, 1060, -268, -21, -1 + -981, -981, -981, 563, -981, 14, -981, -267, 357, -981, + -981, 65, -318, -327, -5, -981, -981, 878, -981, 1448, + 18, -45, -981, -981, -387, 29, 1072, -190, 4, -38, + -298, -461, -19, 1858, -97, 1100, 20, -20, -981, -981, + 12, -981, 2730, -981, 651, 73, -247, -384, 116, -981, + -9, -454, -269, 17, 118, -365, 26, -981, -981, -981, + -981, -981, -981, -981, -981, -981, -981, -981, -981, -981, + -981, -981, -981, 141, -151, -468, -17, -590, -981, -981, + -981, 313, 275, -981, -637, -981, -981, -172, -981, -26, + -981, -981, -981, 262, -981, -981, -981, -80, -981, -479, + -981, -569, -981, -981, -981, -599, -981, 61, 250, -981, + 148, -981, -981, -888, -791, -981, -981, -981, 299, -889, + -748, -981, -10, -981, -981, -981, -981, 766, 276, 62, + -159, -981, -981, -981, -981, -981, -285, -981, 870, -981, + -981, 1046, 1, -981, -981, 1154, 1875, 2479, 1129, 1627, + -981, -981, -23, 644, 16, 146, 550, 110, -981, -981, + -981, -69, 67, -219, -242, -946, -723, -554, -981, 1045, + -769, -575, -980, 108, -514, -981, -534, -981, 230, -368, + -981, -981, -981, 43, -475, 699, -356, -981, -981, -81, + -981, 75, -25, 647, -249, 1060, -268, -21, -1 }; /* YYDEFGOTO[NTERM-NUM]. */ @@ -3480,9 +3484,9 @@ static const yytype_int16 yydefgoto[] = 680, 986, 460, 289, 471, 707, 867, 1131, 229, 766, 1014, 1105, 1034, 920, 794, 921, 795, 893, 1084, 1085, 552, 897, 606, 396, 87, 88, 672, 447, 671, 494, - 1082, 1132, 1178, 1179, 1180, 820, 821, 1053, 822, 823, - 824, 825, 948, 949, 826, 827, 828, 953, 829, 830, - 831, 832, 694, 861, 990, 994, 89, 90, 91, 332, + 1082, 1132, 1178, 1179, 1180, 819, 820, 1053, 821, 822, + 823, 824, 948, 949, 825, 826, 827, 953, 828, 829, + 830, 831, 694, 861, 990, 994, 89, 90, 91, 332, 333, 557, 92, 93, 94, 558, 252, 253, 254, 489, 95, 96, 97, 326, 98, 99, 218, 219, 102, 220, 456, 681, 371, 372, 373, 374, 375, 923, 924, 376, @@ -3499,264 +3503,264 @@ static const yytype_int16 yytable[] = { 105, 284, 507, 212, 212, 435, 437, 285, 441, 212, 592, 719, 282, 709, 245, 545, 520, 107, 206, 280, - 344, 451, 785, 622, 927, 206, 205, 221, 245, 559, - 125, 125, 251, 732, 849, 850, 314, 1086, 125, 206, - 781, 835, 900, 401, 265, 265, -107, 348, 265, 660, - 729, 393, 307, 311, 729, 300, 782, 540, 271, 271, - 783, 542, 271, 786, 732, 749, 70, 439, 70, 206, - 479, 528, 392, 392, 687, 658, 553, 325, 955, 667, - -110, 125, 670, -114, 616, 1056, 950, 394, -112, 255, - 895, 702, 961, 276, 281, 446, 306, 310, 267, 273, - 712, 951, 274, 688, 864, 1110, 585, 125, 868, -113, - 280, -109, 448, 439, 792, 874, 673, 676, 658, 881, + 344, 451, 785, 950, 927, 206, 205, 221, 245, 961, + 125, 125, 251, 732, 849, 850, 314, 622, 125, 206, + 781, 835, 900, 401, 265, 265, 559, 348, 265, 660, + 729, 393, 307, 311, 729, 300, 1110, 540, 271, 271, + 783, 542, 271, 786, 732, 1086, 70, 955, 70, 206, + 479, 439, 392, 392, 687, 658, 553, 325, 1056, 667, + 782, 125, 670, -107, 616, 602, 749, 106, -110, 255, + 394, 702, 439, 276, 281, 446, 306, 310, 283, 863, + 712, 493, 603, 688, 864, 528, 585, 125, 868, -112, + 280, 576, 448, 395, -114, 874, 673, 676, 658, 881, 667, 335, 337, 339, 341, 616, 616, 605, 582, 688, - -111, 800, 1115, 269, 269, 394, 476, 269, -108, 963, - 379, 379, 86, 347, 86, 126, 126, 485, 974, 217, - 217, -662, 818, 228, 980, 217, 217, 217, 951, 1086, - 217, 793, 367, -99, 293, 752, 1056, -560, 598, 688, - 601, 305, 964, 561, 896, 612, 561, 445, 561, 440, - 561, 576, 561, 106, 276, 281, 497, 368, 379, 379, - 468, 469, 86, 689, 688, 470, 315, -102, 283, -665, - -106, 395, 445, 781, 287, -104, 217, -666, 525, 818, - 397, 1110, 392, 392, 985, -550, 781, 212, 212, 782, - 436, -556, 315, 783, -560, 440, -105, 394, -101, 1057, - -550, 275, 782, -559, 550, 432, 783, 1173, 449, -561, - 480, 481, 450, -562, 507, 577, 952, -103, 398, 545, - -115, -305, -80, 206, -564, -100, 760, 402, 217, 930, - 86, 325, 732, -662, 927, -550, -305, 927, 505, -662, - 505, -563, -550, -94, 514, 544, 531, 434, 516, 519, - 379, 379, 729, 729, 538, 538, 504, 443, 508, 538, - -559, 270, 270, 1092, 246, 270, -561, 247, 248, 300, - -562, -305, -556, -555, 265, -666, 444, 265, -305, 755, - 1116, -564, -555, 478, 860, 545, 1114, 602, 863, 935, - 493, 271, 536, -565, 275, 249, 490, 250, -563, 270, - 270, -115, -107, 785, 603, -114, 526, -550, -554, 125, - 455, -107, -114, 470, 212, 212, 212, 212, 781, 574, - 575, 86, 608, -99, 462, 444, -106, 618, 781, 527, - 685, 515, 568, 217, 217, -113, 442, 569, 570, 571, - 572, 526, 530, 927, 1021, 589, 336, 589, 328, 329, - -565, 614, 473, 843, -114, 379, 556, 763, 729, 357, - 358, 359, 360, 512, -550, -554, 269, 561, 618, 618, - -113, 41, 477, 300, 42, 764, 467, 863, 296, 906, - 1002, 463, -109, 70, 1130, 854, 217, 1080, 573, 217, - 482, -105, 940, 1174, 217, 217, 125, 957, 86, 486, - 330, 331, 514, -101, 844, 86, 86, 379, 872, 873, - 488, 998, 246, 86, 958, 247, 248, 665, 58, 658, - 665, 667, 843, 844, 315, 493, 690, 514, 520, 493, - 718, 886, 265, -111, 502, 695, 506, -109, -111, 502, - -108, 665, 522, 249, 514, 250, 296, 726, 899, 781, - 419, -109, -111, 514, -103, 616, 535, 265, 665, 86, + -113, 267, 273, 269, 269, 274, 476, 269, 394, 398, + 379, 379, 86, 347, 86, 126, 126, 485, -109, 217, + 217, 895, 800, 228, -111, 217, 217, 217, 1115, 1056, + 217, 287, 1110, 497, 951, 752, 293, -560, 598, 688, + 601, 305, -559, 561, 792, 577, 561, 445, 561, -108, + 561, -561, 561, 440, 276, 281, 951, 1086, 379, 379, + 468, 469, 86, 689, 688, 470, 315, 397, -556, 402, + -99, 443, 445, 781, 440, -102, 217, -665, 525, 612, + 275, 470, 392, 392, 985, 817, 781, 212, 212, 1057, + -562, 793, 315, 783, -560, 957, -104, -115, -666, -559, + 394, -106, 275, 436, 550, 896, 783, 817, -561, 455, + 480, 481, 958, 782, 507, -564, -563, -105, 432, 545, + 477, -305, 952, 206, 1114, -565, 782, 467, 217, 930, + 86, 325, 732, 1173, 927, -101, -305, 927, 505, 41, + 505, -103, 42, 760, 514, 544, 531, -562, 516, 519, + 379, 379, 729, 729, 538, 538, 504, 963, 508, 538, + 434, 270, 270, 1092, 246, 270, -100, 247, 248, 300, + 1116, -305, -564, -563, 265, -666, -555, 265, -305, 755, + 246, -80, -565, 247, 248, 545, 58, 342, 343, -550, + 964, 271, 536, -554, -556, 249, 490, 250, 860, 270, + 270, 473, -94, 785, 486, -107, -555, 502, 462, 125, + 935, 249, 482, 250, 212, 212, 212, 212, 781, 574, + 575, 86, 608, -115, 706, 478, -99, 618, 781, 527, + 685, 1174, 568, 217, 217, -107, 442, 569, 570, 571, + 572, 444, 530, 927, 1021, 589, -550, 589, 526, 488, + -554, 614, 444, 526, -114, 379, 556, 763, 729, 357, + 358, 359, 360, 512, 515, 463, 269, 561, 618, 618, + -114, 493, 843, 300, 493, 764, 718, -113, 296, 906, + 1002, -114, -113, 70, 367, 854, 217, 1080, 573, 217, + 845, -106, 940, 847, 217, 217, 125, 336, 86, 328, + 329, -109, 514, -105, 844, 86, 86, 379, 419, 368, + 449, 845, 246, 86, 450, 247, 248, 665, 863, 658, + 665, 667, -101, 974, 315, 1130, 690, 514, 520, 980, + 502, 886, 265, -111, 506, 695, 426, 427, 428, 429, + 430, 665, 522, 249, 514, 250, 296, 726, 899, 781, + 419, 330, 331, 514, -103, 616, 529, 265, 665, 86, 217, 217, 217, 217, 86, 217, 217, 665, 891, 1108, - 529, 740, 1111, 545, 265, 342, 343, 748, 666, 544, + 535, 740, 1111, 545, 265, 452, 453, 748, 666, 544, 970, 505, 505, 265, 86, 610, 976, 978, 777, 616, - 610, 105, 245, -79, -663, 616, 616, 932, 1081, 736, - 737, 1164, 666, 206, 732, 86, 868, 665, 217, 947, - 86, 315, 807, 623, 541, 960, 543, 730, 514, 666, + 610, 105, 245, -79, -662, 616, 616, 932, 1081, 736, + 737, 1164, 666, 206, 732, 86, 868, 665, 217, 541, + 86, 315, 807, 623, 354, 355, 543, 730, 514, 666, 547, 270, 470, 566, 270, 729, 1031, 1032, 666, 567, - 975, 975, 665, 909, 911, 913, 578, 915, 520, 916, - 884, 125, 584, 125, 217, 544, 1003, 1004, 265, 747, - -571, 781, 983, 587, 623, 623, 735, 70, -554, -108, - -108, 590, 781, 1176, 1177, 591, 745, 594, 666, 217, - 599, 86, 217, -554, 597, 796, 354, 355, 1170, 600, - -100, 721, 86, 771, 452, 453, 217, 836, 379, 886, - 86, 866, 863, 666, 1197, 217, 520, 798, 1012, 774, - 86, 1202, 1203, 789, -432, -434, -663, 604, -554, 125, - 678, -421, -663, 419, 523, -554, 615, 775, 1060, 840, - 692, 246, 296, 693, 247, 248, 696, 616, 846, 533, - 505, 848, 699, 86, 1026, 1027, 280, 1043, 697, 280, - 796, 796, 86, 428, 429, 430, 212, 555, 842, 365, - 366, 367, 249, 722, 250, 742, 315, 280, 315, 1054, - 217, 845, 862, 865, 847, 734, 879, 865, 86, 853, - 104, -94, 104, 206, 865, -421, 368, 104, 104, 270, - 212, 858, 845, 104, 104, 104, 739, 245, 104, 1065, - -421, -348, 454, 454, 925, 217, 538, -109, 206, 505, - 1005, 839, 757, 878, 270, 1141, -348, 770, 883, 1156, - 276, -111, 773, 276, 791, 217, 802, 508, 801, 803, - 104, 270, 841, -421, 315, -421, 296, 431, 618, 839, - 270, 276, -421, 544, 104, 855, 856, 981, 688, 1113, - 947, -348, 432, 863, 1054, 871, 589, 706, -348, 888, - 270, 457, 877, -669, 270, 349, 350, 351, 352, 353, - 880, 474, 618, 972, 882, 889, 432, 898, 618, 618, - 892, 1093, 1095, 1096, 1097, 514, 432, 433, 902, 904, - 576, 610, 270, 908, 434, 270, 104, 778, 104, 665, - 910, 778, 217, 86, 912, 270, -109, 125, 698, -109, - -109, 458, 914, 917, 943, 265, 705, 944, 434, 1071, - -111, 475, 1153, -111, -111, 1073, 717, -669, 434, -558, - 796, 956, 959, 966, 968, 962, 217, -109, 965, -109, - 991, 212, -669, 995, -558, 246, 520, 936, 247, 248, - 967, -111, 993, -111, 988, 947, 1113, 989, 997, 73, - 666, 73, 121, 121, 996, 888, 999, 505, 1010, 763, - 121, 357, 358, 359, 360, -669, 249, -669, 250, -558, - 1015, -665, 1030, 1033, -669, 1009, -558, 764, 1036, 104, - 1113, 1038, 246, 483, 759, 247, 248, 1040, 1045, 1168, - 969, 104, 104, 589, 589, 1046, 1128, 1129, 432, 73, - 618, 1042, 1044, 121, 563, 86, 328, 329, 1047, 922, - 1048, 1051, 315, 86, 623, 250, 532, 217, 125, 1049, - 534, 354, 355, 125, 334, 1050, 1055, 328, 329, 121, - 1062, 1103, -665, 484, 925, 1063, 1088, 925, 1074, 925, - 434, 1075, 246, 1087, 104, 247, 248, 104, 623, 217, - 1091, 524, 104, 104, 623, 623, 104, 1124, 330, 331, - 86, 86, 1094, 104, 104, 1098, 432, 73, 212, 212, - 1099, 104, 1100, 249, 86, 250, 865, 217, 1118, 330, - 331, 1120, 270, 270, 1119, 125, 86, 86, 1121, 1133, - 1161, 1077, 1078, 931, 1135, 86, -665, 1189, 1137, 246, - 1142, 475, 247, 248, 1144, 925, 86, 86, 434, 1146, - 548, -665, -557, 1198, 1199, 1148, 833, 104, 104, 104, - 104, 104, 104, 104, 104, 432, 1165, -557, 1151, 1134, - 249, 833, 250, 1029, 1152, -665, 491, -666, 1035, 247, - 248, -295, 104, 925, -665, 925, -665, 925, 865, 925, - -665, 246, 270, -665, 247, 248, -295, 1175, 73, 1187, - 549, -306, -557, 104, 1188, 1190, 104, 434, 104, -557, - 1191, 104, 589, 1182, 246, 741, -306, 247, 248, 1186, - 226, 130, 1160, 925, 805, 1166, 623, 919, 270, 982, - 1117, -295, 514, 1052, 695, 865, 86, 86, -295, 432, - 1068, -666, 104, 954, 86, 1011, 665, 250, 492, 833, - 1159, -306, 104, 104, 217, 217, 86, 208, -306, 776, - 1102, 0, 265, 0, 1101, 1107, 733, 104, 0, 104, - 104, 865, 865, 738, 806, 73, 0, 0, 0, 432, - 104, 434, 73, 73, 104, 744, 922, 0, 104, 922, - 73, 0, 922, 104, 922, 212, 212, 1122, 104, 0, - 0, 121, 865, 0, 0, -666, 0, 666, 0, 865, - 865, 0, 432, 0, 458, 0, 0, 984, 1194, 1195, - -666, 434, 86, 0, 86, 0, 0, 86, 0, 0, - 992, 104, 0, 675, 677, 0, 73, 0, 768, 769, - 104, 73, 1000, 1001, 0, 0, 833, 1123, 0, 0, - 0, 1007, 833, -666, 434, -666, 0, 0, 104, -666, - 922, 73, -666, 1013, 0, 0, 104, 675, 677, 799, - 778, 0, 0, 931, 217, 0, 931, 0, 931, 86, + 975, 872, 665, 909, 911, 913, 873, 915, 520, 916, + 884, 125, 578, 125, 217, 544, 1003, 1004, 265, 747, + 998, 781, 983, 584, 623, 623, 735, 70, -550, -108, + -109, -571, 781, 1176, 1177, -111, 745, 587, 666, 217, + 604, 86, 217, -550, 590, 796, 843, 591, 1170, -108, + -100, 721, 86, 771, 866, 863, 217, 836, 379, 886, + 86, 844, 594, 666, 1197, 217, 520, 798, 1012, 774, + 86, 1202, 1203, 789, 597, -109, -662, 599, -550, 125, + 678, -421, -662, 419, 523, -550, 600, 775, 1060, 840, + -111, 246, 296, 615, 247, 248, 975, 616, 846, 533, + 505, 848, 693, 86, 491, 692, 280, 247, 248, 280, + 796, 796, 86, 428, 429, 430, 212, 696, 842, 365, + 366, 367, 249, 697, 250, -108, 315, 280, 315, 699, + 217, 246, 862, 865, 247, 248, 879, 865, 86, 853, + 104, 555, 104, 206, 865, -421, 368, 104, 104, 270, + 212, 858, 722, 104, 104, 104, 734, 245, 104, 1065, + -421, -348, -432, -434, 925, 217, 538, -109, 206, 505, + 1005, 839, 739, 878, 270, 1141, -348, 742, 883, 1156, + 276, -111, -94, 276, 757, 217, 770, 508, 1026, 1027, + 104, 270, 773, -421, 315, -421, 296, 431, 618, 839, + 270, 276, -421, 544, 104, 454, 454, 981, 688, 791, + 801, -348, 432, 802, -663, 803, 589, 841, -348, 888, + 270, 855, 856, 863, 270, -669, 871, 877, 882, 892, + -665, 457, 618, 972, 880, 889, 898, 904, 618, 618, + 902, 1093, 1095, 1096, 1097, 514, 432, 433, 576, 943, + 908, 610, 270, 910, 434, 270, 104, 778, 104, 665, + 912, 778, 217, 86, 917, 270, -109, 125, 698, -109, + -109, 914, 944, 956, 959, 265, 705, -666, -554, 1071, + -111, 458, 962, -111, -111, 1073, 717, 965, 434, -669, + 796, 966, 967, -554, -665, 988, 217, -109, 993, -109, + 991, 212, 989, 995, -669, 997, 520, 936, 999, -665, + 1015, -111, 1010, -111, 349, 350, 351, 352, 353, 73, + 666, 73, 121, 121, 996, 888, -663, 505, -554, 1030, + 121, 1033, -663, 1051, 1040, -554, 1042, -669, 1036, -669, + 1038, -666, -665, -665, -665, 1009, -669, 1055, -665, 104, + 1045, -665, 1044, 474, 759, 1047, -666, 1046, 1062, 1168, + 1048, 104, 104, 589, 589, 1063, 1128, 1129, 432, 73, + 618, 1049, 1050, 121, 1074, 86, 1075, 1087, 1088, 922, + 1094, 1098, 315, 86, 623, -558, 532, 217, 125, -666, + 534, -666, 1099, 125, 563, -666, 328, 329, -666, 121, + -558, 1103, 1100, 475, 925, 1121, 1118, 925, 1119, 925, + 434, 1120, 246, 1133, 104, 247, 248, 104, 623, 217, + 354, 355, 104, 104, 623, 623, 104, 1124, 483, 1135, + 86, 86, -665, 104, 104, -558, 1137, 73, 212, 212, + 1142, 104, -558, 432, 86, 250, 865, 217, 330, 331, + 1144, 1146, 270, 270, 1148, 125, 86, 86, 1151, 524, + 1161, 1077, 1078, 931, 548, 86, 1152, 1189, 763, 1165, + 357, 358, 359, 360, 432, 925, 86, 86, 484, 432, + -557, -666, -295, 1198, 1199, 434, 764, 104, 104, 104, + 104, 104, 104, 104, 104, -557, 1175, -295, 246, 1134, + 1182, 247, 248, 1029, 947, 1186, 1187, 1188, 1035, 475, + 960, 1190, 104, 925, 549, 925, 434, 925, 865, 925, + 1191, 434, 270, 334, 226, 741, 328, 329, 73, 249, + -557, 250, -295, 104, 675, 677, 104, -557, 104, -295, + 246, 104, 589, 247, 248, 130, 919, 1166, 442, 1160, + 982, 1117, 1052, 925, 954, 1159, 623, 246, 270, 1091, + 247, 248, 514, 492, 695, 865, 86, 86, 675, 677, + 1068, 249, 104, 250, 86, 208, 665, 776, 330, 331, + 1102, 1107, 104, 104, 217, 217, 86, 0, 1011, 0, + 250, 0, 265, -306, 0, 0, 733, 104, 0, 104, + 104, 865, 865, 738, 0, 73, 805, 0, -306, 0, + 104, 0, 73, 73, 104, 744, 922, 743, 104, 922, + 73, 432, 922, 104, 922, 212, 212, 1101, 104, 0, + 0, 121, 865, 1043, 0, 0, 968, 666, 0, 865, + 865, 0, 432, -306, 1037, 1039, 0, 984, 1194, 1195, + -306, 0, 86, 0, 86, 1054, 806, 86, 0, 0, + 992, 104, 0, 434, 0, 0, 73, 0, 768, 769, + 104, 73, 1000, 1001, 1122, 0, 0, 458, 0, 0, + 0, 1007, 562, 0, 434, 328, 329, 0, 104, 432, + 922, 73, 0, 1013, 246, 0, 104, 247, 248, 799, + 778, 0, 969, 931, 217, 0, 931, 0, 931, 86, 86, 0, 73, 0, 0, 472, 0, 73, 121, 0, - 73, 472, 0, 104, 0, 270, 0, 0, 922, 0, - 922, 0, 922, 787, 922, 357, 358, 359, 360, 327, - 328, 329, 0, 104, 834, 562, 743, 0, 328, 329, - 0, 361, 338, 328, 329, 340, 328, 329, 495, 834, - 0, 73, 73, 246, 0, 0, 247, 248, 922, 0, - 86, 217, 217, 0, 931, 0, 363, 857, 73, 86, - 86, 0, 1064, 365, 366, 367, 0, 0, 0, 73, - 1072, 0, 330, 331, 0, 0, 250, 73, 0, 0, - 330, 331, 1079, 554, 833, 330, 331, 73, 330, 331, - 368, 0, 931, 0, 931, 0, 931, 0, 931, 0, - 104, 104, 0, 0, 0, 833, 833, 0, 0, 0, - 555, 328, 329, 890, 560, 328, 329, 834, 0, 270, - 73, 564, 328, 329, 565, 328, 329, 0, 495, 73, - 0, 0, 931, 901, 104, 583, 0, 808, 809, 810, - 0, 0, 579, 121, 0, 121, 1037, 1039, 1125, 0, - 1126, 0, 0, 1127, 945, 73, 0, 0, 812, 0, - 593, 39, 40, 330, 331, 0, 813, 330, 331, 74, - 0, 74, 122, 122, 330, 331, 0, 330, 331, 0, - 122, 758, 328, 329, 0, 246, 833, 833, 247, 248, - 0, 833, 0, 0, 814, 0, 0, 0, 0, 0, - 815, 816, 0, 817, 0, 1162, 1163, 0, 0, 57, - 0, 121, 502, 104, 834, 0, 249, 0, 250, 74, - 834, 104, 104, 122, 763, 104, 357, 358, 359, 360, - 819, 0, 0, 0, 330, 331, 0, 120, 0, 0, - 0, 0, 764, 0, 946, 0, 0, 0, 0, 122, - 1106, 0, 0, 0, 1008, 0, 104, 104, 0, 833, + 73, 472, 0, 104, 1123, 270, 0, 250, 922, 0, + 922, 434, 922, 0, 922, 1113, 947, 330, 331, 0, + 1054, 0, 787, 104, 357, 358, 359, 360, 1106, 0, + 0, 327, 328, 329, 0, 0, 0, 0, 495, 0, + 361, 73, 73, 0, 0, 0, 0, 0, 922, 0, + 86, 217, 217, 0, 931, 0, 0, 857, 73, 86, + 86, 0, 1064, 0, 246, 363, 0, 247, 248, 73, + 1072, 0, 365, 366, 367, 0, 0, 73, 0, 0, + 0, 0, 1079, 554, 330, 331, 0, 73, 1153, 0, + 0, 502, 931, 0, 931, 249, 931, 250, 931, 368, + 104, 104, 1143, 1145, 1147, 0, 1149, 1150, 338, 328, + 329, 832, 1083, 890, 357, 358, 359, 360, 0, 270, + 73, 947, 1113, 340, 328, 329, 832, 0, 495, 73, + 764, 0, 931, 901, 104, 583, 555, 328, 329, 0, + 0, 0, 579, 121, 0, 121, 0, 0, 1125, 0, + 1126, 0, 0, 1127, 0, 73, 1113, 0, 0, 0, + 593, 330, 331, 0, 1181, 1183, 1184, 1185, 0, 74, + 0, 74, 122, 122, 0, 0, 330, 331, 0, 0, + 122, 560, 328, 329, 564, 328, 329, 1196, 0, 330, + 331, -678, -678, -678, 0, -678, -678, 0, -678, 565, + 328, 329, 0, -678, 832, 1162, 1163, 0, 971, 973, + 0, 121, 0, 104, 977, 979, 0, 0, 0, 74, + 0, 104, 104, 122, 763, 104, 357, 358, 359, 360, + 758, 328, 329, 0, 330, 331, 0, 330, 331, 0, + 971, 973, 764, 977, 979, 0, 0, 0, 0, 122, + 0, 0, 330, 331, 1008, 0, 104, 104, 0, 0, 708, 708, 104, 104, 0, 0, 1193, 363, 104, 104, - 1017, 833, 0, 765, 0, 1200, 1201, 0, 416, 417, - 73, 0, 104, 0, 0, 104, 0, 74, 0, 0, - 0, 419, 833, 833, 104, 104, 0, 0, 0, 0, - 0, 0, 0, 104, 0, -678, -678, -678, 0, -678, - -678, 495, -678, 0, 104, 104, 554, -678, 495, 426, - 427, 428, 429, 430, 1143, 1145, 1147, 833, 1149, 1150, - 0, 763, 0, 357, 358, 359, 360, 971, 973, 0, - 0, 0, 834, 977, 979, 767, 0, 0, 103, 764, - 103, 128, 128, 763, 0, 357, 358, 359, 360, 231, - 0, 0, 784, 834, 834, 788, 0, 0, 0, 971, - 973, 764, 977, 979, 363, 0, 0, 0, 74, 1083, - 1016, 357, 358, 359, 360, 419, 1181, 1183, 1184, 1185, - 0, 0, 73, 0, 104, 0, 363, 764, 103, 121, - 73, 73, 317, 0, 104, 104, 0, 246, 0, 1196, - 247, 248, 104, 426, 427, 428, 429, 430, 0, 0, - 0, 0, 104, 104, 104, 0, 0, 0, 317, 0, - 0, -678, 0, 0, 0, 73, 0, -678, 249, 0, - 250, 73, 73, 0, 834, 834, 0, 73, 73, 834, - 0, 0, 0, 0, 0, 74, 0, 0, 0, 0, - 852, 73, 74, 74, 0, 0, 103, 416, 417, 0, - 74, 0, 0, 73, 73, 1061, 0, 0, 0, 0, - 419, 122, 73, 0, 356, 0, 357, 358, 359, 360, - 104, 0, 104, 73, 73, 104, 0, 0, 1061, 0, - 0, 472, 361, 0, 0, 423, 424, 425, 426, 427, - 428, 429, 430, 0, 0, 0, 74, 834, 0, 0, - 121, 74, 0, 0, 0, 121, 0, 363, 0, 834, - 0, 0, 0, 364, 365, 366, 367, 0, 0, 0, + 1017, 0, 0, 765, 0, 1200, 1201, 0, 0, 0, + 73, 0, 104, 330, 331, 104, 0, 74, 0, 0, + 0, 0, 0, 246, 104, 104, 247, 248, 0, 804, + 832, 0, 0, 104, 0, 0, 832, 0, 0, 0, + 0, 495, 0, 0, 104, 104, 554, -678, 495, 0, + 0, 0, 0, -678, 249, 0, 250, 404, 405, 406, + 407, 408, 409, 410, 411, 412, 413, 414, 415, 0, + 0, 0, 0, 416, 417, 767, 1061, 0, 103, 0, + 103, 128, 128, 0, 0, 0, 419, 0, 0, 231, + 0, 0, 784, 0, 0, 788, 0, 0, 0, 1061, + 0, 763, 0, 357, 358, 359, 360, 420, 74, 421, + 422, 423, 424, 425, 426, 427, 428, 429, 430, 764, + 0, 833, 73, 0, 104, 416, 417, -280, 103, 121, + 73, 73, 317, 0, 104, 104, 833, 0, 419, 0, + 0, 0, 104, 763, 363, 357, 358, 359, 360, 0, + 1016, 0, 104, 104, 104, 0, 0, 0, 317, 832, + 0, 764, 0, 0, 0, 73, 426, 427, 428, 429, + 430, 73, 73, 0, 0, 0, 0, 73, 73, 0, + 832, 832, 0, 0, 0, 74, 363, 0, 0, 0, + 852, 73, 74, 74, 416, 417, 103, 0, 0, 0, + 74, 0, 0, 73, 73, 0, 0, 419, 0, 0, + 0, 122, 73, 0, 833, 0, 0, 0, 0, 0, + 104, 0, 104, 73, 73, 104, 0, 0, 0, 834, + 0, 472, 423, 424, 425, 426, 427, 428, 429, 430, + 416, 417, 0, 0, 834, 0, 74, 0, 0, 0, + 121, 74, 0, 419, 0, 121, 0, 0, 0, 0, + 0, 832, 832, 0, 0, 0, 832, 0, 0, 0, 0, 74, 104, 0, 0, 0, 0, 104, 104, 0, - 834, 834, 0, 0, 0, 0, 0, 103, 926, 907, - 0, 368, 74, 0, 369, 0, 0, 74, 122, 0, - 74, 0, 0, 73, 0, 0, 0, 551, 0, 83, - 0, 83, 0, 73, 73, 834, 0, 121, 0, 0, - 227, 73, 0, 0, 0, 0, 100, 0, 100, 127, - 127, 127, 0, 73, 0, 0, 0, 230, 104, 104, - 104, 74, 74, 0, 0, 0, 0, 104, 104, 0, - 0, 0, 416, 417, 0, 0, 0, 0, 74, 83, - 0, 0, 0, 987, 103, 419, 0, 0, 0, 74, + 425, 426, 427, 428, 429, 430, 0, 103, 926, 907, + 0, 0, 74, 0, 0, 0, 0, 74, 122, 0, + 74, 0, 0, 73, 0, 0, 0, 0, 0, 83, + 833, 83, 0, 73, 73, 0, 833, 121, 0, 0, + 227, 73, 834, 0, 0, 0, 100, 0, 100, 127, + 127, 127, 0, 73, 832, 0, 0, 230, 104, 104, + 104, 74, 74, 0, 0, 0, 832, 104, 104, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 74, 83, + 0, 0, 0, 987, 103, 0, 0, 832, 832, 74, 0, 103, 103, 0, 0, 0, 100, 74, 0, 103, - 316, 0, 0, 0, 403, 0, 0, 74, 708, 0, - 317, 472, 425, 426, 427, 428, 429, 430, 0, 73, - 0, 73, 0, 1020, 73, 1022, 316, 0, 0, 1023, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 74, 0, 0, 0, 0, 103, 0, 83, 0, 74, - 103, 404, 405, 406, 407, 408, 409, 410, 411, 412, - 413, 414, 415, 122, 100, 122, 0, 416, 417, 0, - 103, 0, 418, 0, 0, 74, 73, 73, 0, 0, - 419, 0, 0, 0, 356, 0, 357, 358, 359, 360, + 316, 0, 0, 0, 0, 0, 0, 74, 708, 0, + 317, 472, 0, 0, 0, 0, 0, 0, 0, 73, + 0, 73, 832, 1020, 73, 1022, 316, 0, 0, 1023, + 0, 0, 0, 0, 0, 0, 0, 0, 834, 0, + 74, 0, 0, 0, 834, 103, 0, 83, 0, 74, + 103, 0, 0, 0, 0, 0, 0, 0, 0, 833, + 0, 0, 0, 122, 100, 122, 0, 0, 0, 0, + 103, 0, 0, 0, 0, 74, 73, 73, 0, 0, + 833, 833, 0, 0, 0, 0, 0, 0, 0, 0, 0, 103, 0, 0, 0, 0, 103, 317, 0, 624, - 0, 420, 361, 421, 422, 423, 424, 425, 426, 427, - 428, 429, 430, 0, 0, 0, 0, 0, 0, 0, - 472, 0, 0, 0, 0, 0, 472, 363, 0, 1089, - 1090, 122, 0, 364, 365, 366, 367, 73, 83, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 472, 0, 0, 0, 0, 0, 472, 0, 0, 1089, + 1090, 122, 0, 0, 0, 0, 0, 73, 83, 0, 624, 624, 0, 0, 0, 0, 73, 73, 808, 809, 810, 1109, 0, 1112, 0, 100, 0, 103, 0, 0, - 0, 368, 0, 0, 369, 811, 0, 0, 103, 812, - 0, 0, 39, 40, 0, 1104, 103, 813, 0, 0, - 0, 0, 0, 0, 0, 0, 103, 0, 0, 0, + 0, 833, 833, 0, 0, 945, 833, 834, 103, 812, + 0, 0, 39, 40, 41, 0, 103, 42, 0, 0, + 0, 0, 0, 0, 0, 0, 103, 0, 834, 834, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 74, 0, 0, 0, 0, 814, 1136, 472, 472, 1138, - 0, 815, 816, 0, 817, 83, 818, 0, 0, 103, - 57, 0, 83, 83, 0, 0, 0, 0, 103, 0, - 83, 0, 100, 0, 0, 0, 0, 0, 0, 100, - 100, 819, 317, 0, 317, 0, 0, 100, 120, 0, - 0, 0, 1167, 0, 103, 0, 0, 1169, 316, 1171, - 0, 0, 0, 1172, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 83, 0, 0, 0, + 74, 0, 0, 0, 0, 813, 1136, 472, 472, 1138, + 0, 814, 815, 0, 816, 83, 0, 0, 0, 103, + 57, 58, 83, 83, 0, 0, 0, 0, 103, 0, + 83, 0, 100, 0, 833, 0, 0, 0, 0, 100, + 100, 818, 317, 0, 317, 0, 833, 100, 120, 0, + 0, 0, 1167, 0, 103, 946, 0, 1169, 316, 1171, + 0, 0, 0, 1172, 0, 0, 0, 833, 833, 834, + 834, 0, 0, 0, 834, 0, 83, 0, 0, 0, 0, 83, 0, 0, 0, 0, 356, 0, 357, 358, 359, 360, 0, 100, 0, 0, 0, 1192, 100, 0, - 0, 83, 0, 0, 361, 0, 0, 0, 472, 0, + 0, 83, 833, 0, 361, 0, 0, 0, 472, 0, 317, 0, 74, 0, 0, 0, 0, 0, 100, 122, 74, 74, 83, 0, 472, 472, 0, 83, 0, 363, 619, 0, 0, 0, 0, 364, 365, 366, 367, 100, - 0, 0, 0, 0, 100, 316, 356, 0, 357, 358, - 359, 360, 0, 0, 0, 74, 0, 0, 0, 0, + 0, 0, 834, 0, 100, 316, 356, 0, 357, 358, + 359, 360, 0, 0, 834, 74, 0, 0, 0, 0, 0, 74, 74, 368, 361, 0, 369, 74, 74, 0, - 0, 619, 619, 0, 0, 0, 0, 0, 362, 103, + 0, 619, 619, 0, 0, 834, 834, 1104, 362, 103, 0, 74, 0, 0, 0, 0, 0, 0, 83, 363, 0, 0, 0, 74, 74, 364, 365, 366, 367, 83, - 0, 0, 74, 0, 0, 100, 0, 83, 808, 809, - 810, 0, 0, 74, 74, 0, 100, 83, 0, 0, - 0, 0, 0, 368, 100, 945, 369, 0, 0, 812, - 0, 0, 39, 40, 100, 0, 0, 813, 0, 370, - 122, 0, 0, 0, 0, 122, 0, 0, 0, 0, + 0, 0, 74, 0, 0, 100, 0, 83, 0, 0, + 834, 0, 0, 74, 74, 0, 100, 83, 0, 0, + 0, 0, 0, 368, 100, 0, 369, 0, 356, 0, + 357, 358, 359, 360, 100, 0, 0, 0, 0, 370, + 122, 0, 0, 0, 0, 122, 361, 0, 0, 0, 83, 0, 0, 0, 0, 0, 0, 0, 0, 83, - 0, 0, 0, 0, 0, 814, 0, 100, 0, 0, - 0, 815, 816, 0, 817, 0, 100, 0, 0, 0, - 57, 0, 0, 0, 0, 83, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, + 0, 363, 0, 0, 0, 0, 100, 364, 365, 366, + 367, 0, 0, 0, 0, 83, 0, 0, 0, 0, 316, 103, 316, 74, 0, 0, 0, 0, 317, 103, - 624, 819, 100, 74, 74, 0, 0, 122, 120, 0, - 0, 74, 0, 0, 808, 809, 810, 0, 0, 0, - 0, 0, 0, 74, 0, 0, 0, 0, 0, 0, - 0, 945, 0, 0, 624, 812, 0, 0, 39, 40, - 624, 624, 0, 813, 0, 0, 103, 103, 0, 0, - 101, 0, 101, 0, 0, 0, 0, 0, 316, 0, - 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 814, 103, 103, 0, 0, 0, 815, 816, 0, - 0, 103, 0, 0, 0, 0, 57, 0, 0, 74, - 0, 74, 103, 103, 74, 0, 0, 0, 0, 0, - 101, 0, 0, 0, 0, 0, 0, 819, 0, 0, - 83, 0, 0, 0, 120, 0, 0, 0, 0, 128, - 0, 0, 0, 0, 128, 0, 0, 100, 0, 0, - 0, 0, 0, 0, 0, 0, -692, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 74, 74, 0, -692, - -692, -692, -692, -692, -692, 0, -692, 0, 0, 0, - 0, -692, -692, -692, 0, 0, 0, 0, 101, 0, + 624, 0, 100, 74, 74, 368, 0, 122, 369, 0, + 0, 74, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 551, 0, 74, 0, 0, 0, 356, 0, 357, + 358, 359, 360, 0, 624, 0, 0, 0, 0, 0, + 624, 624, 808, 809, 810, 361, 103, 103, 0, 0, + 101, 0, 101, 0, 0, 0, 0, 0, 316, 945, + 103, 581, 0, 812, 0, 0, 39, 40, 41, 0, + 363, 42, 103, 103, 0, 0, 364, 365, 366, 367, + 356, 103, 357, 358, 359, 360, 0, 0, 0, 74, + 0, 74, 103, 103, 74, 0, 0, 0, 361, 813, + 101, 0, 0, 0, 368, 814, 815, 369, 816, 0, + 83, 0, 0, 0, 57, 58, 0, 0, 0, 128, + 0, 0, 0, 363, 128, 0, 0, 100, 0, 364, + 365, 366, 367, 0, 0, 818, -692, 0, 0, 0, + 0, 0, 120, 0, 0, 0, 74, 74, 0, -692, + -692, -692, -692, -692, -692, 0, -692, 368, 0, 0, + 369, -692, -692, -692, 0, 0, 0, 0, 101, 0, 0, 0, 624, -692, -692, 0, -692, -692, -692, -692, -692, 0, 103, 103, 0, 0, 1070, 0, 0, 0, 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -3775,40 +3779,40 @@ static const yytype_int16 yytable[] = 100, 100, 0, 83, 83, 103, 103, 0, 0, 100, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 100, 100, 0, 101, 101, 0, 288, 290, 291, 292, - 0, 101, 0, 266, 308, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 345, 346, 127, 0, 0, - 0, 0, 127, 0, 0, 356, 0, 357, 358, 359, - 360, 0, 0, 0, 0, 0, 103, 0, 0, 0, - 0, 0, 0, 361, 0, 103, 103, 101, 0, 0, - 0, 0, 101, 619, 0, 0, 0, 362, 0, 0, + 0, 101, 0, 266, 308, 356, 0, 357, 358, 359, + 360, 0, 0, 0, 0, 345, 346, 127, 0, 0, + 0, 0, 127, 361, 0, 356, 0, 357, 358, 359, + 360, 0, 0, 0, 0, 0, 103, 0, 0, 779, + 0, 0, 0, 361, 0, 103, 103, 101, 363, 0, + 0, 0, 101, 619, 364, 365, 366, 367, 0, 903, 0, 0, 0, 83, 83, 0, 0, 1067, 363, 0, 0, 83, 101, 0, 364, 365, 366, 367, 0, 0, - 100, 100, 0, 83, 1069, 0, 0, 0, 100, 0, - 0, 0, 0, 101, 0, -692, 0, 0, 101, 0, - 100, 101, 368, 0, 0, 369, 0, 0, 0, 0, - 0, 0, 356, 0, 357, 358, 359, 360, 370, 0, + 100, 100, 368, 83, 1069, 369, 0, 0, 100, 808, + 809, 810, 0, 101, 0, 0, 0, 0, 101, 0, + 100, 101, 368, 0, 0, 369, 811, 0, 0, 0, + 812, 0, 0, 39, 40, 41, 0, 0, 42, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 361, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 101, 101, 0, 0, 581, 0, 0, 83, - 0, 83, 0, 0, 83, 363, 0, 0, 0, 101, - 0, 364, 365, 366, 367, 804, 100, 0, 100, 0, - 101, 100, 0, 0, 0, 0, 0, 0, 101, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 101, 368, - 0, 0, 369, 404, 405, 406, 407, 408, 409, 410, - 411, 412, 413, 414, 415, 0, 83, 83, 0, 416, - 417, 498, 499, 500, 345, 0, 0, 0, 0, 0, - 0, 101, 419, 100, 100, 266, 0, 0, 266, 0, - 101, 0, 0, 0, 0, 0, 0, 356, 0, 357, - 358, 359, 360, 420, 0, 421, 422, 423, 424, 425, - 426, 427, 428, 429, 430, 361, 101, 356, 0, 357, - 358, 359, 360, -280, 0, 0, 0, 83, 0, 0, - 0, 779, 0, 0, 0, 361, 83, 83, 0, 0, - 363, 0, 0, 0, 100, 0, 364, 365, 366, 367, - 0, 903, 0, 100, 100, 0, 0, 0, 0, 0, - 363, 0, 0, 0, 0, 0, 364, 365, 366, 367, - 0, 0, 0, 0, 368, 0, 0, 369, 0, 0, - 0, 0, 0, 0, 586, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 368, 596, 0, 369, 0, 0, + 0, 0, 101, 101, 0, 0, 813, 0, 0, 83, + 0, 83, 814, 815, 83, 816, 0, 817, 0, 101, + 0, 57, 58, 0, 0, 0, 100, 0, 100, 0, + 101, 100, 0, 0, 0, 808, 809, 810, 101, 0, + 0, 0, 818, 0, 0, 0, 0, 0, 101, 120, + 0, 0, 945, 0, 0, 0, 812, 0, 0, 39, + 40, 41, 0, 0, 42, 0, 83, 83, 0, 0, + 0, 498, 499, 500, 345, 0, 0, 0, 0, 0, + 0, 101, 0, 100, 100, 266, 0, 0, 266, 0, + 101, 0, 813, 0, 0, 0, 0, 0, 814, 815, + 356, 0, 357, 358, 359, 360, 0, 57, 58, 0, + 0, 0, 0, 0, 0, 0, 101, 0, 361, 0, + 0, 0, 0, 0, 0, 0, 0, 83, 818, 0, + 0, 0, 362, 0, 0, 120, 83, 83, 0, 0, + 0, 0, 0, 363, 100, 0, 0, 0, 0, 364, + 365, 366, 367, 100, 100, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -692, 0, 0, 0, 0, 0, 0, 368, 0, 0, + 369, 0, 0, 0, 586, 0, 0, 0, 0, 0, + 0, 0, 0, 370, 0, 596, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 609, 0, 0, 0, 0, 620, 0, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 0, 641, @@ -3846,13 +3850,13 @@ static const yytype_int16 yytable[] = -420, 0, 0, -420, 101, 101, 728, 308, 0, -420, 0, -420, 101, 0, 0, -420, 0, 0, 0, 0, 0, 0, 0, 0, 101, -420, 0, 0, -420, -420, - 0, 0, -420, 0, -420, -420, -420, -420, -420, -420, + 403, 0, -420, 0, -420, -420, -420, -420, -420, -420, -420, -420, -420, -420, 0, 0, 0, 0, -420, -420, -420, -420, -420, 0, 275, -420, -420, -420, -420, 0, 0, 0, 0, 0, 0, 0, 0, 0, 934, 0, 0, 0, 0, 674, 937, 0, 266, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 0, - 101, 0, 101, 416, 417, 101, 0, 0, 501, 0, + 101, 0, 101, 416, 417, 101, 0, 0, 418, 0, 0, 0, 0, 0, 0, 0, 419, 674, 674, 0, 0, 0, 728, 674, 674, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 420, 0, 421, @@ -4860,283 +4864,289 @@ static const yytype_int16 yytable[] = 424, 425, 426, 427, 428, 429, 430, 0, 0, 0, 0, 0, 0, 0, 0, -285, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 0, 0, - 0, 0, 416, 417, 0, 0, 0, 0, 0, 0, + 0, 0, 416, 417, 0, 0, 0, 501, 0, 0, 0, 0, 0, 0, 0, 419, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 420, 0, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 404, 405, - 406, 407, 408, 409, 410, 411, 412, 413, -693, -693, - 0, 0, 0, 0, 416, 417, 404, 405, 406, 407, - 408, 409, 0, 0, 412, 413, 0, 419, 0, 0, - 0, 0, 416, 417, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 419, 0, 0, 0, 0, + 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, + 0, 0, 0, 0, 416, 417, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 419, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 420, 0, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, - 0, 0, 0, 0, 0, 0, 0, 0, 421, 422, - 423, 424, 425, 426, 427, 428, 429, 430 + 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, + -693, -693, 0, 0, 0, 0, 416, 417, 404, 405, + 406, 407, 408, 409, 0, 0, 412, 413, 0, 419, + 0, 0, 0, 0, 416, 417, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 419, 0, 0, + 0, 0, 421, 422, 423, 424, 425, 426, 427, 428, + 429, 430, 0, 0, 0, 0, 0, 0, 0, 0, + 421, 422, 423, 424, 425, 426, 427, 428, 429, 430 }; static const yytype_int16 yycheck[] = { 1, 26, 271, 8, 9, 86, 87, 27, 88, 14, 378, 490, 21, 481, 13, 313, 284, 3, 6, 20, - 65, 118, 597, 402, 793, 13, 6, 9, 27, 331, - 4, 5, 14, 508, 671, 672, 55, 1011, 12, 27, - 594, 640, 765, 81, 15, 16, 25, 68, 19, 433, - 504, 74, 53, 54, 508, 51, 594, 306, 15, 16, - 594, 310, 19, 597, 539, 547, 1, 26, 3, 57, - 221, 295, 73, 74, 458, 431, 318, 57, 816, 435, - 25, 55, 438, 25, 402, 967, 815, 74, 25, 14, - 29, 475, 821, 20, 21, 104, 53, 54, 15, 16, - 484, 51, 19, 459, 694, 1036, 373, 81, 698, 25, - 111, 25, 117, 26, 79, 705, 443, 444, 474, 718, + 65, 118, 597, 814, 793, 13, 6, 9, 27, 820, + 4, 5, 14, 508, 671, 672, 55, 402, 12, 27, + 594, 640, 765, 81, 15, 16, 331, 68, 19, 433, + 504, 74, 53, 54, 508, 51, 1036, 306, 15, 16, + 594, 310, 19, 597, 539, 1011, 1, 815, 3, 57, + 221, 26, 73, 74, 458, 431, 318, 57, 967, 435, + 594, 55, 438, 25, 402, 34, 547, 0, 25, 14, + 74, 475, 26, 20, 21, 104, 53, 54, 144, 18, + 484, 20, 51, 459, 694, 295, 373, 81, 698, 25, + 111, 57, 117, 28, 25, 705, 443, 444, 474, 718, 476, 59, 60, 61, 62, 443, 444, 394, 370, 485, - 25, 613, 1046, 15, 16, 122, 217, 19, 25, 93, - 73, 74, 1, 68, 3, 4, 5, 228, 840, 8, - 9, 26, 102, 12, 846, 14, 15, 16, 51, 1133, - 19, 126, 103, 142, 55, 549, 1048, 90, 387, 525, - 389, 53, 126, 332, 113, 399, 335, 104, 337, 138, - 339, 57, 341, 0, 111, 112, 57, 128, 121, 122, - 213, 214, 51, 460, 550, 16, 55, 142, 144, 144, - 142, 28, 129, 757, 138, 142, 65, 144, 289, 102, - 121, 1142, 213, 214, 851, 90, 770, 222, 223, 757, - 90, 92, 81, 757, 147, 138, 142, 214, 142, 967, - 105, 144, 770, 90, 315, 105, 770, 1151, 51, 90, - 222, 223, 55, 90, 513, 121, 139, 142, 142, 547, - 121, 90, 121, 241, 90, 142, 558, 121, 117, 793, - 119, 241, 737, 138, 1033, 140, 105, 1036, 269, 144, - 271, 90, 147, 142, 275, 313, 297, 147, 283, 284, - 213, 214, 736, 737, 305, 306, 269, 92, 271, 310, - 147, 15, 16, 1016, 115, 19, 147, 118, 119, 295, - 147, 140, 92, 92, 275, 144, 92, 278, 147, 551, - 1048, 147, 92, 92, 693, 613, 1045, 34, 18, 801, - 20, 278, 305, 90, 144, 146, 251, 148, 147, 53, - 54, 121, 121, 908, 51, 121, 92, 90, 90, 313, - 125, 121, 121, 16, 349, 350, 351, 352, 902, 354, - 355, 210, 397, 142, 90, 92, 142, 402, 912, 294, - 457, 278, 348, 222, 223, 121, 90, 349, 350, 351, - 352, 92, 297, 1142, 908, 376, 62, 378, 64, 65, - 147, 400, 121, 92, 121, 318, 324, 51, 842, 53, - 54, 55, 56, 275, 147, 147, 278, 556, 443, 444, - 121, 60, 92, 399, 63, 69, 55, 18, 51, 777, - 878, 147, 121, 348, 25, 682, 275, 1007, 353, 278, - 55, 142, 806, 1152, 283, 284, 400, 34, 287, 25, - 116, 117, 433, 142, 92, 294, 295, 370, 92, 92, - 142, 92, 115, 302, 51, 118, 119, 435, 107, 805, - 438, 807, 92, 92, 313, 20, 461, 458, 726, 20, - 25, 730, 433, 121, 142, 466, 57, 121, 121, 142, - 121, 459, 138, 146, 475, 148, 119, 502, 142, 1033, - 101, 121, 121, 484, 142, 803, 141, 458, 476, 348, + 25, 15, 16, 15, 16, 19, 217, 19, 122, 142, + 73, 74, 1, 68, 3, 4, 5, 228, 25, 8, + 9, 29, 613, 12, 25, 14, 15, 16, 1046, 1048, + 19, 138, 1142, 57, 51, 549, 55, 90, 387, 525, + 389, 53, 90, 332, 79, 121, 335, 104, 337, 25, + 339, 90, 341, 138, 111, 112, 51, 1133, 121, 122, + 213, 214, 51, 460, 550, 16, 55, 121, 92, 121, + 142, 92, 129, 757, 138, 142, 65, 144, 289, 399, + 144, 16, 213, 214, 851, 102, 770, 222, 223, 967, + 90, 126, 81, 757, 147, 34, 142, 121, 144, 147, + 214, 142, 144, 90, 315, 113, 770, 102, 147, 125, + 222, 223, 51, 757, 513, 90, 90, 142, 105, 547, + 92, 90, 139, 241, 1045, 90, 770, 55, 117, 793, + 119, 241, 737, 1151, 1033, 142, 105, 1036, 269, 60, + 271, 142, 63, 558, 275, 313, 297, 147, 283, 284, + 213, 214, 736, 737, 305, 306, 269, 93, 271, 310, + 147, 15, 16, 1016, 115, 19, 142, 118, 119, 295, + 1048, 140, 147, 147, 275, 144, 92, 278, 147, 551, + 115, 121, 147, 118, 119, 613, 107, 58, 59, 90, + 126, 278, 305, 90, 92, 146, 251, 148, 693, 53, + 54, 121, 142, 908, 25, 121, 92, 142, 90, 313, + 801, 146, 55, 148, 349, 350, 351, 352, 902, 354, + 355, 210, 397, 121, 27, 92, 142, 402, 912, 294, + 457, 1152, 348, 222, 223, 121, 90, 349, 350, 351, + 352, 92, 297, 1142, 908, 376, 147, 378, 92, 142, + 147, 400, 92, 92, 121, 318, 324, 51, 842, 53, + 54, 55, 56, 275, 278, 147, 278, 556, 443, 444, + 121, 20, 92, 399, 20, 69, 25, 121, 51, 777, + 878, 121, 121, 348, 103, 682, 275, 1007, 353, 278, + 667, 142, 806, 670, 283, 284, 400, 62, 287, 64, + 65, 121, 433, 142, 92, 294, 295, 370, 101, 128, + 51, 688, 115, 302, 55, 118, 119, 435, 18, 805, + 438, 807, 142, 840, 313, 25, 461, 458, 726, 846, + 142, 730, 433, 121, 57, 466, 129, 130, 131, 132, + 133, 459, 138, 146, 475, 148, 119, 502, 142, 1033, + 101, 116, 117, 484, 142, 803, 145, 458, 476, 348, 349, 350, 351, 352, 353, 354, 355, 485, 747, 1033, - 145, 521, 1036, 801, 475, 58, 59, 545, 435, 547, + 141, 521, 1036, 801, 475, 58, 59, 545, 435, 547, 837, 512, 513, 484, 373, 397, 843, 844, 587, 837, 402, 522, 521, 121, 26, 843, 844, 794, 1007, 512, - 513, 1130, 459, 521, 1009, 394, 1126, 525, 397, 815, - 399, 400, 623, 402, 139, 821, 55, 504, 549, 476, + 513, 1130, 459, 521, 1009, 394, 1126, 525, 397, 139, + 399, 400, 623, 402, 37, 38, 55, 504, 549, 476, 142, 275, 16, 101, 278, 1009, 924, 925, 485, 101, - 92, 92, 550, 782, 783, 784, 57, 786, 836, 788, - 721, 545, 121, 547, 433, 613, 40, 41, 549, 536, - 121, 1135, 849, 142, 443, 444, 511, 522, 90, 121, - 121, 51, 1146, 1162, 1163, 142, 531, 142, 525, 458, - 51, 460, 461, 105, 142, 606, 37, 38, 1142, 142, - 142, 493, 471, 584, 58, 59, 475, 642, 551, 888, - 479, 17, 18, 550, 1193, 484, 894, 610, 895, 51, - 489, 1200, 1201, 604, 40, 41, 138, 121, 140, 613, + 92, 92, 550, 782, 783, 784, 92, 786, 836, 788, + 721, 545, 57, 547, 433, 613, 40, 41, 549, 536, + 92, 1135, 849, 121, 443, 444, 511, 522, 90, 121, + 121, 121, 1146, 1162, 1163, 121, 531, 142, 525, 458, + 121, 460, 461, 105, 51, 606, 92, 142, 1142, 121, + 142, 493, 471, 584, 17, 18, 475, 642, 551, 888, + 479, 92, 142, 550, 1193, 484, 894, 610, 895, 51, + 489, 1200, 1201, 604, 142, 121, 138, 51, 140, 613, 99, 26, 144, 101, 287, 147, 142, 69, 975, 658, - 15, 115, 295, 13, 118, 119, 121, 975, 667, 302, - 661, 670, 16, 522, 920, 921, 667, 943, 121, 670, - 671, 672, 531, 131, 132, 133, 681, 63, 661, 101, - 102, 103, 146, 15, 148, 139, 545, 688, 547, 965, - 549, 667, 693, 694, 670, 145, 716, 698, 557, 681, - 1, 142, 3, 691, 705, 90, 128, 8, 9, 433, - 715, 691, 688, 14, 15, 16, 145, 716, 19, 986, - 105, 90, 121, 122, 793, 584, 747, 16, 716, 730, - 881, 658, 142, 715, 458, 1103, 105, 142, 721, 1123, - 667, 16, 15, 670, 15, 604, 44, 730, 142, 121, - 51, 475, 141, 138, 613, 140, 399, 90, 803, 686, - 484, 688, 147, 801, 65, 141, 15, 847, 1124, 1045, - 1046, 140, 105, 18, 1050, 141, 777, 27, 147, 736, - 504, 90, 141, 26, 508, 40, 41, 42, 43, 44, - 139, 90, 837, 838, 15, 139, 105, 139, 843, 844, - 141, 1020, 1021, 1022, 1023, 806, 105, 140, 142, 44, - 57, 693, 536, 142, 147, 539, 117, 587, 119, 807, - 142, 591, 681, 682, 142, 549, 115, 801, 471, 118, - 119, 140, 142, 44, 57, 806, 479, 55, 147, 990, - 115, 140, 1118, 118, 119, 996, 489, 90, 147, 90, - 851, 51, 51, 90, 57, 142, 715, 146, 142, 148, - 861, 866, 105, 864, 105, 115, 1134, 802, 118, 119, - 142, 146, 14, 148, 15, 1151, 1152, 93, 15, 1, - 807, 3, 4, 5, 866, 842, 15, 888, 145, 51, - 12, 53, 54, 55, 56, 138, 146, 140, 148, 140, - 142, 144, 142, 142, 147, 888, 147, 69, 142, 210, - 1186, 142, 115, 90, 557, 118, 119, 15, 142, 1138, - 123, 222, 223, 924, 925, 142, 1077, 1078, 105, 51, - 975, 141, 141, 55, 62, 794, 64, 65, 139, 793, - 142, 51, 801, 802, 803, 148, 299, 806, 922, 142, - 303, 37, 38, 927, 61, 142, 55, 64, 65, 81, - 15, 1030, 26, 140, 1033, 139, 139, 1036, 15, 1038, - 147, 15, 115, 15, 275, 118, 119, 278, 837, 838, - 142, 90, 283, 284, 843, 844, 287, 1068, 116, 117, - 849, 850, 142, 294, 295, 126, 105, 119, 1003, 1004, - 126, 302, 55, 146, 863, 148, 1007, 866, 142, 116, - 117, 15, 736, 737, 139, 989, 875, 876, 55, 142, - 15, 1003, 1004, 793, 142, 884, 90, 1178, 142, 115, - 142, 140, 118, 119, 142, 1104, 895, 896, 147, 142, - 90, 105, 90, 1194, 1195, 142, 625, 348, 349, 350, - 351, 352, 353, 354, 355, 105, 15, 105, 142, 1084, - 146, 640, 148, 922, 142, 144, 115, 144, 927, 118, - 119, 90, 373, 1142, 138, 1144, 140, 1146, 1079, 1148, - 144, 115, 806, 147, 118, 119, 105, 141, 210, 15, - 140, 90, 140, 394, 15, 40, 397, 147, 399, 147, - 41, 402, 1103, 142, 115, 522, 105, 118, 119, 142, - 12, 5, 1126, 1182, 90, 1133, 975, 793, 842, 848, - 1050, 140, 1123, 964, 1125, 1126, 985, 986, 147, 105, - 989, 26, 433, 816, 993, 146, 1124, 148, 253, 718, - 1125, 140, 443, 444, 1003, 1004, 1005, 6, 147, 587, - 1030, -1, 1123, -1, 90, 1033, 509, 458, -1, 460, - 461, 1162, 1163, 516, 140, 287, -1, -1, -1, 105, - 471, 147, 294, 295, 475, 528, 1030, -1, 479, 1033, - 302, -1, 1036, 484, 1038, 1190, 1191, 90, 489, -1, - -1, 313, 1193, -1, -1, 90, -1, 1124, -1, 1200, - 1201, -1, 105, -1, 140, -1, -1, 850, 1190, 1191, - 105, 147, 1071, -1, 1073, -1, -1, 1076, -1, -1, - 863, 522, -1, 443, 444, -1, 348, -1, 581, 582, - 531, 353, 875, 876, -1, -1, 815, 140, -1, -1, - -1, 884, 821, 138, 147, 140, -1, -1, 549, 144, - 1104, 373, 147, 896, -1, -1, 557, 477, 478, 612, - 1030, -1, -1, 1033, 1123, -1, 1036, -1, 1038, 1128, + 121, 115, 295, 142, 118, 119, 92, 975, 667, 302, + 661, 670, 13, 522, 115, 15, 667, 118, 119, 670, + 671, 672, 531, 131, 132, 133, 681, 121, 661, 101, + 102, 103, 146, 121, 148, 121, 545, 688, 547, 16, + 549, 115, 693, 694, 118, 119, 716, 698, 557, 681, + 1, 63, 3, 691, 705, 90, 128, 8, 9, 433, + 715, 691, 15, 14, 15, 16, 145, 716, 19, 986, + 105, 90, 40, 41, 793, 584, 747, 16, 716, 730, + 881, 658, 145, 715, 458, 1103, 105, 139, 721, 1123, + 667, 16, 142, 670, 142, 604, 142, 730, 920, 921, + 51, 475, 15, 138, 613, 140, 399, 90, 803, 686, + 484, 688, 147, 801, 65, 121, 122, 847, 1124, 15, + 142, 140, 105, 44, 26, 121, 777, 141, 147, 736, + 504, 141, 15, 18, 508, 26, 141, 141, 15, 141, + 26, 90, 837, 838, 139, 139, 139, 44, 843, 844, + 142, 1020, 1021, 1022, 1023, 806, 105, 140, 57, 57, + 142, 693, 536, 142, 147, 539, 117, 587, 119, 807, + 142, 591, 681, 682, 44, 549, 115, 801, 471, 118, + 119, 142, 55, 51, 51, 806, 479, 26, 90, 990, + 115, 140, 142, 118, 119, 996, 489, 142, 147, 90, + 851, 90, 142, 105, 90, 15, 715, 146, 14, 148, + 861, 866, 93, 864, 105, 15, 1134, 802, 15, 105, + 142, 146, 145, 148, 40, 41, 42, 43, 44, 1, + 807, 3, 4, 5, 866, 842, 138, 888, 140, 142, + 12, 142, 144, 51, 15, 147, 141, 138, 142, 140, + 142, 90, 138, 144, 140, 888, 147, 55, 144, 210, + 142, 147, 141, 90, 557, 139, 105, 142, 15, 1138, + 142, 222, 223, 924, 925, 139, 1077, 1078, 105, 51, + 975, 142, 142, 55, 15, 794, 15, 15, 139, 793, + 142, 126, 801, 802, 803, 90, 299, 806, 922, 138, + 303, 140, 126, 927, 62, 144, 64, 65, 147, 81, + 105, 1030, 55, 140, 1033, 55, 142, 1036, 139, 1038, + 147, 15, 115, 142, 275, 118, 119, 278, 837, 838, + 37, 38, 283, 284, 843, 844, 287, 1068, 90, 142, + 849, 850, 144, 294, 295, 140, 142, 119, 1003, 1004, + 142, 302, 147, 105, 863, 148, 1007, 866, 116, 117, + 142, 142, 736, 737, 142, 989, 875, 876, 142, 90, + 15, 1003, 1004, 793, 90, 884, 142, 1178, 51, 15, + 53, 54, 55, 56, 105, 1104, 895, 896, 140, 105, + 90, 144, 90, 1194, 1195, 147, 69, 348, 349, 350, + 351, 352, 353, 354, 355, 105, 141, 105, 115, 1084, + 142, 118, 119, 922, 814, 142, 15, 15, 927, 140, + 820, 40, 373, 1142, 140, 1144, 147, 1146, 1079, 1148, + 41, 147, 806, 61, 12, 522, 64, 65, 210, 146, + 140, 148, 140, 394, 443, 444, 397, 147, 399, 147, + 115, 402, 1103, 118, 119, 5, 793, 1133, 832, 1126, + 848, 1050, 964, 1182, 815, 1125, 975, 115, 842, 142, + 118, 119, 1123, 253, 1125, 1126, 985, 986, 477, 478, + 989, 146, 433, 148, 993, 6, 1124, 587, 116, 117, + 1030, 1033, 443, 444, 1003, 1004, 1005, -1, 146, -1, + 148, -1, 1123, 90, -1, -1, 509, 458, -1, 460, + 461, 1162, 1163, 516, -1, 287, 90, -1, 105, -1, + 471, -1, 294, 295, 475, 528, 1030, 526, 479, 1033, + 302, 105, 1036, 484, 1038, 1190, 1191, 90, 489, -1, + -1, 313, 1193, 943, -1, -1, 57, 1124, -1, 1200, + 1201, -1, 105, 140, 929, 930, -1, 850, 1190, 1191, + 147, -1, 1071, -1, 1073, 965, 140, 1076, -1, -1, + 863, 522, -1, 147, -1, -1, 348, -1, 581, 582, + 531, 353, 875, 876, 90, -1, -1, 140, -1, -1, + -1, 884, 61, -1, 147, 64, 65, -1, 549, 105, + 1104, 373, -1, 896, 115, -1, 557, 118, 119, 612, + 1030, -1, 123, 1033, 1123, -1, 1036, -1, 1038, 1128, 1129, -1, 394, -1, -1, 215, -1, 399, 400, -1, - 402, 221, -1, 584, -1, 1009, -1, -1, 1142, -1, - 1144, -1, 1146, 51, 1148, 53, 54, 55, 56, 63, - 64, 65, -1, 604, 625, 61, 526, -1, 64, 65, - -1, 69, 63, 64, 65, 63, 64, 65, 258, 640, - -1, 443, 444, 115, -1, -1, 118, 119, 1182, -1, - 1189, 1190, 1191, -1, 1104, -1, 94, 690, 460, 1198, - 1199, -1, 985, 101, 102, 103, -1, -1, -1, 471, - 993, -1, 116, 117, -1, -1, 148, 479, -1, -1, - 116, 117, 1005, 318, 943, 116, 117, 489, 116, 117, - 128, -1, 1142, -1, 1144, -1, 1146, -1, 1148, -1, - 681, 682, -1, -1, -1, 964, 965, -1, -1, -1, - 63, 64, 65, 746, 63, 64, 65, 718, -1, 1123, - 522, 63, 64, 65, 63, 64, 65, -1, 348, 531, - -1, -1, 1182, 766, 715, 370, -1, 34, 35, 36, - -1, -1, 362, 545, -1, 547, 929, 930, 1071, -1, - 1073, -1, -1, 1076, 51, 557, -1, -1, 55, -1, - 380, 58, 59, 116, 117, -1, 63, 116, 117, 1, - -1, 3, 4, 5, 116, 117, -1, 116, 117, -1, - 12, 63, 64, 65, -1, 115, 1045, 1046, 118, 119, - -1, 1050, -1, -1, 91, -1, -1, -1, -1, -1, - 97, 98, -1, 100, -1, 1128, 1129, -1, -1, 106, - -1, 613, 142, 794, 815, -1, 146, -1, 148, 51, - 821, 802, 803, 55, 51, 806, 53, 54, 55, 56, - 127, -1, -1, -1, 116, 117, -1, 134, -1, -1, - -1, -1, 69, -1, 141, -1, -1, -1, -1, 81, - 1033, -1, -1, -1, 887, -1, 837, 838, -1, 1118, + 402, 221, -1, 584, 140, 1009, -1, 148, 1142, -1, + 1144, 147, 1146, -1, 1148, 1045, 1046, 116, 117, -1, + 1050, -1, 51, 604, 53, 54, 55, 56, 1033, -1, + -1, 63, 64, 65, -1, -1, -1, -1, 258, -1, + 69, 443, 444, -1, -1, -1, -1, -1, 1182, -1, + 1189, 1190, 1191, -1, 1104, -1, -1, 690, 460, 1198, + 1199, -1, 985, -1, 115, 94, -1, 118, 119, 471, + 993, -1, 101, 102, 103, -1, -1, 479, -1, -1, + -1, -1, 1005, 318, 116, 117, -1, 489, 1118, -1, + -1, 142, 1142, -1, 1144, 146, 1146, 148, 1148, 128, + 681, 682, 1107, 1108, 1109, -1, 1111, 1112, 63, 64, + 65, 625, 51, 746, 53, 54, 55, 56, -1, 1123, + 522, 1151, 1152, 63, 64, 65, 640, -1, 348, 531, + 69, -1, 1182, 766, 715, 370, 63, 64, 65, -1, + -1, -1, 362, 545, -1, 547, -1, -1, 1071, -1, + 1073, -1, -1, 1076, -1, 557, 1186, -1, -1, -1, + 380, 116, 117, -1, 1169, 1170, 1171, 1172, -1, 1, + -1, 3, 4, 5, -1, -1, 116, 117, -1, -1, + 12, 63, 64, 65, 63, 64, 65, 1192, -1, 116, + 117, 13, 14, 15, -1, 17, 18, -1, 20, 63, + 64, 65, -1, 25, 718, 1128, 1129, -1, 837, 838, + -1, 613, -1, 794, 843, 844, -1, -1, -1, 51, + -1, 802, 803, 55, 51, 806, 53, 54, 55, 56, + 63, 64, 65, -1, 116, 117, -1, 116, 117, -1, + 869, 870, 69, 872, 873, -1, -1, -1, -1, 81, + -1, -1, 116, 117, 887, -1, 837, 838, -1, -1, 480, 481, 843, 844, -1, -1, 1189, 94, 849, 850, - 903, 1130, -1, 100, -1, 1198, 1199, -1, 88, 89, - 682, -1, 863, -1, -1, 866, -1, 119, -1, -1, - -1, 101, 1151, 1152, 875, 876, -1, -1, -1, -1, - -1, -1, -1, 884, -1, 13, 14, 15, -1, 17, - 18, 531, 20, -1, 895, 896, 551, 25, 538, 129, - 130, 131, 132, 133, 1107, 1108, 1109, 1186, 1111, 1112, - -1, 51, -1, 53, 54, 55, 56, 837, 838, -1, - -1, -1, 943, 843, 844, 580, -1, -1, 1, 69, - 3, 4, 5, 51, -1, 53, 54, 55, 56, 12, - -1, -1, 597, 964, 965, 600, -1, -1, -1, 869, - 870, 69, 872, 873, 94, -1, -1, -1, 210, 51, - 100, 53, 54, 55, 56, 101, 1169, 1170, 1171, 1172, - -1, -1, 794, -1, 975, -1, 94, 69, 51, 801, - 802, 803, 55, -1, 985, 986, -1, 115, -1, 1192, - 118, 119, 993, 129, 130, 131, 132, 133, -1, -1, - -1, -1, 1003, 1004, 1005, -1, -1, -1, 81, -1, - -1, 139, -1, -1, -1, 837, -1, 145, 146, -1, - 148, 843, 844, -1, 1045, 1046, -1, 849, 850, 1050, - -1, -1, -1, -1, -1, 287, -1, -1, -1, -1, - 680, 863, 294, 295, -1, -1, 119, 88, 89, -1, - 302, -1, -1, 875, 876, 975, -1, -1, -1, -1, - 101, 313, 884, -1, 51, -1, 53, 54, 55, 56, - 1071, -1, 1073, 895, 896, 1076, -1, -1, 998, -1, - -1, 721, 69, -1, -1, 126, 127, 128, 129, 130, - 131, 132, 133, -1, -1, -1, 348, 1118, -1, -1, - 922, 353, -1, -1, -1, 927, -1, 94, -1, 1130, - -1, -1, -1, 100, 101, 102, 103, -1, -1, -1, + 903, -1, -1, 100, -1, 1198, 1199, -1, -1, -1, + 682, -1, 863, 116, 117, 866, -1, 119, -1, -1, + -1, -1, -1, 115, 875, 876, 118, 119, -1, 44, + 814, -1, -1, 884, -1, -1, 820, -1, -1, -1, + -1, 531, -1, -1, 895, 896, 551, 139, 538, -1, + -1, -1, -1, 145, 146, -1, 148, 72, 73, 74, + 75, 76, 77, 78, 79, 80, 81, 82, 83, -1, + -1, -1, -1, 88, 89, 580, 975, -1, 1, -1, + 3, 4, 5, -1, -1, -1, 101, -1, -1, 12, + -1, -1, 597, -1, -1, 600, -1, -1, -1, 998, + -1, 51, -1, 53, 54, 55, 56, 122, 210, 124, + 125, 126, 127, 128, 129, 130, 131, 132, 133, 69, + -1, 625, 794, -1, 975, 88, 89, 142, 51, 801, + 802, 803, 55, -1, 985, 986, 640, -1, 101, -1, + -1, -1, 993, 51, 94, 53, 54, 55, 56, -1, + 100, -1, 1003, 1004, 1005, -1, -1, -1, 81, 943, + -1, 69, -1, -1, -1, 837, 129, 130, 131, 132, + 133, 843, 844, -1, -1, -1, -1, 849, 850, -1, + 964, 965, -1, -1, -1, 287, 94, -1, -1, -1, + 680, 863, 294, 295, 88, 89, 119, -1, -1, -1, + 302, -1, -1, 875, 876, -1, -1, 101, -1, -1, + -1, 313, 884, -1, 718, -1, -1, -1, -1, -1, + 1071, -1, 1073, 895, 896, 1076, -1, -1, -1, 625, + -1, 721, 126, 127, 128, 129, 130, 131, 132, 133, + 88, 89, -1, -1, 640, -1, 348, -1, -1, -1, + 922, 353, -1, 101, -1, 927, -1, -1, -1, -1, + -1, 1045, 1046, -1, -1, -1, 1050, -1, -1, -1, -1, 373, 1123, -1, -1, -1, -1, 1128, 1129, -1, - 1151, 1152, -1, -1, -1, -1, -1, 210, 793, 779, - -1, 128, 394, -1, 131, -1, -1, 399, 400, -1, - 402, -1, -1, 975, -1, -1, -1, 144, -1, 1, - -1, 3, -1, 985, 986, 1186, -1, 989, -1, -1, - 12, 993, -1, -1, -1, -1, 1, -1, 3, 4, - 5, 6, -1, 1005, -1, -1, -1, 12, 1189, 1190, - 1191, 443, 444, -1, -1, -1, -1, 1198, 1199, -1, - -1, -1, 88, 89, -1, -1, -1, -1, 460, 51, - -1, -1, -1, 853, 287, 101, -1, -1, -1, 471, + 128, 129, 130, 131, 132, 133, -1, 210, 793, 779, + -1, -1, 394, -1, -1, -1, -1, 399, 400, -1, + 402, -1, -1, 975, -1, -1, -1, -1, -1, 1, + 814, 3, -1, 985, 986, -1, 820, 989, -1, -1, + 12, 993, 718, -1, -1, -1, 1, -1, 3, 4, + 5, 6, -1, 1005, 1118, -1, -1, 12, 1189, 1190, + 1191, 443, 444, -1, -1, -1, 1130, 1198, 1199, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 460, 51, + -1, -1, -1, 853, 287, -1, -1, 1151, 1152, 471, -1, 294, 295, -1, -1, -1, 51, 479, -1, 302, - 55, -1, -1, -1, 25, -1, -1, 489, 878, -1, - 313, 881, 128, 129, 130, 131, 132, 133, -1, 1071, - -1, 1073, -1, 908, 1076, 910, 81, -1, -1, 914, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 522, -1, -1, -1, -1, 348, -1, 119, -1, 531, - 353, 72, 73, 74, 75, 76, 77, 78, 79, 80, - 81, 82, 83, 545, 119, 547, -1, 88, 89, -1, - 373, -1, 93, -1, -1, 557, 1128, 1129, -1, -1, - 101, -1, -1, -1, 51, -1, 53, 54, 55, 56, + 55, -1, -1, -1, -1, -1, -1, 489, 878, -1, + 313, 881, -1, -1, -1, -1, -1, -1, -1, 1071, + -1, 1073, 1186, 908, 1076, 910, 81, -1, -1, 914, + -1, -1, -1, -1, -1, -1, -1, -1, 814, -1, + 522, -1, -1, -1, 820, 348, -1, 119, -1, 531, + 353, -1, -1, -1, -1, -1, -1, -1, -1, 943, + -1, -1, -1, 545, 119, 547, -1, -1, -1, -1, + 373, -1, -1, -1, -1, 557, 1128, 1129, -1, -1, + 964, 965, -1, -1, -1, -1, -1, -1, -1, -1, -1, 394, -1, -1, -1, -1, 399, 400, -1, 402, - -1, 122, 69, 124, 125, 126, 127, 128, 129, 130, - 131, 132, 133, -1, -1, -1, -1, -1, -1, -1, - 990, -1, -1, -1, -1, -1, 996, 94, -1, 1014, - 1015, 613, -1, 100, 101, 102, 103, 1189, 210, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 990, -1, -1, -1, -1, -1, 996, -1, -1, 1014, + 1015, 613, -1, -1, -1, -1, -1, 1189, 210, -1, 443, 444, -1, -1, -1, -1, 1198, 1199, 34, 35, 36, 1036, -1, 1038, -1, 210, -1, 460, -1, -1, - -1, 128, -1, -1, 131, 51, -1, -1, 471, 55, - -1, -1, 58, 59, -1, 142, 479, 63, -1, -1, - -1, -1, -1, -1, -1, -1, 489, -1, -1, -1, + -1, 1045, 1046, -1, -1, 51, 1050, 943, 471, 55, + -1, -1, 58, 59, 60, -1, 479, 63, -1, -1, + -1, -1, -1, -1, -1, -1, 489, -1, 964, 965, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 682, -1, -1, -1, -1, 91, 1091, 1077, 1078, 1094, - -1, 97, 98, -1, 100, 287, 102, -1, -1, 522, - 106, -1, 294, 295, -1, -1, -1, -1, 531, -1, - 302, -1, 287, -1, -1, -1, -1, -1, -1, 294, - 295, 127, 545, -1, 547, -1, -1, 302, 134, -1, - -1, -1, 1137, -1, 557, -1, -1, 1142, 313, 1144, - -1, -1, -1, 1148, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 348, -1, -1, -1, + -1, 97, 98, -1, 100, 287, -1, -1, -1, 522, + 106, 107, 294, 295, -1, -1, -1, -1, 531, -1, + 302, -1, 287, -1, 1118, -1, -1, -1, -1, 294, + 295, 127, 545, -1, 547, -1, 1130, 302, 134, -1, + -1, -1, 1137, -1, 557, 141, -1, 1142, 313, 1144, + -1, -1, -1, 1148, -1, -1, -1, 1151, 1152, 1045, + 1046, -1, -1, -1, 1050, -1, 348, -1, -1, -1, -1, 353, -1, -1, -1, -1, 51, -1, 53, 54, 55, 56, -1, 348, -1, -1, -1, 1182, 353, -1, - -1, 373, -1, -1, 69, -1, -1, -1, 1178, -1, + -1, 373, 1186, -1, 69, -1, -1, -1, 1178, -1, 613, -1, 794, -1, -1, -1, -1, -1, 373, 801, 802, 803, 394, -1, 1194, 1195, -1, 399, -1, 94, 402, -1, -1, -1, -1, 100, 101, 102, 103, 394, - -1, -1, -1, -1, 399, 400, 51, -1, 53, 54, - 55, 56, -1, -1, -1, 837, -1, -1, -1, -1, + -1, -1, 1118, -1, 399, 400, 51, -1, 53, 54, + 55, 56, -1, -1, 1130, 837, -1, -1, -1, -1, -1, 843, 844, 128, 69, -1, 131, 849, 850, -1, - -1, 443, 444, -1, -1, -1, -1, -1, 83, 682, + -1, 443, 444, -1, -1, 1151, 1152, 142, 83, 682, -1, 863, -1, -1, -1, -1, -1, -1, 460, 94, -1, -1, -1, 875, 876, 100, 101, 102, 103, 471, - -1, -1, 884, -1, -1, 460, -1, 479, 34, 35, - 36, -1, -1, 895, 896, -1, 471, 489, -1, -1, - -1, -1, -1, 128, 479, 51, 131, -1, -1, 55, - -1, -1, 58, 59, 489, -1, -1, 63, -1, 144, - 922, -1, -1, -1, -1, 927, -1, -1, -1, -1, + -1, -1, 884, -1, -1, 460, -1, 479, -1, -1, + 1186, -1, -1, 895, 896, -1, 471, 489, -1, -1, + -1, -1, -1, 128, 479, -1, 131, -1, 51, -1, + 53, 54, 55, 56, 489, -1, -1, -1, -1, 144, + 922, -1, -1, -1, -1, 927, 69, -1, -1, -1, 522, -1, -1, -1, -1, -1, -1, -1, -1, 531, - -1, -1, -1, -1, -1, 91, -1, 522, -1, -1, - -1, 97, 98, -1, 100, -1, 531, -1, -1, -1, - 106, -1, -1, -1, -1, 557, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 522, -1, -1, + -1, 94, -1, -1, -1, -1, 531, 100, 101, 102, + 103, -1, -1, -1, -1, 557, -1, -1, -1, -1, 545, 794, 547, 975, -1, -1, -1, -1, 801, 802, - 803, 127, 557, 985, 986, -1, -1, 989, 134, -1, - -1, 993, -1, -1, 34, 35, 36, -1, -1, -1, - -1, -1, -1, 1005, -1, -1, -1, -1, -1, -1, - -1, 51, -1, -1, 837, 55, -1, -1, 58, 59, - 843, 844, -1, 63, -1, -1, 849, 850, -1, -1, - 1, -1, 3, -1, -1, -1, -1, -1, 613, -1, - 863, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 91, 875, 876, -1, -1, -1, 97, 98, -1, - -1, 884, -1, -1, -1, -1, 106, -1, -1, 1071, - -1, 1073, 895, 896, 1076, -1, -1, -1, -1, -1, - 51, -1, -1, -1, -1, -1, -1, 127, -1, -1, - 682, -1, -1, -1, 134, -1, -1, -1, -1, 922, - -1, -1, -1, -1, 927, -1, -1, 682, -1, -1, - -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 1128, 1129, -1, 13, - 14, 15, 16, 17, 18, -1, 20, -1, -1, -1, - -1, 25, 26, 27, -1, -1, -1, -1, 119, -1, + 803, -1, 557, 985, 986, 128, -1, 989, 131, -1, + -1, 993, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 144, -1, 1005, -1, -1, -1, 51, -1, 53, + 54, 55, 56, -1, 837, -1, -1, -1, -1, -1, + 843, 844, 34, 35, 36, 69, 849, 850, -1, -1, + 1, -1, 3, -1, -1, -1, -1, -1, 613, 51, + 863, 85, -1, 55, -1, -1, 58, 59, 60, -1, + 94, 63, 875, 876, -1, -1, 100, 101, 102, 103, + 51, 884, 53, 54, 55, 56, -1, -1, -1, 1071, + -1, 1073, 895, 896, 1076, -1, -1, -1, 69, 91, + 51, -1, -1, -1, 128, 97, 98, 131, 100, -1, + 682, -1, -1, -1, 106, 107, -1, -1, -1, 922, + -1, -1, -1, 94, 927, -1, -1, 682, -1, 100, + 101, 102, 103, -1, -1, 127, 0, -1, -1, -1, + -1, -1, 134, -1, -1, -1, 1128, 1129, -1, 13, + 14, 15, 16, 17, 18, -1, 20, 128, -1, -1, + 131, 25, 26, 27, -1, -1, -1, -1, 119, -1, -1, -1, 975, 37, 38, -1, 40, 41, 42, 43, 44, -1, 985, 986, -1, -1, 989, -1, -1, -1, 993, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -5155,40 +5165,40 @@ static const yytype_int16 yycheck[] = 875, 876, -1, 895, 896, 1128, 1129, -1, -1, 884, -1, -1, -1, -1, -1, -1, 287, -1, -1, -1, 895, 896, -1, 294, 295, -1, 46, 47, 48, 49, - -1, 302, -1, 53, 54, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, 65, 66, 922, -1, -1, - -1, -1, 927, -1, -1, 51, -1, 53, 54, 55, - 56, -1, -1, -1, -1, -1, 1189, -1, -1, -1, - -1, -1, -1, 69, -1, 1198, 1199, 348, -1, -1, - -1, -1, 353, 975, -1, -1, -1, 83, -1, -1, + -1, 302, -1, 53, 54, 51, -1, 53, 54, 55, + 56, -1, -1, -1, -1, 65, 66, 922, -1, -1, + -1, -1, 927, 69, -1, 51, -1, 53, 54, 55, + 56, -1, -1, -1, -1, -1, 1189, -1, -1, 85, + -1, -1, -1, 69, -1, 1198, 1199, 348, 94, -1, + -1, -1, 353, 975, 100, 101, 102, 103, -1, 85, -1, -1, -1, 985, 986, -1, -1, 989, 94, -1, -1, 993, 373, -1, 100, 101, 102, 103, -1, -1, - 985, 986, -1, 1005, 989, -1, -1, -1, 993, -1, - -1, -1, -1, 394, -1, 121, -1, -1, 399, -1, - 1005, 402, 128, -1, -1, 131, -1, -1, -1, -1, - -1, -1, 51, -1, 53, 54, 55, 56, 144, -1, + 985, 986, 128, 1005, 989, 131, -1, -1, 993, 34, + 35, 36, -1, 394, -1, -1, -1, -1, 399, -1, + 1005, 402, 128, -1, -1, 131, 51, -1, -1, -1, + 55, -1, -1, 58, 59, 60, -1, -1, 63, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 443, 444, -1, -1, 91, -1, -1, 1071, + -1, 1073, 97, 98, 1076, 100, -1, 102, -1, 460, + -1, 106, 107, -1, -1, -1, 1071, -1, 1073, -1, + 471, 1076, -1, -1, -1, 34, 35, 36, 479, -1, + -1, -1, 127, -1, -1, -1, -1, -1, 489, 134, + -1, -1, 51, -1, -1, -1, 55, -1, -1, 58, + 59, 60, -1, -1, 63, -1, 1128, 1129, -1, -1, + -1, 261, 262, 263, 264, -1, -1, -1, -1, -1, + -1, 522, -1, 1128, 1129, 275, -1, -1, 278, -1, + 531, -1, 91, -1, -1, -1, -1, -1, 97, 98, + 51, -1, 53, 54, 55, 56, -1, 106, 107, -1, + -1, -1, -1, -1, -1, -1, 557, -1, 69, -1, + -1, -1, -1, -1, -1, -1, -1, 1189, 127, -1, + -1, -1, 83, -1, -1, 134, 1198, 1199, -1, -1, + -1, -1, -1, 94, 1189, -1, -1, -1, -1, 100, + 101, 102, 103, 1198, 1199, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 69, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, 443, 444, -1, -1, 85, -1, -1, 1071, - -1, 1073, -1, -1, 1076, 94, -1, -1, -1, 460, - -1, 100, 101, 102, 103, 44, 1071, -1, 1073, -1, - 471, 1076, -1, -1, -1, -1, -1, -1, 479, -1, - -1, -1, -1, -1, -1, -1, -1, -1, 489, 128, - -1, -1, 131, 72, 73, 74, 75, 76, 77, 78, - 79, 80, 81, 82, 83, -1, 1128, 1129, -1, 88, - 89, 261, 262, 263, 264, -1, -1, -1, -1, -1, - -1, 522, 101, 1128, 1129, 275, -1, -1, 278, -1, - 531, -1, -1, -1, -1, -1, -1, 51, -1, 53, - 54, 55, 56, 122, -1, 124, 125, 126, 127, 128, - 129, 130, 131, 132, 133, 69, 557, 51, -1, 53, - 54, 55, 56, 142, -1, -1, -1, 1189, -1, -1, - -1, 85, -1, -1, -1, 69, 1198, 1199, -1, -1, - 94, -1, -1, -1, 1189, -1, 100, 101, 102, 103, - -1, 85, -1, 1198, 1199, -1, -1, -1, -1, -1, - 94, -1, -1, -1, -1, -1, 100, 101, 102, 103, - -1, -1, -1, -1, 128, -1, -1, 131, -1, -1, - -1, -1, -1, -1, 374, -1, -1, -1, -1, -1, - -1, -1, -1, -1, 128, 385, -1, 131, -1, -1, + 121, -1, -1, -1, -1, -1, -1, 128, -1, -1, + 131, -1, -1, -1, 374, -1, -1, -1, -1, -1, + -1, -1, -1, 144, -1, 385, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 397, -1, -1, -1, -1, 402, -1, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, -1, 419, @@ -5226,7 +5236,7 @@ static const yytype_int16 yycheck[] = 90, -1, -1, 93, 985, 986, 736, 737, -1, 99, -1, 101, 993, -1, -1, 105, -1, -1, -1, -1, -1, -1, -1, -1, 1005, 115, -1, -1, 118, 119, - -1, -1, 122, -1, 124, 125, 126, 127, 128, 129, + 25, -1, 122, -1, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, -1, -1, -1, -1, 138, 139, 140, 141, 142, -1, 144, 145, 146, 147, 148, -1, -1, -1, -1, -1, -1, -1, -1, -1, 798, -1, @@ -6240,19 +6250,25 @@ static const yytype_int16 yycheck[] = 127, 128, 129, 130, 131, 132, 133, -1, -1, -1, -1, -1, -1, -1, -1, 142, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, -1, -1, - -1, -1, 88, 89, -1, -1, -1, -1, -1, -1, + -1, -1, 88, 89, -1, -1, -1, 93, -1, -1, -1, -1, -1, -1, -1, 101, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 122, -1, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, - -1, -1, -1, -1, 88, 89, 72, 73, 74, 75, - 76, 77, -1, -1, 80, 81, -1, 101, -1, -1, - -1, -1, 88, 89, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, 101, -1, -1, -1, -1, + -1, -1, -1, -1, 88, 89, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 101, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 122, -1, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, - -1, -1, -1, -1, -1, -1, -1, -1, 124, 125, - 126, 127, 128, 129, 130, 131, 132, 133 + 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, + 82, 83, -1, -1, -1, -1, 88, 89, 72, 73, + 74, 75, 76, 77, -1, -1, 80, 81, -1, 101, + -1, -1, -1, -1, 88, 89, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 101, -1, -1, + -1, -1, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, -1, -1, -1, -1, -1, -1, -1, -1, + 124, 125, 126, 127, 128, 129, 130, 131, 132, 133 }; /* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of @@ -6340,9 +6356,9 @@ static const yytype_int16 yystos[] = 311, 316, 323, 325, 318, 320, 325, 51, 318, 174, 191, 15, 79, 126, 233, 235, 347, 191, 202, 342, 180, 142, 44, 121, 44, 90, 140, 338, 34, 35, - 36, 51, 55, 63, 91, 97, 98, 100, 102, 127, - 254, 255, 257, 258, 259, 260, 263, 264, 265, 267, - 268, 269, 270, 290, 294, 254, 341, 92, 92, 194, + 36, 51, 55, 91, 97, 98, 100, 102, 127, 254, + 255, 257, 258, 259, 260, 263, 264, 265, 267, 268, + 269, 270, 276, 290, 294, 254, 341, 92, 92, 194, 199, 141, 202, 92, 92, 195, 199, 195, 199, 233, 233, 172, 344, 169, 156, 141, 15, 342, 185, 191, 204, 272, 347, 18, 226, 347, 17, 225, 226, 92, @@ -7196,7 +7212,7 @@ YYLTYPE yylloc = yyloc_default; -#line 7200 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7216 "mrbgems/mruby-compiler/core/y.tab.c" yylsp[0] = yylloc; goto yysetstate; @@ -7409,84 +7425,84 @@ YYLTYPE yylloc = yyloc_default; switch (yyn) { case 2: /* $@1: %empty */ -#line 2178 "mrbgems/mruby-compiler/core/parse.y" +#line 2182 "mrbgems/mruby-compiler/core/parse.y" { p->lstate = EXPR_BEG; if (!p->locals) p->locals = cons(0,0); } -#line 7418 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7434 "mrbgems/mruby-compiler/core/y.tab.c" break; case 3: /* program: $@1 top_compstmt */ -#line 2183 "mrbgems/mruby-compiler/core/parse.y" +#line 2187 "mrbgems/mruby-compiler/core/parse.y" { p->tree = new_scope(p, (yyvsp[0].nd)); } -#line 7426 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7442 "mrbgems/mruby-compiler/core/y.tab.c" break; case 4: /* top_compstmt: top_stmts opt_terms */ -#line 2189 "mrbgems/mruby-compiler/core/parse.y" +#line 2193 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 7434 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7450 "mrbgems/mruby-compiler/core/y.tab.c" break; case 5: /* top_stmts: none */ -#line 2195 "mrbgems/mruby-compiler/core/parse.y" +#line 2199 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, 0); } -#line 7442 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7458 "mrbgems/mruby-compiler/core/y.tab.c" break; case 6: /* top_stmts: top_stmt */ -#line 2199 "mrbgems/mruby-compiler/core/parse.y" +#line 2203 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, (yyvsp[0].nd)); } -#line 7450 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7466 "mrbgems/mruby-compiler/core/y.tab.c" break; case 7: /* top_stmts: top_stmts terms top_stmt */ -#line 2203 "mrbgems/mruby-compiler/core/parse.y" +#line 2207 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = stmts_push(p, (yyvsp[-2].nd), newline_node((yyvsp[0].nd))); } -#line 7458 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7474 "mrbgems/mruby-compiler/core/y.tab.c" break; case 8: /* top_stmts: error top_stmt */ -#line 2207 "mrbgems/mruby-compiler/core/parse.y" +#line 2211 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, 0); } -#line 7466 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7482 "mrbgems/mruby-compiler/core/y.tab.c" break; case 10: /* @2: %empty */ -#line 2214 "mrbgems/mruby-compiler/core/parse.y" +#line 2218 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = local_switch(p); nvars_block(p); } -#line 7475 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7491 "mrbgems/mruby-compiler/core/y.tab.c" break; case 11: /* top_stmt: "'BEGIN'" @2 '{' top_compstmt '}' */ -#line 2219 "mrbgems/mruby-compiler/core/parse.y" +#line 2223 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[-4]), p, "BEGIN not supported"); local_resume(p, (yyvsp[-3].nd)); nvars_unnest(p); (yyval.nd) = 0; } -#line 7486 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7502 "mrbgems/mruby-compiler/core/y.tab.c" break; case 12: /* bodystmt: compstmt opt_rescue opt_else opt_ensure */ -#line 2231 "mrbgems/mruby-compiler/core/parse.y" +#line 2235 "mrbgems/mruby-compiler/core/parse.y" { if ((yyvsp[-2].nd)) { (yyval.nd) = new_rescue(p, (yyvsp[-3].nd), (yyvsp[-2].nd), (yyvsp[-1].nd)); @@ -7507,89 +7523,89 @@ YYLTYPE yylloc = yyloc_default; } } } -#line 7511 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7527 "mrbgems/mruby-compiler/core/y.tab.c" break; case 13: /* compstmt: stmts opt_terms */ -#line 2254 "mrbgems/mruby-compiler/core/parse.y" +#line 2258 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 7519 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7535 "mrbgems/mruby-compiler/core/y.tab.c" break; case 14: /* stmts: none */ -#line 2260 "mrbgems/mruby-compiler/core/parse.y" +#line 2264 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, 0); } -#line 7527 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7543 "mrbgems/mruby-compiler/core/y.tab.c" break; case 15: /* stmts: stmt */ -#line 2264 "mrbgems/mruby-compiler/core/parse.y" +#line 2268 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, (yyvsp[0].nd)); } -#line 7535 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7551 "mrbgems/mruby-compiler/core/y.tab.c" break; case 16: /* stmts: stmts terms stmt */ -#line 2268 "mrbgems/mruby-compiler/core/parse.y" +#line 2272 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = stmts_push(p, (yyvsp[-2].nd), newline_node((yyvsp[0].nd))); } -#line 7543 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7559 "mrbgems/mruby-compiler/core/y.tab.c" break; case 17: /* stmts: error stmt */ -#line 2272 "mrbgems/mruby-compiler/core/parse.y" +#line 2276 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, (yyvsp[0].nd)); } -#line 7551 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7567 "mrbgems/mruby-compiler/core/y.tab.c" break; case 18: /* $@3: %empty */ -#line 2277 "mrbgems/mruby-compiler/core/parse.y" +#line 2281 "mrbgems/mruby-compiler/core/parse.y" {p->lstate = EXPR_FNAME;} -#line 7557 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7573 "mrbgems/mruby-compiler/core/y.tab.c" break; case 19: /* stmt: "'alias'" fsym $@3 fsym */ -#line 2278 "mrbgems/mruby-compiler/core/parse.y" +#line 2282 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_alias(p, (yyvsp[-2].id), (yyvsp[0].id)); } -#line 7565 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7581 "mrbgems/mruby-compiler/core/y.tab.c" break; case 20: /* stmt: "'undef'" undef_list */ -#line 2282 "mrbgems/mruby-compiler/core/parse.y" +#line 2286 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_undef(p, (yyvsp[0].nd)); } -#line 7573 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7589 "mrbgems/mruby-compiler/core/y.tab.c" break; case 21: /* stmt: stmt "'if' modifier" expr_value */ -#line 2286 "mrbgems/mruby-compiler/core/parse.y" +#line 2290 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[0].nd)), (yyvsp[-2].nd), 0); } -#line 7581 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7597 "mrbgems/mruby-compiler/core/y.tab.c" break; case 22: /* stmt: stmt "'unless' modifier" expr_value */ -#line 2290 "mrbgems/mruby-compiler/core/parse.y" +#line 2294 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[0].nd)), 0, (yyvsp[-2].nd)); } -#line 7589 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7605 "mrbgems/mruby-compiler/core/y.tab.c" break; case 23: /* stmt: stmt "'while' modifier" expr_value */ -#line 2294 "mrbgems/mruby-compiler/core/parse.y" +#line 2298 "mrbgems/mruby-compiler/core/parse.y" { if ((yyvsp[-2].nd) && node_type_p((yyvsp[-2].nd), NODE_BEGIN)) { (yyval.nd) = new_while_mod(p, cond((yyvsp[0].nd)), (yyvsp[-2].nd)); @@ -7598,11 +7614,11 @@ YYLTYPE yylloc = yyloc_default; (yyval.nd) = new_while(p, cond((yyvsp[0].nd)), (yyvsp[-2].nd)); } } -#line 7602 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7618 "mrbgems/mruby-compiler/core/y.tab.c" break; case 24: /* stmt: stmt "'until' modifier" expr_value */ -#line 2303 "mrbgems/mruby-compiler/core/parse.y" +#line 2307 "mrbgems/mruby-compiler/core/parse.y" { if ((yyvsp[-2].nd) && node_type_p((yyvsp[-2].nd), NODE_BEGIN)) { (yyval.nd) = new_until_mod(p, cond((yyvsp[0].nd)), (yyvsp[-2].nd)); @@ -7611,117 +7627,117 @@ YYLTYPE yylloc = yyloc_default; (yyval.nd) = new_until(p, cond((yyvsp[0].nd)), (yyvsp[-2].nd)); } } -#line 7615 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7631 "mrbgems/mruby-compiler/core/y.tab.c" break; case 25: /* stmt: stmt "'rescue' modifier" stmt */ -#line 2312 "mrbgems/mruby-compiler/core/parse.y" +#line 2316 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_mod_rescue(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7623 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7639 "mrbgems/mruby-compiler/core/y.tab.c" break; case 26: /* stmt: "'END'" '{' compstmt '}' */ -#line 2316 "mrbgems/mruby-compiler/core/parse.y" +#line 2320 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[-3]), p, "END not supported"); (yyval.nd) = new_postexe(p, (yyvsp[-1].nd)); } -#line 7632 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7648 "mrbgems/mruby-compiler/core/y.tab.c" break; case 28: /* stmt: mlhs '=' command_call */ -#line 2322 "mrbgems/mruby-compiler/core/parse.y" +#line 2326 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_masgn(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7640 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7656 "mrbgems/mruby-compiler/core/y.tab.c" break; case 29: /* stmt: lhs '=' mrhs */ -#line 2326 "mrbgems/mruby-compiler/core/parse.y" +#line 2330 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_asgn(p, (yyvsp[-2].nd), new_array(p, (yyvsp[0].nd))); } -#line 7648 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7664 "mrbgems/mruby-compiler/core/y.tab.c" break; case 30: /* stmt: mlhs '=' arg */ -#line 2330 "mrbgems/mruby-compiler/core/parse.y" +#line 2334 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_masgn(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7656 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7672 "mrbgems/mruby-compiler/core/y.tab.c" break; case 31: /* stmt: mlhs '=' mrhs */ -#line 2334 "mrbgems/mruby-compiler/core/parse.y" +#line 2338 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_masgn(p, (yyvsp[-2].nd), new_array(p, (yyvsp[0].nd))); } -#line 7664 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7680 "mrbgems/mruby-compiler/core/y.tab.c" break; case 33: /* command_asgn: lhs '=' command_rhs */ -#line 2341 "mrbgems/mruby-compiler/core/parse.y" +#line 2345 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_asgn(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7672 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7688 "mrbgems/mruby-compiler/core/y.tab.c" break; case 34: /* command_asgn: var_lhs tOP_ASGN command_rhs */ -#line 2345 "mrbgems/mruby-compiler/core/parse.y" +#line 2349 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, (yyvsp[-2].nd), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 7680 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7696 "mrbgems/mruby-compiler/core/y.tab.c" break; case 35: /* command_asgn: primary_value '[' opt_call_args ']' tOP_ASGN command_rhs */ -#line 2349 "mrbgems/mruby-compiler/core/parse.y" +#line 2353 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, new_call(p, (yyvsp[-5].nd), intern_op(aref), (yyvsp[-3].nd), '.'), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 7688 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7704 "mrbgems/mruby-compiler/core/y.tab.c" break; case 36: /* command_asgn: primary_value call_op "local variable or method" tOP_ASGN command_rhs */ -#line 2353 "mrbgems/mruby-compiler/core/parse.y" +#line 2357 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, new_call(p, (yyvsp[-4].nd), (yyvsp[-2].id), 0, (yyvsp[-3].num)), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 7696 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7712 "mrbgems/mruby-compiler/core/y.tab.c" break; case 37: /* command_asgn: primary_value call_op "constant" tOP_ASGN command_rhs */ -#line 2357 "mrbgems/mruby-compiler/core/parse.y" +#line 2361 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, new_call(p, (yyvsp[-4].nd), (yyvsp[-2].id), 0, (yyvsp[-3].num)), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 7704 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7720 "mrbgems/mruby-compiler/core/y.tab.c" break; case 38: /* command_asgn: primary_value "::" "constant" tOP_ASGN command_call */ -#line 2361 "mrbgems/mruby-compiler/core/parse.y" +#line 2365 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[-4]), p, "constant re-assignment"); (yyval.nd) = 0; } -#line 7713 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7729 "mrbgems/mruby-compiler/core/y.tab.c" break; case 39: /* command_asgn: primary_value "::" "local variable or method" tOP_ASGN command_rhs */ -#line 2366 "mrbgems/mruby-compiler/core/parse.y" +#line 2370 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, new_call(p, (yyvsp[-4].nd), (yyvsp[-2].id), 0, tCOLON2), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 7721 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7737 "mrbgems/mruby-compiler/core/y.tab.c" break; case 40: /* command_asgn: defn_head f_opt_arglist_paren '=' command */ -#line 2370 "mrbgems/mruby-compiler/core/parse.y" +#line 2374 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); endless_method_name(p, (yyvsp[-3].nd)); @@ -7730,11 +7746,11 @@ YYLTYPE yylloc = yyloc_default; nvars_unnest(p); p->in_def--; } -#line 7734 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7750 "mrbgems/mruby-compiler/core/y.tab.c" break; case 41: /* command_asgn: defn_head f_opt_arglist_paren '=' command "'rescue' modifier" arg */ -#line 2379 "mrbgems/mruby-compiler/core/parse.y" +#line 2383 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-5].nd); endless_method_name(p, (yyvsp[-5].nd)); @@ -7743,11 +7759,11 @@ YYLTYPE yylloc = yyloc_default; nvars_unnest(p); p->in_def--; } -#line 7747 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7763 "mrbgems/mruby-compiler/core/y.tab.c" break; case 42: /* command_asgn: defs_head f_opt_arglist_paren '=' command */ -#line 2388 "mrbgems/mruby-compiler/core/parse.y" +#line 2392 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); void_expr_error(p, (yyvsp[0].nd)); @@ -7756,11 +7772,11 @@ YYLTYPE yylloc = yyloc_default; p->in_def--; p->in_single--; } -#line 7760 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7776 "mrbgems/mruby-compiler/core/y.tab.c" break; case 43: /* command_asgn: defs_head f_opt_arglist_paren '=' command "'rescue' modifier" arg */ -#line 2397 "mrbgems/mruby-compiler/core/parse.y" +#line 2401 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-5].nd); void_expr_error(p, (yyvsp[-2].nd)); @@ -7769,111 +7785,111 @@ YYLTYPE yylloc = yyloc_default; p->in_def--; p->in_single--; } -#line 7773 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7789 "mrbgems/mruby-compiler/core/y.tab.c" break; case 44: /* command_asgn: backref tOP_ASGN command_rhs */ -#line 2406 "mrbgems/mruby-compiler/core/parse.y" +#line 2410 "mrbgems/mruby-compiler/core/parse.y" { backref_error(p, (yyvsp[-2].nd)); (yyval.nd) = new_stmts(p, 0); } -#line 7782 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7798 "mrbgems/mruby-compiler/core/y.tab.c" break; case 46: /* command_rhs: command_call "'rescue' modifier" stmt */ -#line 2414 "mrbgems/mruby-compiler/core/parse.y" +#line 2418 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_mod_rescue(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7790 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7806 "mrbgems/mruby-compiler/core/y.tab.c" break; case 49: /* expr: expr "'and'" expr */ -#line 2422 "mrbgems/mruby-compiler/core/parse.y" +#line 2426 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_and(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7798 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7814 "mrbgems/mruby-compiler/core/y.tab.c" break; case 50: /* expr: expr "'or'" expr */ -#line 2426 "mrbgems/mruby-compiler/core/parse.y" +#line 2430 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_or(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7806 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7822 "mrbgems/mruby-compiler/core/y.tab.c" break; case 51: /* expr: "'not'" opt_nl expr */ -#line 2430 "mrbgems/mruby-compiler/core/parse.y" +#line 2434 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, cond((yyvsp[0].nd)), "!"); } -#line 7814 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7830 "mrbgems/mruby-compiler/core/y.tab.c" break; case 52: /* expr: '!' command_call */ -#line 2434 "mrbgems/mruby-compiler/core/parse.y" +#line 2438 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, cond((yyvsp[0].nd)), "!"); } -#line 7822 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7838 "mrbgems/mruby-compiler/core/y.tab.c" break; case 53: /* $@4: %empty */ -#line 2437 "mrbgems/mruby-compiler/core/parse.y" +#line 2441 "mrbgems/mruby-compiler/core/parse.y" {p->in_kwarg++;} -#line 7828 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7844 "mrbgems/mruby-compiler/core/y.tab.c" break; case 54: /* expr: arg "=>" $@4 p_expr */ -#line 2438 "mrbgems/mruby-compiler/core/parse.y" +#line 2442 "mrbgems/mruby-compiler/core/parse.y" { /* expr => pattern (raises NoMatchingPatternError on failure) */ p->in_kwarg--; (yyval.nd) = new_match_pat(p, (yyvsp[-3].nd), (yyvsp[0].nd), TRUE); } -#line 7838 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7854 "mrbgems/mruby-compiler/core/y.tab.c" break; case 55: /* $@5: %empty */ -#line 2443 "mrbgems/mruby-compiler/core/parse.y" +#line 2447 "mrbgems/mruby-compiler/core/parse.y" {p->in_kwarg++;} -#line 7844 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7860 "mrbgems/mruby-compiler/core/y.tab.c" break; case 56: /* expr: arg "'in'" $@5 p_expr */ -#line 2444 "mrbgems/mruby-compiler/core/parse.y" +#line 2448 "mrbgems/mruby-compiler/core/parse.y" { /* expr in pattern (returns true/false) */ p->in_kwarg--; (yyval.nd) = new_match_pat(p, (yyvsp[-3].nd), (yyvsp[0].nd), FALSE); } -#line 7854 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7870 "mrbgems/mruby-compiler/core/y.tab.c" break; case 58: /* defn_head: "'def'" fname */ -#line 2453 "mrbgems/mruby-compiler/core/parse.y" +#line 2457 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_def(p, (yyvsp[0].id)); p->cmdarg_stack = 0; p->in_def++; nvars_block(p); } -#line 7865 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7881 "mrbgems/mruby-compiler/core/y.tab.c" break; case 59: /* $@6: %empty */ -#line 2462 "mrbgems/mruby-compiler/core/parse.y" +#line 2466 "mrbgems/mruby-compiler/core/parse.y" { p->lstate = EXPR_FNAME; } -#line 7873 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7889 "mrbgems/mruby-compiler/core/y.tab.c" break; case 60: /* defs_head: "'def'" singleton dot_or_colon $@6 fname */ -#line 2466 "mrbgems/mruby-compiler/core/parse.y" +#line 2470 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_sdef(p, (yyvsp[-3].nd), (yyvsp[0].id)); p->cmdarg_stack = 0; @@ -7882,1054 +7898,1054 @@ YYLTYPE yylloc = yyloc_default; nvars_block(p); p->lstate = EXPR_ENDFN; /* force for args */ } -#line 7886 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7902 "mrbgems/mruby-compiler/core/y.tab.c" break; case 61: /* expr_value: expr */ -#line 2477 "mrbgems/mruby-compiler/core/parse.y" +#line 2481 "mrbgems/mruby-compiler/core/parse.y" { if (!(yyvsp[0].nd)) (yyval.nd) = new_nil(p); else { (yyval.nd) = (yyvsp[0].nd); } } -#line 7897 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7913 "mrbgems/mruby-compiler/core/y.tab.c" break; case 65: /* block_command: block_call call_op2 operation2 command_args */ -#line 2491 "mrbgems/mruby-compiler/core/parse.y" +#line 2495 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), (yyvsp[-2].num)); } -#line 7905 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7921 "mrbgems/mruby-compiler/core/y.tab.c" break; case 66: /* $@7: %empty */ -#line 2497 "mrbgems/mruby-compiler/core/parse.y" +#line 2501 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); nvars_nest(p); } -#line 7914 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7930 "mrbgems/mruby-compiler/core/y.tab.c" break; case 67: /* cmd_brace_block: "{" $@7 opt_block_param compstmt '}' */ -#line 2504 "mrbgems/mruby-compiler/core/parse.y" +#line 2508 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_block(p, (yyvsp[-2].nd), (yyvsp[-1].nd)); local_unnest(p); nvars_unnest(p); } -#line 7924 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7940 "mrbgems/mruby-compiler/core/y.tab.c" break; case 68: /* command: operation command_args */ -#line 2512 "mrbgems/mruby-compiler/core/parse.y" +#line 2516 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_fcall(p, (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 7932 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7948 "mrbgems/mruby-compiler/core/y.tab.c" break; case 69: /* command: operation command_args cmd_brace_block */ -#line 2516 "mrbgems/mruby-compiler/core/parse.y" +#line 2520 "mrbgems/mruby-compiler/core/parse.y" { args_with_block(p, (yyvsp[-1].nd), (yyvsp[0].nd)); (yyval.nd) = new_fcall(p, (yyvsp[-2].id), (yyvsp[-1].nd)); } -#line 7941 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7957 "mrbgems/mruby-compiler/core/y.tab.c" break; case 70: /* command: primary_value call_op operation2 command_args */ -#line 2521 "mrbgems/mruby-compiler/core/parse.y" +#line 2525 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), (yyvsp[-2].num)); } -#line 7949 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7965 "mrbgems/mruby-compiler/core/y.tab.c" break; case 71: /* command: primary_value call_op operation2 command_args cmd_brace_block */ -#line 2525 "mrbgems/mruby-compiler/core/parse.y" +#line 2529 "mrbgems/mruby-compiler/core/parse.y" { args_with_block(p, (yyvsp[-1].nd), (yyvsp[0].nd)); (yyval.nd) = new_call(p, (yyvsp[-4].nd), (yyvsp[-2].id), (yyvsp[-1].nd), (yyvsp[-3].num)); } -#line 7958 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7974 "mrbgems/mruby-compiler/core/y.tab.c" break; case 72: /* command: primary_value "::" operation2 command_args */ -#line 2530 "mrbgems/mruby-compiler/core/parse.y" +#line 2534 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), tCOLON2); } -#line 7966 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7982 "mrbgems/mruby-compiler/core/y.tab.c" break; case 73: /* command: primary_value "::" operation2 command_args cmd_brace_block */ -#line 2534 "mrbgems/mruby-compiler/core/parse.y" +#line 2538 "mrbgems/mruby-compiler/core/parse.y" { args_with_block(p, (yyvsp[-1].nd), (yyvsp[0].nd)); (yyval.nd) = new_call(p, (yyvsp[-4].nd), (yyvsp[-2].id), (yyvsp[-1].nd), tCOLON2); } -#line 7975 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7991 "mrbgems/mruby-compiler/core/y.tab.c" break; case 74: /* command: "'super'" command_args */ -#line 2539 "mrbgems/mruby-compiler/core/parse.y" +#line 2543 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_super(p, (yyvsp[0].nd)); } -#line 7983 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7999 "mrbgems/mruby-compiler/core/y.tab.c" break; case 75: /* command: "'yield'" command_args */ -#line 2543 "mrbgems/mruby-compiler/core/parse.y" +#line 2547 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_yield(p, (yyvsp[0].nd)); } -#line 7991 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8007 "mrbgems/mruby-compiler/core/y.tab.c" break; case 76: /* command: "'return'" call_args */ -#line 2547 "mrbgems/mruby-compiler/core/parse.y" +#line 2551 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_return(p, ret_args(p, (yyvsp[0].nd))); } -#line 7999 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8015 "mrbgems/mruby-compiler/core/y.tab.c" break; case 77: /* command: "'break'" call_args */ -#line 2551 "mrbgems/mruby-compiler/core/parse.y" +#line 2555 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_break(p, ret_args(p, (yyvsp[0].nd))); } -#line 8007 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8023 "mrbgems/mruby-compiler/core/y.tab.c" break; case 78: /* command: "'next'" call_args */ -#line 2555 "mrbgems/mruby-compiler/core/parse.y" +#line 2559 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_next(p, ret_args(p, (yyvsp[0].nd))); } -#line 8015 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8031 "mrbgems/mruby-compiler/core/y.tab.c" break; case 79: /* mlhs: mlhs_basic */ -#line 2561 "mrbgems/mruby-compiler/core/parse.y" +#line 2565 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 8023 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8039 "mrbgems/mruby-compiler/core/y.tab.c" break; case 80: /* mlhs: tLPAREN mlhs_inner rparen */ -#line 2565 "mrbgems/mruby-compiler/core/parse.y" +#line 2569 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 8031 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8047 "mrbgems/mruby-compiler/core/y.tab.c" break; case 82: /* mlhs_inner: tLPAREN mlhs_inner rparen */ -#line 2572 "mrbgems/mruby-compiler/core/parse.y" +#line 2576 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 8039 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8055 "mrbgems/mruby-compiler/core/y.tab.c" break; case 83: /* mlhs_basic: mlhs_list */ -#line 2578 "mrbgems/mruby-compiler/core/parse.y" +#line 2582 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 8047 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8063 "mrbgems/mruby-compiler/core/y.tab.c" break; case 84: /* mlhs_basic: mlhs_list mlhs_item */ -#line 2582 "mrbgems/mruby-compiler/core/parse.y" +#line 2586 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(push((yyvsp[-1].nd),(yyvsp[0].nd))); } -#line 8055 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8071 "mrbgems/mruby-compiler/core/y.tab.c" break; case 85: /* mlhs_basic: mlhs_list "*" mlhs_node */ -#line 2586 "mrbgems/mruby-compiler/core/parse.y" +#line 2590 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list2((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 8063 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8079 "mrbgems/mruby-compiler/core/y.tab.c" break; case 86: /* mlhs_basic: mlhs_list "*" mlhs_node ',' mlhs_post */ -#line 2590 "mrbgems/mruby-compiler/core/parse.y" +#line 2594 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3((yyvsp[-4].nd), (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 8071 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8087 "mrbgems/mruby-compiler/core/y.tab.c" break; case 87: /* mlhs_basic: mlhs_list "*" */ -#line 2594 "mrbgems/mruby-compiler/core/parse.y" +#line 2598 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list2((yyvsp[-1].nd), new_nil(p)); } -#line 8079 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8095 "mrbgems/mruby-compiler/core/y.tab.c" break; case 88: /* mlhs_basic: mlhs_list "*" ',' mlhs_post */ -#line 2598 "mrbgems/mruby-compiler/core/parse.y" +#line 2602 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3((yyvsp[-3].nd), new_nil(p), (yyvsp[0].nd)); } -#line 8087 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8103 "mrbgems/mruby-compiler/core/y.tab.c" break; case 89: /* mlhs_basic: "*" mlhs_node */ -#line 2602 "mrbgems/mruby-compiler/core/parse.y" +#line 2606 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list2(0, (yyvsp[0].nd)); } -#line 8095 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8111 "mrbgems/mruby-compiler/core/y.tab.c" break; case 90: /* mlhs_basic: "*" mlhs_node ',' mlhs_post */ -#line 2606 "mrbgems/mruby-compiler/core/parse.y" +#line 2610 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3(0, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 8103 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8119 "mrbgems/mruby-compiler/core/y.tab.c" break; case 91: /* mlhs_basic: "*" */ -#line 2610 "mrbgems/mruby-compiler/core/parse.y" +#line 2614 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list2(0, new_nil(p)); } -#line 8111 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8127 "mrbgems/mruby-compiler/core/y.tab.c" break; case 92: /* mlhs_basic: "*" ',' mlhs_post */ -#line 2614 "mrbgems/mruby-compiler/core/parse.y" +#line 2618 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3(0, new_nil(p), (yyvsp[0].nd)); } -#line 8119 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8135 "mrbgems/mruby-compiler/core/y.tab.c" break; case 94: /* mlhs_item: tLPAREN mlhs_inner rparen */ -#line 2621 "mrbgems/mruby-compiler/core/parse.y" +#line 2625 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_masgn(p, (yyvsp[-1].nd), NULL); } -#line 8127 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8143 "mrbgems/mruby-compiler/core/y.tab.c" break; case 95: /* mlhs_list: mlhs_item ',' */ -#line 2627 "mrbgems/mruby-compiler/core/parse.y" +#line 2631 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[-1].nd)); } -#line 8135 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8151 "mrbgems/mruby-compiler/core/y.tab.c" break; case 96: /* mlhs_list: mlhs_list mlhs_item ',' */ -#line 2631 "mrbgems/mruby-compiler/core/parse.y" +#line 2635 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[-1].nd)); } -#line 8143 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8159 "mrbgems/mruby-compiler/core/y.tab.c" break; case 97: /* mlhs_post: mlhs_item */ -#line 2637 "mrbgems/mruby-compiler/core/parse.y" +#line 2641 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 8151 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8167 "mrbgems/mruby-compiler/core/y.tab.c" break; case 98: /* mlhs_post: mlhs_list mlhs_item */ -#line 2641 "mrbgems/mruby-compiler/core/parse.y" +#line 2645 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 8159 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8175 "mrbgems/mruby-compiler/core/y.tab.c" break; case 99: /* mlhs_node: variable */ -#line 2647 "mrbgems/mruby-compiler/core/parse.y" +#line 2651 "mrbgems/mruby-compiler/core/parse.y" { assignable(p, (yyvsp[0].nd)); } -#line 8167 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8183 "mrbgems/mruby-compiler/core/y.tab.c" break; case 100: /* mlhs_node: primary_value '[' opt_call_args ']' */ -#line 2651 "mrbgems/mruby-compiler/core/parse.y" +#line 2655 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), intern_op(aref), (yyvsp[-1].nd), '.'); } -#line 8175 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8191 "mrbgems/mruby-compiler/core/y.tab.c" break; case 101: /* mlhs_node: primary_value call_op "local variable or method" */ -#line 2655 "mrbgems/mruby-compiler/core/parse.y" +#line 2659 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); } -#line 8183 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8199 "mrbgems/mruby-compiler/core/y.tab.c" break; case 102: /* mlhs_node: primary_value "::" "local variable or method" */ -#line 2659 "mrbgems/mruby-compiler/core/parse.y" +#line 2663 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, tCOLON2); } -#line 8191 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8207 "mrbgems/mruby-compiler/core/y.tab.c" break; case 103: /* mlhs_node: primary_value call_op "constant" */ -#line 2663 "mrbgems/mruby-compiler/core/parse.y" +#line 2667 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); } -#line 8199 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8215 "mrbgems/mruby-compiler/core/y.tab.c" break; case 104: /* mlhs_node: primary_value "::" "constant" */ -#line 2667 "mrbgems/mruby-compiler/core/parse.y" +#line 2671 "mrbgems/mruby-compiler/core/parse.y" { if (p->in_def || p->in_single) yyerror(&(yylsp[-2]), p, "dynamic constant assignment"); (yyval.nd) = new_colon2(p, (yyvsp[-2].nd), (yyvsp[0].id)); } -#line 8209 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8225 "mrbgems/mruby-compiler/core/y.tab.c" break; case 105: /* mlhs_node: tCOLON3 "constant" */ -#line 2673 "mrbgems/mruby-compiler/core/parse.y" +#line 2677 "mrbgems/mruby-compiler/core/parse.y" { if (p->in_def || p->in_single) yyerror(&(yylsp[-1]), p, "dynamic constant assignment"); (yyval.nd) = new_colon3(p, (yyvsp[0].id)); } -#line 8219 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8235 "mrbgems/mruby-compiler/core/y.tab.c" break; case 106: /* mlhs_node: backref */ -#line 2679 "mrbgems/mruby-compiler/core/parse.y" +#line 2683 "mrbgems/mruby-compiler/core/parse.y" { backref_error(p, (yyvsp[0].nd)); (yyval.nd) = 0; } -#line 8228 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8244 "mrbgems/mruby-compiler/core/y.tab.c" break; case 107: /* lhs: variable */ -#line 2686 "mrbgems/mruby-compiler/core/parse.y" +#line 2690 "mrbgems/mruby-compiler/core/parse.y" { assignable(p, (yyvsp[0].nd)); } -#line 8236 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8252 "mrbgems/mruby-compiler/core/y.tab.c" break; case 108: /* lhs: primary_value '[' opt_call_args ']' */ -#line 2690 "mrbgems/mruby-compiler/core/parse.y" +#line 2694 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), intern_op(aref), (yyvsp[-1].nd), '.'); } -#line 8244 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8260 "mrbgems/mruby-compiler/core/y.tab.c" break; case 109: /* lhs: primary_value call_op "local variable or method" */ -#line 2694 "mrbgems/mruby-compiler/core/parse.y" +#line 2698 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); } -#line 8252 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8268 "mrbgems/mruby-compiler/core/y.tab.c" break; case 110: /* lhs: primary_value "::" "local variable or method" */ -#line 2698 "mrbgems/mruby-compiler/core/parse.y" +#line 2702 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, tCOLON2); } -#line 8260 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8276 "mrbgems/mruby-compiler/core/y.tab.c" break; case 111: /* lhs: primary_value call_op "constant" */ -#line 2702 "mrbgems/mruby-compiler/core/parse.y" +#line 2706 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); } -#line 8268 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8284 "mrbgems/mruby-compiler/core/y.tab.c" break; case 112: /* lhs: primary_value "::" "constant" */ -#line 2706 "mrbgems/mruby-compiler/core/parse.y" +#line 2710 "mrbgems/mruby-compiler/core/parse.y" { if (p->in_def || p->in_single) yyerror(&(yylsp[-2]), p, "dynamic constant assignment"); (yyval.nd) = new_colon2(p, (yyvsp[-2].nd), (yyvsp[0].id)); } -#line 8278 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8294 "mrbgems/mruby-compiler/core/y.tab.c" break; case 113: /* lhs: tCOLON3 "constant" */ -#line 2712 "mrbgems/mruby-compiler/core/parse.y" +#line 2716 "mrbgems/mruby-compiler/core/parse.y" { if (p->in_def || p->in_single) yyerror(&(yylsp[-1]), p, "dynamic constant assignment"); (yyval.nd) = new_colon3(p, (yyvsp[0].id)); } -#line 8288 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8304 "mrbgems/mruby-compiler/core/y.tab.c" break; case 114: /* lhs: backref */ -#line 2718 "mrbgems/mruby-compiler/core/parse.y" +#line 2722 "mrbgems/mruby-compiler/core/parse.y" { backref_error(p, (yyvsp[0].nd)); (yyval.nd) = 0; } -#line 8297 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8313 "mrbgems/mruby-compiler/core/y.tab.c" break; case 115: /* lhs: "numbered parameter" */ -#line 2723 "mrbgems/mruby-compiler/core/parse.y" +#line 2727 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[0]), p, "can't assign to numbered parameter"); } -#line 8305 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8321 "mrbgems/mruby-compiler/core/y.tab.c" break; case 116: /* cname: "local variable or method" */ -#line 2729 "mrbgems/mruby-compiler/core/parse.y" +#line 2733 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[0]), p, "class/module name must be CONSTANT"); } -#line 8313 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8329 "mrbgems/mruby-compiler/core/y.tab.c" break; case 118: /* cpath: tCOLON3 cname */ -#line 2736 "mrbgems/mruby-compiler/core/parse.y" +#line 2740 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = cons(int_to_node(1), sym_to_node((yyvsp[0].id))); } -#line 8321 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8337 "mrbgems/mruby-compiler/core/y.tab.c" break; case 119: /* cpath: cname */ -#line 2740 "mrbgems/mruby-compiler/core/parse.y" +#line 2744 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = cons(int_to_node(0), sym_to_node((yyvsp[0].id))); } -#line 8329 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8345 "mrbgems/mruby-compiler/core/y.tab.c" break; case 120: /* cpath: primary_value "::" cname */ -#line 2744 "mrbgems/mruby-compiler/core/parse.y" +#line 2748 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[-2].nd)); (yyval.nd) = cons((yyvsp[-2].nd), sym_to_node((yyvsp[0].id))); } -#line 8338 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8354 "mrbgems/mruby-compiler/core/y.tab.c" break; case 124: /* fname: op */ -#line 2754 "mrbgems/mruby-compiler/core/parse.y" +#line 2758 "mrbgems/mruby-compiler/core/parse.y" { p->lstate = EXPR_ENDFN; (yyval.id) = (yyvsp[0].id); } -#line 8347 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8363 "mrbgems/mruby-compiler/core/y.tab.c" break; case 125: /* fname: reswords */ -#line 2759 "mrbgems/mruby-compiler/core/parse.y" +#line 2763 "mrbgems/mruby-compiler/core/parse.y" { p->lstate = EXPR_ENDFN; (yyval.id) = (yyvsp[0].id); } -#line 8356 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8372 "mrbgems/mruby-compiler/core/y.tab.c" break; case 128: /* undef_list: fsym */ -#line 2770 "mrbgems/mruby-compiler/core/parse.y" +#line 2774 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = cons(sym_to_node((yyvsp[0].id)), 0); } -#line 8364 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8380 "mrbgems/mruby-compiler/core/y.tab.c" break; case 129: /* $@8: %empty */ -#line 2773 "mrbgems/mruby-compiler/core/parse.y" +#line 2777 "mrbgems/mruby-compiler/core/parse.y" {p->lstate = EXPR_FNAME;} -#line 8370 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8386 "mrbgems/mruby-compiler/core/y.tab.c" break; case 130: /* undef_list: undef_list ',' $@8 fsym */ -#line 2774 "mrbgems/mruby-compiler/core/parse.y" +#line 2778 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-3].nd), sym_to_node((yyvsp[0].id))); } -#line 8378 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8394 "mrbgems/mruby-compiler/core/y.tab.c" break; case 131: /* op: '|' */ -#line 2779 "mrbgems/mruby-compiler/core/parse.y" +#line 2783 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(or); } -#line 8384 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8400 "mrbgems/mruby-compiler/core/y.tab.c" break; case 132: /* op: '^' */ -#line 2780 "mrbgems/mruby-compiler/core/parse.y" +#line 2784 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(xor); } -#line 8390 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8406 "mrbgems/mruby-compiler/core/y.tab.c" break; case 133: /* op: '&' */ -#line 2781 "mrbgems/mruby-compiler/core/parse.y" +#line 2785 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(and); } -#line 8396 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8412 "mrbgems/mruby-compiler/core/y.tab.c" break; case 134: /* op: "<=>" */ -#line 2782 "mrbgems/mruby-compiler/core/parse.y" +#line 2786 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(cmp); } -#line 8402 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8418 "mrbgems/mruby-compiler/core/y.tab.c" break; case 135: /* op: "==" */ -#line 2783 "mrbgems/mruby-compiler/core/parse.y" +#line 2787 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(eq); } -#line 8408 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8424 "mrbgems/mruby-compiler/core/y.tab.c" break; case 136: /* op: "===" */ -#line 2784 "mrbgems/mruby-compiler/core/parse.y" +#line 2788 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(eqq); } -#line 8414 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8430 "mrbgems/mruby-compiler/core/y.tab.c" break; case 137: /* op: "=~" */ -#line 2785 "mrbgems/mruby-compiler/core/parse.y" +#line 2789 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(match); } -#line 8420 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8436 "mrbgems/mruby-compiler/core/y.tab.c" break; case 138: /* op: "!~" */ -#line 2786 "mrbgems/mruby-compiler/core/parse.y" +#line 2790 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(nmatch); } -#line 8426 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8442 "mrbgems/mruby-compiler/core/y.tab.c" break; case 139: /* op: '>' */ -#line 2787 "mrbgems/mruby-compiler/core/parse.y" +#line 2791 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(gt); } -#line 8432 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8448 "mrbgems/mruby-compiler/core/y.tab.c" break; case 140: /* op: ">=" */ -#line 2788 "mrbgems/mruby-compiler/core/parse.y" +#line 2792 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(ge); } -#line 8438 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8454 "mrbgems/mruby-compiler/core/y.tab.c" break; case 141: /* op: '<' */ -#line 2789 "mrbgems/mruby-compiler/core/parse.y" +#line 2793 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(lt); } -#line 8444 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8460 "mrbgems/mruby-compiler/core/y.tab.c" break; case 142: /* op: "<=" */ -#line 2790 "mrbgems/mruby-compiler/core/parse.y" +#line 2794 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(le); } -#line 8450 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8466 "mrbgems/mruby-compiler/core/y.tab.c" break; case 143: /* op: "!=" */ -#line 2791 "mrbgems/mruby-compiler/core/parse.y" +#line 2795 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(neq); } -#line 8456 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8472 "mrbgems/mruby-compiler/core/y.tab.c" break; case 144: /* op: "<<" */ -#line 2792 "mrbgems/mruby-compiler/core/parse.y" +#line 2796 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(lshift); } -#line 8462 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8478 "mrbgems/mruby-compiler/core/y.tab.c" break; case 145: /* op: ">>" */ -#line 2793 "mrbgems/mruby-compiler/core/parse.y" +#line 2797 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(rshift); } -#line 8468 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8484 "mrbgems/mruby-compiler/core/y.tab.c" break; case 146: /* op: '+' */ -#line 2794 "mrbgems/mruby-compiler/core/parse.y" +#line 2798 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(add); } -#line 8474 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8490 "mrbgems/mruby-compiler/core/y.tab.c" break; case 147: /* op: '-' */ -#line 2795 "mrbgems/mruby-compiler/core/parse.y" +#line 2799 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(sub); } -#line 8480 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8496 "mrbgems/mruby-compiler/core/y.tab.c" break; case 148: /* op: '*' */ -#line 2796 "mrbgems/mruby-compiler/core/parse.y" +#line 2800 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(mul); } -#line 8486 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8502 "mrbgems/mruby-compiler/core/y.tab.c" break; case 149: /* op: "*" */ -#line 2797 "mrbgems/mruby-compiler/core/parse.y" +#line 2801 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(mul); } -#line 8492 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8508 "mrbgems/mruby-compiler/core/y.tab.c" break; case 150: /* op: '/' */ -#line 2798 "mrbgems/mruby-compiler/core/parse.y" +#line 2802 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(div); } -#line 8498 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8514 "mrbgems/mruby-compiler/core/y.tab.c" break; case 151: /* op: '%' */ -#line 2799 "mrbgems/mruby-compiler/core/parse.y" +#line 2803 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(mod); } -#line 8504 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8520 "mrbgems/mruby-compiler/core/y.tab.c" break; case 152: /* op: tPOW */ -#line 2800 "mrbgems/mruby-compiler/core/parse.y" +#line 2804 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(pow); } -#line 8510 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8526 "mrbgems/mruby-compiler/core/y.tab.c" break; case 153: /* op: "**" */ -#line 2801 "mrbgems/mruby-compiler/core/parse.y" +#line 2805 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(pow); } -#line 8516 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8532 "mrbgems/mruby-compiler/core/y.tab.c" break; case 154: /* op: '!' */ -#line 2802 "mrbgems/mruby-compiler/core/parse.y" +#line 2806 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(not); } -#line 8522 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8538 "mrbgems/mruby-compiler/core/y.tab.c" break; case 155: /* op: '~' */ -#line 2803 "mrbgems/mruby-compiler/core/parse.y" +#line 2807 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(neg); } -#line 8528 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8544 "mrbgems/mruby-compiler/core/y.tab.c" break; case 156: /* op: "unary plus" */ -#line 2804 "mrbgems/mruby-compiler/core/parse.y" +#line 2808 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(plus); } -#line 8534 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8550 "mrbgems/mruby-compiler/core/y.tab.c" break; case 157: /* op: "unary minus" */ -#line 2805 "mrbgems/mruby-compiler/core/parse.y" +#line 2809 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(minus); } -#line 8540 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8556 "mrbgems/mruby-compiler/core/y.tab.c" break; case 158: /* op: tAREF */ -#line 2806 "mrbgems/mruby-compiler/core/parse.y" +#line 2810 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(aref); } -#line 8546 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8562 "mrbgems/mruby-compiler/core/y.tab.c" break; case 159: /* op: tASET */ -#line 2807 "mrbgems/mruby-compiler/core/parse.y" +#line 2811 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(aset); } -#line 8552 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8568 "mrbgems/mruby-compiler/core/y.tab.c" break; case 160: /* op: '`' */ -#line 2808 "mrbgems/mruby-compiler/core/parse.y" +#line 2812 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(tick); } -#line 8558 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8574 "mrbgems/mruby-compiler/core/y.tab.c" break; case 201: /* arg: lhs '=' arg_rhs */ -#line 2826 "mrbgems/mruby-compiler/core/parse.y" +#line 2830 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_asgn(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 8566 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8582 "mrbgems/mruby-compiler/core/y.tab.c" break; case 202: /* arg: var_lhs tOP_ASGN arg_rhs */ -#line 2830 "mrbgems/mruby-compiler/core/parse.y" +#line 2834 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, (yyvsp[-2].nd), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 8574 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8590 "mrbgems/mruby-compiler/core/y.tab.c" break; case 203: /* arg: primary_value '[' opt_call_args ']' tOP_ASGN arg_rhs */ -#line 2834 "mrbgems/mruby-compiler/core/parse.y" +#line 2838 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, new_call(p, (yyvsp[-5].nd), intern_op(aref), (yyvsp[-3].nd), '.'), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 8582 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8598 "mrbgems/mruby-compiler/core/y.tab.c" break; case 204: /* arg: primary_value call_op "local variable or method" tOP_ASGN arg_rhs */ -#line 2838 "mrbgems/mruby-compiler/core/parse.y" +#line 2842 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, new_call(p, (yyvsp[-4].nd), (yyvsp[-2].id), 0, (yyvsp[-3].num)), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 8590 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8606 "mrbgems/mruby-compiler/core/y.tab.c" break; case 205: /* arg: primary_value call_op "constant" tOP_ASGN arg_rhs */ -#line 2842 "mrbgems/mruby-compiler/core/parse.y" +#line 2846 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, new_call(p, (yyvsp[-4].nd), (yyvsp[-2].id), 0, (yyvsp[-3].num)), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 8598 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8614 "mrbgems/mruby-compiler/core/y.tab.c" break; case 206: /* arg: primary_value "::" "local variable or method" tOP_ASGN arg_rhs */ -#line 2846 "mrbgems/mruby-compiler/core/parse.y" +#line 2850 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, new_call(p, (yyvsp[-4].nd), (yyvsp[-2].id), 0, tCOLON2), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 8606 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8622 "mrbgems/mruby-compiler/core/y.tab.c" break; case 207: /* arg: primary_value "::" "constant" tOP_ASGN arg_rhs */ -#line 2850 "mrbgems/mruby-compiler/core/parse.y" +#line 2854 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[-4]), p, "constant re-assignment"); (yyval.nd) = new_stmts(p, 0); } -#line 8615 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8631 "mrbgems/mruby-compiler/core/y.tab.c" break; case 208: /* arg: tCOLON3 "constant" tOP_ASGN arg_rhs */ -#line 2855 "mrbgems/mruby-compiler/core/parse.y" +#line 2859 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[-3]), p, "constant re-assignment"); (yyval.nd) = new_stmts(p, 0); } -#line 8624 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8640 "mrbgems/mruby-compiler/core/y.tab.c" break; case 209: /* arg: backref tOP_ASGN arg_rhs */ -#line 2860 "mrbgems/mruby-compiler/core/parse.y" +#line 2864 "mrbgems/mruby-compiler/core/parse.y" { backref_error(p, (yyvsp[-2].nd)); (yyval.nd) = new_stmts(p, 0); } -#line 8633 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8649 "mrbgems/mruby-compiler/core/y.tab.c" break; case 210: /* arg: arg ".." arg */ -#line 2865 "mrbgems/mruby-compiler/core/parse.y" +#line 2869 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_dot2(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 8641 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8657 "mrbgems/mruby-compiler/core/y.tab.c" break; case 211: /* arg: arg ".." */ -#line 2869 "mrbgems/mruby-compiler/core/parse.y" +#line 2873 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_dot2(p, (yyvsp[-1].nd), new_nil(p)); } -#line 8649 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8665 "mrbgems/mruby-compiler/core/y.tab.c" break; case 212: /* arg: tBDOT2 arg */ -#line 2873 "mrbgems/mruby-compiler/core/parse.y" +#line 2877 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_dot2(p, new_nil(p), (yyvsp[0].nd)); } -#line 8657 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8673 "mrbgems/mruby-compiler/core/y.tab.c" break; case 213: /* arg: arg "..." arg */ -#line 2877 "mrbgems/mruby-compiler/core/parse.y" +#line 2881 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_dot3(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 8665 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8681 "mrbgems/mruby-compiler/core/y.tab.c" break; case 214: /* arg: arg "..." */ -#line 2881 "mrbgems/mruby-compiler/core/parse.y" +#line 2885 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_dot3(p, (yyvsp[-1].nd), new_nil(p)); } -#line 8673 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8689 "mrbgems/mruby-compiler/core/y.tab.c" break; case 215: /* arg: tBDOT3 arg */ -#line 2885 "mrbgems/mruby-compiler/core/parse.y" +#line 2889 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_dot3(p, new_nil(p), (yyvsp[0].nd)); } -#line 8681 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8697 "mrbgems/mruby-compiler/core/y.tab.c" break; case 216: /* arg: arg '+' arg */ -#line 2889 "mrbgems/mruby-compiler/core/parse.y" +#line 2893 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "+", (yyvsp[0].nd)); } -#line 8689 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8705 "mrbgems/mruby-compiler/core/y.tab.c" break; case 217: /* arg: arg '-' arg */ -#line 2893 "mrbgems/mruby-compiler/core/parse.y" +#line 2897 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "-", (yyvsp[0].nd)); } -#line 8697 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8713 "mrbgems/mruby-compiler/core/y.tab.c" break; case 218: /* arg: arg '*' arg */ -#line 2897 "mrbgems/mruby-compiler/core/parse.y" +#line 2901 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "*", (yyvsp[0].nd)); } -#line 8705 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8721 "mrbgems/mruby-compiler/core/y.tab.c" break; case 219: /* arg: arg '/' arg */ -#line 2901 "mrbgems/mruby-compiler/core/parse.y" +#line 2905 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "/", (yyvsp[0].nd)); } -#line 8713 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8729 "mrbgems/mruby-compiler/core/y.tab.c" break; case 220: /* arg: arg '%' arg */ -#line 2905 "mrbgems/mruby-compiler/core/parse.y" +#line 2909 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "%", (yyvsp[0].nd)); } -#line 8721 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8737 "mrbgems/mruby-compiler/core/y.tab.c" break; case 221: /* arg: arg tPOW arg */ -#line 2909 "mrbgems/mruby-compiler/core/parse.y" +#line 2913 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "**", (yyvsp[0].nd)); } -#line 8729 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8745 "mrbgems/mruby-compiler/core/y.tab.c" break; case 222: /* arg: tUMINUS_NUM "integer literal" tPOW arg */ -#line 2913 "mrbgems/mruby-compiler/core/parse.y" +#line 2917 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_negate(p, call_bin_op(p, (yyvsp[-2].nd), "**", (yyvsp[0].nd))); } -#line 8737 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8753 "mrbgems/mruby-compiler/core/y.tab.c" break; case 223: /* arg: tUMINUS_NUM "float literal" tPOW arg */ -#line 2917 "mrbgems/mruby-compiler/core/parse.y" +#line 2921 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_negate(p, call_bin_op(p, (yyvsp[-2].nd), "**", (yyvsp[0].nd))); } -#line 8745 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8761 "mrbgems/mruby-compiler/core/y.tab.c" break; case 224: /* arg: "unary plus" arg */ -#line 2921 "mrbgems/mruby-compiler/core/parse.y" +#line 2925 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, (yyvsp[0].nd), "+@"); } -#line 8753 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8769 "mrbgems/mruby-compiler/core/y.tab.c" break; case 225: /* arg: "unary minus" arg */ -#line 2925 "mrbgems/mruby-compiler/core/parse.y" +#line 2929 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_negate(p, (yyvsp[0].nd)); } -#line 8761 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8777 "mrbgems/mruby-compiler/core/y.tab.c" break; case 226: /* arg: arg '|' arg */ -#line 2929 "mrbgems/mruby-compiler/core/parse.y" +#line 2933 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "|", (yyvsp[0].nd)); } -#line 8769 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8785 "mrbgems/mruby-compiler/core/y.tab.c" break; case 227: /* arg: arg '^' arg */ -#line 2933 "mrbgems/mruby-compiler/core/parse.y" +#line 2937 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "^", (yyvsp[0].nd)); } -#line 8777 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8793 "mrbgems/mruby-compiler/core/y.tab.c" break; case 228: /* arg: arg '&' arg */ -#line 2937 "mrbgems/mruby-compiler/core/parse.y" +#line 2941 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "&", (yyvsp[0].nd)); } -#line 8785 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8801 "mrbgems/mruby-compiler/core/y.tab.c" break; case 229: /* arg: arg "<=>" arg */ -#line 2941 "mrbgems/mruby-compiler/core/parse.y" +#line 2945 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<=>", (yyvsp[0].nd)); } -#line 8793 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8809 "mrbgems/mruby-compiler/core/y.tab.c" break; case 230: /* arg: arg '>' arg */ -#line 2945 "mrbgems/mruby-compiler/core/parse.y" +#line 2949 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), ">", (yyvsp[0].nd)); } -#line 8801 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8817 "mrbgems/mruby-compiler/core/y.tab.c" break; case 231: /* arg: arg ">=" arg */ -#line 2949 "mrbgems/mruby-compiler/core/parse.y" +#line 2953 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), ">=", (yyvsp[0].nd)); } -#line 8809 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8825 "mrbgems/mruby-compiler/core/y.tab.c" break; case 232: /* arg: arg '<' arg */ -#line 2953 "mrbgems/mruby-compiler/core/parse.y" +#line 2957 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<", (yyvsp[0].nd)); } -#line 8817 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8833 "mrbgems/mruby-compiler/core/y.tab.c" break; case 233: /* arg: arg "<=" arg */ -#line 2957 "mrbgems/mruby-compiler/core/parse.y" +#line 2961 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<=", (yyvsp[0].nd)); } -#line 8825 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8841 "mrbgems/mruby-compiler/core/y.tab.c" break; case 234: /* arg: arg "==" arg */ -#line 2961 "mrbgems/mruby-compiler/core/parse.y" +#line 2965 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "==", (yyvsp[0].nd)); } -#line 8833 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8849 "mrbgems/mruby-compiler/core/y.tab.c" break; case 235: /* arg: arg "===" arg */ -#line 2965 "mrbgems/mruby-compiler/core/parse.y" +#line 2969 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "===", (yyvsp[0].nd)); } -#line 8841 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8857 "mrbgems/mruby-compiler/core/y.tab.c" break; case 236: /* arg: arg "!=" arg */ -#line 2969 "mrbgems/mruby-compiler/core/parse.y" +#line 2973 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "!=", (yyvsp[0].nd)); } -#line 8849 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8865 "mrbgems/mruby-compiler/core/y.tab.c" break; case 237: /* arg: arg "=~" arg */ -#line 2973 "mrbgems/mruby-compiler/core/parse.y" +#line 2977 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "=~", (yyvsp[0].nd)); } -#line 8857 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8873 "mrbgems/mruby-compiler/core/y.tab.c" break; case 238: /* arg: arg "!~" arg */ -#line 2977 "mrbgems/mruby-compiler/core/parse.y" +#line 2981 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "!~", (yyvsp[0].nd)); } -#line 8865 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8881 "mrbgems/mruby-compiler/core/y.tab.c" break; case 239: /* arg: '!' arg */ -#line 2981 "mrbgems/mruby-compiler/core/parse.y" +#line 2985 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, cond((yyvsp[0].nd)), "!"); } -#line 8873 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8889 "mrbgems/mruby-compiler/core/y.tab.c" break; case 240: /* arg: '~' arg */ -#line 2985 "mrbgems/mruby-compiler/core/parse.y" +#line 2989 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, cond((yyvsp[0].nd)), "~"); } -#line 8881 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8897 "mrbgems/mruby-compiler/core/y.tab.c" break; case 241: /* arg: arg "<<" arg */ -#line 2989 "mrbgems/mruby-compiler/core/parse.y" +#line 2993 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<<", (yyvsp[0].nd)); } -#line 8889 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8905 "mrbgems/mruby-compiler/core/y.tab.c" break; case 242: /* arg: arg ">>" arg */ -#line 2993 "mrbgems/mruby-compiler/core/parse.y" +#line 2997 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), ">>", (yyvsp[0].nd)); } -#line 8897 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8913 "mrbgems/mruby-compiler/core/y.tab.c" break; case 243: /* arg: arg "&&" arg */ -#line 2997 "mrbgems/mruby-compiler/core/parse.y" +#line 3001 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_and(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 8905 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8921 "mrbgems/mruby-compiler/core/y.tab.c" break; case 244: /* arg: arg "||" arg */ -#line 3001 "mrbgems/mruby-compiler/core/parse.y" +#line 3005 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_or(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 8913 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8929 "mrbgems/mruby-compiler/core/y.tab.c" break; case 245: /* arg: arg '?' arg opt_nl ':' arg */ -#line 3005 "mrbgems/mruby-compiler/core/parse.y" +#line 3009 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[-5].nd)), (yyvsp[-3].nd), (yyvsp[0].nd)); } -#line 8921 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8937 "mrbgems/mruby-compiler/core/y.tab.c" break; case 246: /* arg: arg '?' arg opt_nl "label" arg */ -#line 3009 "mrbgems/mruby-compiler/core/parse.y" +#line 3013 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[-5].nd)), (yyvsp[-3].nd), (yyvsp[0].nd)); } -#line 8929 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8945 "mrbgems/mruby-compiler/core/y.tab.c" break; case 247: /* arg: defn_head f_opt_arglist_paren '=' arg */ -#line 3013 "mrbgems/mruby-compiler/core/parse.y" +#line 3017 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); endless_method_name(p, (yyvsp[-3].nd)); @@ -8938,11 +8954,11 @@ YYLTYPE yylloc = yyloc_default; nvars_unnest(p); p->in_def--; } -#line 8942 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8958 "mrbgems/mruby-compiler/core/y.tab.c" break; case 248: /* arg: defn_head f_opt_arglist_paren '=' arg "'rescue' modifier" arg */ -#line 3022 "mrbgems/mruby-compiler/core/parse.y" +#line 3026 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-5].nd); endless_method_name(p, (yyvsp[-5].nd)); @@ -8951,11 +8967,11 @@ YYLTYPE yylloc = yyloc_default; nvars_unnest(p); p->in_def--; } -#line 8955 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8971 "mrbgems/mruby-compiler/core/y.tab.c" break; case 249: /* arg: defs_head f_opt_arglist_paren '=' arg */ -#line 3031 "mrbgems/mruby-compiler/core/parse.y" +#line 3035 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); void_expr_error(p, (yyvsp[0].nd)); @@ -8964,11 +8980,11 @@ YYLTYPE yylloc = yyloc_default; p->in_def--; p->in_single--; } -#line 8968 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8984 "mrbgems/mruby-compiler/core/y.tab.c" break; case 250: /* arg: defs_head f_opt_arglist_paren '=' arg "'rescue' modifier" arg */ -#line 3040 "mrbgems/mruby-compiler/core/parse.y" +#line 3044 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-5].nd); void_expr_error(p, (yyvsp[-2].nd)); @@ -8977,68 +8993,68 @@ YYLTYPE yylloc = yyloc_default; p->in_def--; p->in_single--; } -#line 8981 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8997 "mrbgems/mruby-compiler/core/y.tab.c" break; case 251: /* arg: primary */ -#line 3049 "mrbgems/mruby-compiler/core/parse.y" +#line 3053 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 8989 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9005 "mrbgems/mruby-compiler/core/y.tab.c" break; case 253: /* aref_args: args trailer */ -#line 3056 "mrbgems/mruby-compiler/core/parse.y" +#line 3060 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 8997 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9013 "mrbgems/mruby-compiler/core/y.tab.c" break; case 254: /* aref_args: args comma assocs trailer */ -#line 3060 "mrbgems/mruby-compiler/core/parse.y" +#line 3064 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-3].nd), new_hash(p, (yyvsp[-1].nd))); } -#line 9005 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9021 "mrbgems/mruby-compiler/core/y.tab.c" break; case 255: /* aref_args: assocs trailer */ -#line 3064 "mrbgems/mruby-compiler/core/parse.y" +#line 3068 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = cons(new_hash(p, (yyvsp[-1].nd)), 0); } -#line 9013 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9029 "mrbgems/mruby-compiler/core/y.tab.c" break; case 256: /* arg_rhs: arg */ -#line 3070 "mrbgems/mruby-compiler/core/parse.y" +#line 3074 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 9021 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9037 "mrbgems/mruby-compiler/core/y.tab.c" break; case 257: /* arg_rhs: arg "'rescue' modifier" arg */ -#line 3074 "mrbgems/mruby-compiler/core/parse.y" +#line 3078 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[-2].nd)); (yyval.nd) = new_mod_rescue(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 9030 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9046 "mrbgems/mruby-compiler/core/y.tab.c" break; case 258: /* paren_args: '(' opt_call_args ')' */ -#line 3081 "mrbgems/mruby-compiler/core/parse.y" +#line 3085 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 9038 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9054 "mrbgems/mruby-compiler/core/y.tab.c" break; case 259: /* paren_args: '(' args comma tBDOT3 rparen */ -#line 3085 "mrbgems/mruby-compiler/core/parse.y" +#line 3089 "mrbgems/mruby-compiler/core/parse.y" { mrb_sym r = intern_op(mul); mrb_sym k = intern_op(pow); @@ -9047,11 +9063,11 @@ YYLTYPE yylloc = yyloc_default; list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k))), new_block_arg(p, new_lvar(p, b))); } -#line 9051 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9067 "mrbgems/mruby-compiler/core/y.tab.c" break; case 260: /* paren_args: '(' tBDOT3 rparen */ -#line 3094 "mrbgems/mruby-compiler/core/parse.y" +#line 3098 "mrbgems/mruby-compiler/core/parse.y" { mrb_sym r = intern_op(mul); mrb_sym k = intern_op(pow); @@ -9066,390 +9082,390 @@ YYLTYPE yylloc = yyloc_default; (yyval.nd) = 0; } } -#line 9070 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9086 "mrbgems/mruby-compiler/core/y.tab.c" break; case 265: /* opt_call_args: args comma */ -#line 3117 "mrbgems/mruby-compiler/core/parse.y" +#line 3121 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_callargs(p,(yyvsp[-1].nd),0,0); } -#line 9078 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9094 "mrbgems/mruby-compiler/core/y.tab.c" break; case 266: /* opt_call_args: args comma assocs comma */ -#line 3121 "mrbgems/mruby-compiler/core/parse.y" +#line 3125 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_callargs(p,(yyvsp[-3].nd),(yyvsp[-1].nd),0); } -#line 9086 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9102 "mrbgems/mruby-compiler/core/y.tab.c" break; case 267: /* opt_call_args: assocs comma */ -#line 3125 "mrbgems/mruby-compiler/core/parse.y" +#line 3129 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_callargs(p,0,(yyvsp[-1].nd),0); } -#line 9094 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9110 "mrbgems/mruby-compiler/core/y.tab.c" break; case 268: /* call_args: command */ -#line 3131 "mrbgems/mruby-compiler/core/parse.y" +#line 3135 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = new_callargs(p, list1((yyvsp[0].nd)), 0, 0); } -#line 9103 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9119 "mrbgems/mruby-compiler/core/y.tab.c" break; case 269: /* call_args: args opt_block_arg */ -#line 3136 "mrbgems/mruby-compiler/core/parse.y" +#line 3140 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_callargs(p, (yyvsp[-1].nd), 0, (yyvsp[0].nd)); } -#line 9111 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9127 "mrbgems/mruby-compiler/core/y.tab.c" break; case 270: /* call_args: assocs opt_block_arg */ -#line 3140 "mrbgems/mruby-compiler/core/parse.y" +#line 3144 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_callargs(p, 0, (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9119 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9135 "mrbgems/mruby-compiler/core/y.tab.c" break; case 271: /* call_args: args comma assocs opt_block_arg */ -#line 3144 "mrbgems/mruby-compiler/core/parse.y" +#line 3148 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_callargs(p, (yyvsp[-3].nd), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9127 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9143 "mrbgems/mruby-compiler/core/y.tab.c" break; case 272: /* call_args: block_arg */ -#line 3148 "mrbgems/mruby-compiler/core/parse.y" +#line 3152 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_callargs(p, 0, 0, (yyvsp[0].nd)); } -#line 9135 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9151 "mrbgems/mruby-compiler/core/y.tab.c" break; case 273: /* @9: %empty */ -#line 3153 "mrbgems/mruby-compiler/core/parse.y" +#line 3157 "mrbgems/mruby-compiler/core/parse.y" { (yyval.stack) = p->cmdarg_stack; CMDARG_PUSH(1); } -#line 9144 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9160 "mrbgems/mruby-compiler/core/y.tab.c" break; case 274: /* command_args: @9 call_args */ -#line 3158 "mrbgems/mruby-compiler/core/parse.y" +#line 3162 "mrbgems/mruby-compiler/core/parse.y" { p->cmdarg_stack = (yyvsp[-1].stack); (yyval.nd) = (yyvsp[0].nd); } -#line 9153 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9169 "mrbgems/mruby-compiler/core/y.tab.c" break; case 275: /* block_arg: "&" arg */ -#line 3165 "mrbgems/mruby-compiler/core/parse.y" +#line 3169 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_block_arg(p, (yyvsp[0].nd)); } -#line 9161 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9177 "mrbgems/mruby-compiler/core/y.tab.c" break; case 276: /* block_arg: "&" */ -#line 3169 "mrbgems/mruby-compiler/core/parse.y" +#line 3173 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_block_arg(p, 0); } -#line 9169 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9185 "mrbgems/mruby-compiler/core/y.tab.c" break; case 277: /* opt_block_arg: comma block_arg */ -#line 3175 "mrbgems/mruby-compiler/core/parse.y" +#line 3179 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 9177 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9193 "mrbgems/mruby-compiler/core/y.tab.c" break; case 278: /* opt_block_arg: none */ -#line 3179 "mrbgems/mruby-compiler/core/parse.y" +#line 3183 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 9185 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9201 "mrbgems/mruby-compiler/core/y.tab.c" break; case 280: /* args: arg */ -#line 3188 "mrbgems/mruby-compiler/core/parse.y" +#line 3192 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = list1((yyvsp[0].nd)); } -#line 9194 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9210 "mrbgems/mruby-compiler/core/y.tab.c" break; case 281: /* args: "*" */ -#line 3193 "mrbgems/mruby-compiler/core/parse.y" +#line 3197 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(new_splat(p, new_lvar(p, intern_op(mul)))); } -#line 9202 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9218 "mrbgems/mruby-compiler/core/y.tab.c" break; case 282: /* args: "*" arg */ -#line 3197 "mrbgems/mruby-compiler/core/parse.y" +#line 3201 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(new_splat(p, (yyvsp[0].nd))); } -#line 9210 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9226 "mrbgems/mruby-compiler/core/y.tab.c" break; case 283: /* args: args comma arg */ -#line 3201 "mrbgems/mruby-compiler/core/parse.y" +#line 3205 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 9219 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9235 "mrbgems/mruby-compiler/core/y.tab.c" break; case 284: /* args: args comma "*" */ -#line 3206 "mrbgems/mruby-compiler/core/parse.y" +#line 3210 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), new_splat(p, new_lvar(p, intern_op(mul)))); } -#line 9227 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9243 "mrbgems/mruby-compiler/core/y.tab.c" break; case 285: /* args: args comma "*" arg */ -#line 3210 "mrbgems/mruby-compiler/core/parse.y" +#line 3214 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-3].nd), new_splat(p, (yyvsp[0].nd))); } -#line 9235 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9251 "mrbgems/mruby-compiler/core/y.tab.c" break; case 286: /* mrhs: args comma arg */ -#line 3216 "mrbgems/mruby-compiler/core/parse.y" +#line 3220 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 9244 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9260 "mrbgems/mruby-compiler/core/y.tab.c" break; case 287: /* mrhs: args comma "*" arg */ -#line 3221 "mrbgems/mruby-compiler/core/parse.y" +#line 3225 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-3].nd), new_splat(p, (yyvsp[0].nd))); } -#line 9252 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9268 "mrbgems/mruby-compiler/core/y.tab.c" break; case 288: /* mrhs: "*" arg */ -#line 3225 "mrbgems/mruby-compiler/core/parse.y" +#line 3229 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(new_splat(p, (yyvsp[0].nd))); } -#line 9260 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9276 "mrbgems/mruby-compiler/core/y.tab.c" break; case 290: /* primary: string */ -#line 3232 "mrbgems/mruby-compiler/core/parse.y" +#line 3236 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_str(p, (yyvsp[0].nd)); } -#line 9268 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9284 "mrbgems/mruby-compiler/core/y.tab.c" break; case 291: /* primary: xstring */ -#line 3236 "mrbgems/mruby-compiler/core/parse.y" +#line 3240 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_xstr(p, (yyvsp[0].nd)); } -#line 9276 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9292 "mrbgems/mruby-compiler/core/y.tab.c" break; case 296: /* primary: "method" */ -#line 3244 "mrbgems/mruby-compiler/core/parse.y" +#line 3248 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_fcall(p, (yyvsp[0].id), 0); } -#line 9284 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9300 "mrbgems/mruby-compiler/core/y.tab.c" break; case 297: /* @10: %empty */ -#line 3248 "mrbgems/mruby-compiler/core/parse.y" +#line 3252 "mrbgems/mruby-compiler/core/parse.y" { (yyval.stack) = p->cmdarg_stack; p->cmdarg_stack = 0; } -#line 9293 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9309 "mrbgems/mruby-compiler/core/y.tab.c" break; case 298: /* primary: "'begin'" @10 bodystmt "'end'" */ -#line 3254 "mrbgems/mruby-compiler/core/parse.y" +#line 3258 "mrbgems/mruby-compiler/core/parse.y" { p->cmdarg_stack = (yyvsp[-2].stack); (yyval.nd) = new_begin(p, (yyvsp[-1].nd)); } -#line 9302 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9318 "mrbgems/mruby-compiler/core/y.tab.c" break; case 299: /* @11: %empty */ -#line 3259 "mrbgems/mruby-compiler/core/parse.y" +#line 3263 "mrbgems/mruby-compiler/core/parse.y" { (yyval.stack) = p->cmdarg_stack; p->cmdarg_stack = 0; } -#line 9311 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9327 "mrbgems/mruby-compiler/core/y.tab.c" break; case 300: /* $@12: %empty */ -#line 3263 "mrbgems/mruby-compiler/core/parse.y" +#line 3267 "mrbgems/mruby-compiler/core/parse.y" {p->lstate = EXPR_ENDARG;} -#line 9317 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9333 "mrbgems/mruby-compiler/core/y.tab.c" break; case 301: /* primary: "(" @11 compstmt $@12 rparen */ -#line 3264 "mrbgems/mruby-compiler/core/parse.y" +#line 3268 "mrbgems/mruby-compiler/core/parse.y" { p->cmdarg_stack = (yyvsp[-3].stack); (yyval.nd) = (yyvsp[-2].nd); } -#line 9326 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9342 "mrbgems/mruby-compiler/core/y.tab.c" break; case 302: /* $@13: %empty */ -#line 3268 "mrbgems/mruby-compiler/core/parse.y" +#line 3272 "mrbgems/mruby-compiler/core/parse.y" {p->lstate = EXPR_ENDARG;} -#line 9332 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9348 "mrbgems/mruby-compiler/core/y.tab.c" break; case 303: /* primary: "(" $@13 rparen */ -#line 3269 "mrbgems/mruby-compiler/core/parse.y" +#line 3273 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_nil(p); } -#line 9340 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9356 "mrbgems/mruby-compiler/core/y.tab.c" break; case 304: /* primary: tLPAREN compstmt ')' */ -#line 3273 "mrbgems/mruby-compiler/core/parse.y" +#line 3277 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 9348 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9364 "mrbgems/mruby-compiler/core/y.tab.c" break; case 305: /* primary: primary_value "::" "constant" */ -#line 3277 "mrbgems/mruby-compiler/core/parse.y" +#line 3281 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_colon2(p, (yyvsp[-2].nd), (yyvsp[0].id)); } -#line 9356 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9372 "mrbgems/mruby-compiler/core/y.tab.c" break; case 306: /* primary: tCOLON3 "constant" */ -#line 3281 "mrbgems/mruby-compiler/core/parse.y" +#line 3285 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_colon3(p, (yyvsp[0].id)); } -#line 9364 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9380 "mrbgems/mruby-compiler/core/y.tab.c" break; case 307: /* primary: "[" aref_args ']' */ -#line 3285 "mrbgems/mruby-compiler/core/parse.y" +#line 3289 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_array(p, (yyvsp[-1].nd)); } -#line 9372 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9388 "mrbgems/mruby-compiler/core/y.tab.c" break; case 308: /* primary: tLBRACE assoc_list '}' */ -#line 3289 "mrbgems/mruby-compiler/core/parse.y" +#line 3293 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_hash(p, (yyvsp[-1].nd)); } -#line 9380 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9396 "mrbgems/mruby-compiler/core/y.tab.c" break; case 309: /* primary: "'return'" */ -#line 3293 "mrbgems/mruby-compiler/core/parse.y" +#line 3297 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_return(p, 0); } -#line 9388 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9404 "mrbgems/mruby-compiler/core/y.tab.c" break; case 310: /* primary: "'yield'" opt_paren_args */ -#line 3297 "mrbgems/mruby-compiler/core/parse.y" +#line 3301 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_yield(p, (yyvsp[0].nd)); } -#line 9396 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9412 "mrbgems/mruby-compiler/core/y.tab.c" break; case 311: /* primary: "'not'" '(' expr rparen */ -#line 3301 "mrbgems/mruby-compiler/core/parse.y" +#line 3305 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, cond((yyvsp[-1].nd)), "!"); } -#line 9404 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9420 "mrbgems/mruby-compiler/core/y.tab.c" break; case 312: /* primary: "'not'" '(' rparen */ -#line 3305 "mrbgems/mruby-compiler/core/parse.y" +#line 3309 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, new_nil(p), "!"); } -#line 9412 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9428 "mrbgems/mruby-compiler/core/y.tab.c" break; case 313: /* primary: operation brace_block */ -#line 3309 "mrbgems/mruby-compiler/core/parse.y" +#line 3313 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_fcall(p, (yyvsp[-1].id), new_callargs(p, 0, 0, (yyvsp[0].nd))); } -#line 9420 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9436 "mrbgems/mruby-compiler/core/y.tab.c" break; case 315: /* primary: method_call brace_block */ -#line 3314 "mrbgems/mruby-compiler/core/parse.y" +#line 3318 "mrbgems/mruby-compiler/core/parse.y" { call_with_block(p, (yyvsp[-1].nd), (yyvsp[0].nd)); (yyval.nd) = (yyvsp[-1].nd); } -#line 9429 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9445 "mrbgems/mruby-compiler/core/y.tab.c" break; case 316: /* @14: %empty */ -#line 3319 "mrbgems/mruby-compiler/core/parse.y" +#line 3323 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); nvars_nest(p); (yyval.num) = p->lpar_beg; p->lpar_beg = ++p->paren_nest; } -#line 9440 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9456 "mrbgems/mruby-compiler/core/y.tab.c" break; case 317: /* @15: %empty */ -#line 3326 "mrbgems/mruby-compiler/core/parse.y" +#line 3330 "mrbgems/mruby-compiler/core/parse.y" { (yyval.stack) = p->cmdarg_stack; p->cmdarg_stack = 0; } -#line 9449 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9465 "mrbgems/mruby-compiler/core/y.tab.c" break; case 318: /* primary: "->" @14 f_larglist @15 lambda_body */ -#line 3331 "mrbgems/mruby-compiler/core/parse.y" +#line 3335 "mrbgems/mruby-compiler/core/parse.y" { p->lpar_beg = (yyvsp[-3].num); (yyval.nd) = new_lambda(p, (yyvsp[-2].nd), (yyvsp[0].nd)); @@ -9458,176 +9474,176 @@ YYLTYPE yylloc = yyloc_default; p->cmdarg_stack = (yyvsp[-1].stack); CMDARG_LEXPOP(); } -#line 9462 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9478 "mrbgems/mruby-compiler/core/y.tab.c" break; case 319: /* primary: "'if'" expr_value then compstmt if_tail "'end'" */ -#line 3343 "mrbgems/mruby-compiler/core/parse.y" +#line 3347 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[-4].nd)), (yyvsp[-2].nd), (yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-5].num)); } -#line 9471 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9487 "mrbgems/mruby-compiler/core/y.tab.c" break; case 320: /* primary: "'unless'" expr_value then compstmt opt_else "'end'" */ -#line 3351 "mrbgems/mruby-compiler/core/parse.y" +#line 3355 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[-4].nd)), (yyvsp[-1].nd), (yyvsp[-2].nd)); SET_LINENO((yyval.nd), (yyvsp[-5].num)); } -#line 9480 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9496 "mrbgems/mruby-compiler/core/y.tab.c" break; case 321: /* $@16: %empty */ -#line 3355 "mrbgems/mruby-compiler/core/parse.y" +#line 3359 "mrbgems/mruby-compiler/core/parse.y" {COND_PUSH(1);} -#line 9486 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9502 "mrbgems/mruby-compiler/core/y.tab.c" break; case 322: /* $@17: %empty */ -#line 3355 "mrbgems/mruby-compiler/core/parse.y" +#line 3359 "mrbgems/mruby-compiler/core/parse.y" {COND_POP();} -#line 9492 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9508 "mrbgems/mruby-compiler/core/y.tab.c" break; case 323: /* primary: "'while'" $@16 expr_value do $@17 compstmt "'end'" */ -#line 3358 "mrbgems/mruby-compiler/core/parse.y" +#line 3362 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_while(p, cond((yyvsp[-4].nd)), (yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-6].num)); } -#line 9501 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9517 "mrbgems/mruby-compiler/core/y.tab.c" break; case 324: /* $@18: %empty */ -#line 3362 "mrbgems/mruby-compiler/core/parse.y" +#line 3366 "mrbgems/mruby-compiler/core/parse.y" {COND_PUSH(1);} -#line 9507 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9523 "mrbgems/mruby-compiler/core/y.tab.c" break; case 325: /* $@19: %empty */ -#line 3362 "mrbgems/mruby-compiler/core/parse.y" +#line 3366 "mrbgems/mruby-compiler/core/parse.y" {COND_POP();} -#line 9513 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9529 "mrbgems/mruby-compiler/core/y.tab.c" break; case 326: /* primary: "'until'" $@18 expr_value do $@19 compstmt "'end'" */ -#line 3365 "mrbgems/mruby-compiler/core/parse.y" +#line 3369 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_until(p, cond((yyvsp[-4].nd)), (yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-6].num)); } -#line 9522 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9538 "mrbgems/mruby-compiler/core/y.tab.c" break; case 327: /* primary: "'case'" expr_value opt_terms case_body "'end'" */ -#line 3372 "mrbgems/mruby-compiler/core/parse.y" +#line 3376 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_case(p, (yyvsp[-3].nd), (yyvsp[-1].nd)); } -#line 9530 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9546 "mrbgems/mruby-compiler/core/y.tab.c" break; case 328: /* primary: "'case'" opt_terms case_body "'end'" */ -#line 3376 "mrbgems/mruby-compiler/core/parse.y" +#line 3380 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_case(p, 0, (yyvsp[-1].nd)); } -#line 9538 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9554 "mrbgems/mruby-compiler/core/y.tab.c" break; case 329: /* primary: "'case'" expr_value opt_terms "'in'" p_expr then compstmt in_clauses "'end'" */ -#line 3384 "mrbgems/mruby-compiler/core/parse.y" +#line 3388 "mrbgems/mruby-compiler/core/parse.y" { node *in_clause = new_in(p, (yyvsp[-4].nd), NULL, (yyvsp[-2].nd), FALSE); (yyval.nd) = new_case_match(p, (yyvsp[-7].nd), cons(in_clause, (yyvsp[-1].nd))); } -#line 9547 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9563 "mrbgems/mruby-compiler/core/y.tab.c" break; case 330: /* primary: "'case'" expr_value opt_terms "'in'" p_expr "'if' modifier" expr_value then compstmt in_clauses "'end'" */ -#line 3393 "mrbgems/mruby-compiler/core/parse.y" +#line 3397 "mrbgems/mruby-compiler/core/parse.y" { node *in_clause = new_in(p, (yyvsp[-6].nd), (yyvsp[-4].nd), (yyvsp[-2].nd), FALSE); (yyval.nd) = new_case_match(p, (yyvsp[-9].nd), cons(in_clause, (yyvsp[-1].nd))); } -#line 9556 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9572 "mrbgems/mruby-compiler/core/y.tab.c" break; case 331: /* primary: "'case'" expr_value opt_terms "'in'" p_expr "'unless' modifier" expr_value then compstmt in_clauses "'end'" */ -#line 3402 "mrbgems/mruby-compiler/core/parse.y" +#line 3406 "mrbgems/mruby-compiler/core/parse.y" { node *in_clause = new_in(p, (yyvsp[-6].nd), (yyvsp[-4].nd), (yyvsp[-2].nd), TRUE); (yyval.nd) = new_case_match(p, (yyvsp[-9].nd), cons(in_clause, (yyvsp[-1].nd))); } -#line 9565 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9581 "mrbgems/mruby-compiler/core/y.tab.c" break; case 332: /* $@20: %empty */ -#line 3407 "mrbgems/mruby-compiler/core/parse.y" +#line 3411 "mrbgems/mruby-compiler/core/parse.y" {COND_PUSH(1);} -#line 9571 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9587 "mrbgems/mruby-compiler/core/y.tab.c" break; case 333: /* $@21: %empty */ -#line 3409 "mrbgems/mruby-compiler/core/parse.y" +#line 3413 "mrbgems/mruby-compiler/core/parse.y" {COND_POP();} -#line 9577 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9593 "mrbgems/mruby-compiler/core/y.tab.c" break; case 334: /* primary: "'for'" for_var "'in'" $@20 expr_value do $@21 compstmt "'end'" */ -#line 3412 "mrbgems/mruby-compiler/core/parse.y" +#line 3416 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_for(p, (yyvsp[-7].nd), (yyvsp[-4].nd), (yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-8].num)); } -#line 9586 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9602 "mrbgems/mruby-compiler/core/y.tab.c" break; case 335: /* @22: %empty */ -#line 3418 "mrbgems/mruby-compiler/core/parse.y" +#line 3422 "mrbgems/mruby-compiler/core/parse.y" { if (p->in_def || p->in_single) yyerror(&(yylsp[-2]), p, "class definition in method body"); (yyval.nd) = local_switch(p); nvars_block(p); } -#line 9597 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9613 "mrbgems/mruby-compiler/core/y.tab.c" break; case 336: /* primary: "'class'" cpath superclass @22 bodystmt "'end'" */ -#line 3426 "mrbgems/mruby-compiler/core/parse.y" +#line 3430 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_class(p, (yyvsp[-4].nd), (yyvsp[-3].nd), (yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-5].num)); local_resume(p, (yyvsp[-2].nd)); nvars_unnest(p); } -#line 9608 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9624 "mrbgems/mruby-compiler/core/y.tab.c" break; case 337: /* @23: %empty */ -#line 3434 "mrbgems/mruby-compiler/core/parse.y" +#line 3438 "mrbgems/mruby-compiler/core/parse.y" { (yyval.num) = p->in_def; p->in_def = 0; } -#line 9617 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9633 "mrbgems/mruby-compiler/core/y.tab.c" break; case 338: /* @24: %empty */ -#line 3439 "mrbgems/mruby-compiler/core/parse.y" +#line 3443 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = cons(local_switch(p), int_to_node(p->in_single)); nvars_block(p); p->in_single = 0; } -#line 9627 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9643 "mrbgems/mruby-compiler/core/y.tab.c" break; case 339: /* primary: "'class'" "<<" expr @23 term @24 bodystmt "'end'" */ -#line 3446 "mrbgems/mruby-compiler/core/parse.y" +#line 3450 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_sclass(p, (yyvsp[-5].nd), (yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-7].num)); @@ -9636,44 +9652,44 @@ YYLTYPE yylloc = yyloc_default; p->in_def = (yyvsp[-4].num); p->in_single = node_to_int((yyvsp[-2].nd)->cdr); } -#line 9640 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9656 "mrbgems/mruby-compiler/core/y.tab.c" break; case 340: /* @25: %empty */ -#line 3456 "mrbgems/mruby-compiler/core/parse.y" +#line 3460 "mrbgems/mruby-compiler/core/parse.y" { if (p->in_def || p->in_single) yyerror(&(yylsp[-1]), p, "module definition in method body"); (yyval.nd) = local_switch(p); nvars_block(p); } -#line 9651 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9667 "mrbgems/mruby-compiler/core/y.tab.c" break; case 341: /* primary: "'module'" cpath @25 bodystmt "'end'" */ -#line 3464 "mrbgems/mruby-compiler/core/parse.y" +#line 3468 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_module(p, (yyvsp[-3].nd), (yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-4].num)); local_resume(p, (yyvsp[-2].nd)); nvars_unnest(p); } -#line 9662 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9678 "mrbgems/mruby-compiler/core/y.tab.c" break; case 342: /* primary: defn_head f_arglist bodystmt "'end'" */ -#line 3474 "mrbgems/mruby-compiler/core/parse.y" +#line 3478 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); defn_setup(p, (yyval.nd), (yyvsp[-2].nd), (yyvsp[-1].nd)); nvars_unnest(p); p->in_def--; } -#line 9673 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9689 "mrbgems/mruby-compiler/core/y.tab.c" break; case 343: /* primary: defs_head f_arglist bodystmt "'end'" */ -#line 3484 "mrbgems/mruby-compiler/core/parse.y" +#line 3488 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); defn_setup(p, (yyval.nd), (yyvsp[-2].nd), (yyvsp[-1].nd)); @@ -9681,610 +9697,610 @@ YYLTYPE yylloc = yyloc_default; p->in_def--; p->in_single--; } -#line 9685 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9701 "mrbgems/mruby-compiler/core/y.tab.c" break; case 344: /* primary: "'break'" */ -#line 3492 "mrbgems/mruby-compiler/core/parse.y" +#line 3496 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_break(p, 0); } -#line 9693 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9709 "mrbgems/mruby-compiler/core/y.tab.c" break; case 345: /* primary: "'next'" */ -#line 3496 "mrbgems/mruby-compiler/core/parse.y" +#line 3500 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_next(p, 0); } -#line 9701 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9717 "mrbgems/mruby-compiler/core/y.tab.c" break; case 346: /* primary: "'redo'" */ -#line 3500 "mrbgems/mruby-compiler/core/parse.y" +#line 3504 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_redo(p); } -#line 9709 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9725 "mrbgems/mruby-compiler/core/y.tab.c" break; case 347: /* primary: "'retry'" */ -#line 3504 "mrbgems/mruby-compiler/core/parse.y" +#line 3508 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_retry(p); } -#line 9717 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9733 "mrbgems/mruby-compiler/core/y.tab.c" break; case 348: /* primary_value: primary */ -#line 3510 "mrbgems/mruby-compiler/core/parse.y" +#line 3514 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); if (!(yyval.nd)) (yyval.nd) = new_nil(p); } -#line 9726 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9742 "mrbgems/mruby-compiler/core/y.tab.c" break; case 355: /* if_tail: "'elsif'" expr_value then compstmt if_tail */ -#line 3529 "mrbgems/mruby-compiler/core/parse.y" +#line 3533 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[-3].nd)), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9734 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9750 "mrbgems/mruby-compiler/core/y.tab.c" break; case 357: /* opt_else: "'else'" compstmt */ -#line 3536 "mrbgems/mruby-compiler/core/parse.y" +#line 3540 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 9742 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9758 "mrbgems/mruby-compiler/core/y.tab.c" break; case 358: /* for_var: lhs */ -#line 3542 "mrbgems/mruby-compiler/core/parse.y" +#line 3546 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(list1((yyvsp[0].nd))); } -#line 9750 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9766 "mrbgems/mruby-compiler/core/y.tab.c" break; case 360: /* f_margs: f_arg */ -#line 3549 "mrbgems/mruby-compiler/core/parse.y" +#line 3553 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3((yyvsp[0].nd),0,0); } -#line 9758 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9774 "mrbgems/mruby-compiler/core/y.tab.c" break; case 361: /* f_margs: f_arg ',' "*" f_norm_arg */ -#line 3553 "mrbgems/mruby-compiler/core/parse.y" +#line 3557 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3((yyvsp[-3].nd), new_lvar(p, (yyvsp[0].id)), 0); } -#line 9766 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9782 "mrbgems/mruby-compiler/core/y.tab.c" break; case 362: /* f_margs: f_arg ',' "*" f_norm_arg ',' f_arg */ -#line 3557 "mrbgems/mruby-compiler/core/parse.y" +#line 3561 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3((yyvsp[-5].nd), new_lvar(p, (yyvsp[-2].id)), (yyvsp[0].nd)); } -#line 9774 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9790 "mrbgems/mruby-compiler/core/y.tab.c" break; case 363: /* f_margs: f_arg ',' "*" */ -#line 3561 "mrbgems/mruby-compiler/core/parse.y" +#line 3565 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, intern_op(mul)); (yyval.nd) = list3((yyvsp[-2].nd), int_to_node(-1), 0); } -#line 9783 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9799 "mrbgems/mruby-compiler/core/y.tab.c" break; case 364: /* f_margs: f_arg ',' "*" ',' f_arg */ -#line 3566 "mrbgems/mruby-compiler/core/parse.y" +#line 3570 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3((yyvsp[-4].nd), int_to_node(-1), (yyvsp[0].nd)); } -#line 9791 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9807 "mrbgems/mruby-compiler/core/y.tab.c" break; case 365: /* f_margs: "*" f_norm_arg */ -#line 3570 "mrbgems/mruby-compiler/core/parse.y" +#line 3574 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3(0, new_lvar(p, (yyvsp[0].id)), 0); } -#line 9799 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9815 "mrbgems/mruby-compiler/core/y.tab.c" break; case 366: /* f_margs: "*" f_norm_arg ',' f_arg */ -#line 3574 "mrbgems/mruby-compiler/core/parse.y" +#line 3578 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3(0, new_lvar(p, (yyvsp[-2].id)), (yyvsp[0].nd)); } -#line 9807 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9823 "mrbgems/mruby-compiler/core/y.tab.c" break; case 367: /* f_margs: "*" */ -#line 3578 "mrbgems/mruby-compiler/core/parse.y" +#line 3582 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, intern_op(mul)); (yyval.nd) = list3(0, int_to_node(-1), 0); } -#line 9816 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9832 "mrbgems/mruby-compiler/core/y.tab.c" break; case 368: /* $@26: %empty */ -#line 3583 "mrbgems/mruby-compiler/core/parse.y" +#line 3587 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, intern_op(mul)); } -#line 9824 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9840 "mrbgems/mruby-compiler/core/y.tab.c" break; case 369: /* f_margs: "*" ',' $@26 f_arg */ -#line 3587 "mrbgems/mruby-compiler/core/parse.y" +#line 3591 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3(0, int_to_node(-1), (yyvsp[0].nd)); } -#line 9832 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9848 "mrbgems/mruby-compiler/core/y.tab.c" break; case 370: /* block_args_tail: f_block_kwarg ',' f_kwrest opt_f_block_arg */ -#line 3593 "mrbgems/mruby-compiler/core/parse.y" +#line 3597 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].id)); } -#line 9840 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9856 "mrbgems/mruby-compiler/core/y.tab.c" break; case 371: /* block_args_tail: f_block_kwarg opt_f_block_arg */ -#line 3597 "mrbgems/mruby-compiler/core/parse.y" +#line 3601 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, (yyvsp[-1].nd), 0, (yyvsp[0].id)); } -#line 9848 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9864 "mrbgems/mruby-compiler/core/y.tab.c" break; case 372: /* block_args_tail: f_kwrest opt_f_block_arg */ -#line 3601 "mrbgems/mruby-compiler/core/parse.y" +#line 3605 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, 0, (yyvsp[-1].id), (yyvsp[0].id)); } -#line 9856 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9872 "mrbgems/mruby-compiler/core/y.tab.c" break; case 373: /* block_args_tail: f_block_arg */ -#line 3605 "mrbgems/mruby-compiler/core/parse.y" +#line 3609 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, 0, 0, (yyvsp[0].id)); } -#line 9864 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9880 "mrbgems/mruby-compiler/core/y.tab.c" break; case 374: /* opt_block_args_tail: ',' block_args_tail */ -#line 3611 "mrbgems/mruby-compiler/core/parse.y" +#line 3615 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 9872 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9888 "mrbgems/mruby-compiler/core/y.tab.c" break; case 375: /* opt_block_args_tail: %empty */ -#line 3615 "mrbgems/mruby-compiler/core/parse.y" +#line 3619 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, 0, 0, 0); } -#line 9880 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9896 "mrbgems/mruby-compiler/core/y.tab.c" break; case 376: /* block_param: f_arg ',' f_block_optarg ',' f_rest_arg opt_block_args_tail */ -#line 3621 "mrbgems/mruby-compiler/core/parse.y" +#line 3625 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-5].nd), (yyvsp[-3].nd), (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 9888 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9904 "mrbgems/mruby-compiler/core/y.tab.c" break; case 377: /* block_param: f_arg ',' f_block_optarg ',' f_rest_arg ',' f_arg opt_block_args_tail */ -#line 3625 "mrbgems/mruby-compiler/core/parse.y" +#line 3629 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-7].nd), (yyvsp[-5].nd), (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9896 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9912 "mrbgems/mruby-compiler/core/y.tab.c" break; case 378: /* block_param: f_arg ',' f_block_optarg opt_block_args_tail */ -#line 3629 "mrbgems/mruby-compiler/core/parse.y" +#line 3633 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-3].nd), (yyvsp[-1].nd), 0, 0, (yyvsp[0].nd)); } -#line 9904 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9920 "mrbgems/mruby-compiler/core/y.tab.c" break; case 379: /* block_param: f_arg ',' f_block_optarg ',' f_arg opt_block_args_tail */ -#line 3633 "mrbgems/mruby-compiler/core/parse.y" +#line 3637 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-5].nd), (yyvsp[-3].nd), 0, (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9912 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9928 "mrbgems/mruby-compiler/core/y.tab.c" break; case 380: /* block_param: f_arg ',' f_rest_arg opt_block_args_tail */ -#line 3637 "mrbgems/mruby-compiler/core/parse.y" +#line 3641 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-3].nd), 0, (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 9920 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9936 "mrbgems/mruby-compiler/core/y.tab.c" break; case 381: /* block_param: f_arg ',' opt_block_args_tail */ -#line 3641 "mrbgems/mruby-compiler/core/parse.y" +#line 3645 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-2].nd), 0, 0, 0, (yyvsp[0].nd)); } -#line 9928 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9944 "mrbgems/mruby-compiler/core/y.tab.c" break; case 382: /* block_param: f_arg ',' f_rest_arg ',' f_arg opt_block_args_tail */ -#line 3645 "mrbgems/mruby-compiler/core/parse.y" +#line 3649 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-5].nd), 0, (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9936 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9952 "mrbgems/mruby-compiler/core/y.tab.c" break; case 383: /* block_param: f_arg opt_block_args_tail */ -#line 3649 "mrbgems/mruby-compiler/core/parse.y" +#line 3653 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-1].nd), 0, 0, 0, (yyvsp[0].nd)); } -#line 9944 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9960 "mrbgems/mruby-compiler/core/y.tab.c" break; case 384: /* block_param: f_block_optarg ',' f_rest_arg opt_block_args_tail */ -#line 3653 "mrbgems/mruby-compiler/core/parse.y" +#line 3657 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-3].nd), (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 9952 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9968 "mrbgems/mruby-compiler/core/y.tab.c" break; case 385: /* block_param: f_block_optarg ',' f_rest_arg ',' f_arg opt_block_args_tail */ -#line 3657 "mrbgems/mruby-compiler/core/parse.y" +#line 3661 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-5].nd), (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9960 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9976 "mrbgems/mruby-compiler/core/y.tab.c" break; case 386: /* block_param: f_block_optarg opt_block_args_tail */ -#line 3661 "mrbgems/mruby-compiler/core/parse.y" +#line 3665 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-1].nd), 0, 0, (yyvsp[0].nd)); } -#line 9968 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9984 "mrbgems/mruby-compiler/core/y.tab.c" break; case 387: /* block_param: f_block_optarg ',' f_arg opt_block_args_tail */ -#line 3665 "mrbgems/mruby-compiler/core/parse.y" +#line 3669 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-3].nd), 0, (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9976 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9992 "mrbgems/mruby-compiler/core/y.tab.c" break; case 388: /* block_param: f_rest_arg opt_block_args_tail */ -#line 3669 "mrbgems/mruby-compiler/core/parse.y" +#line 3673 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, 0, (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 9984 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10000 "mrbgems/mruby-compiler/core/y.tab.c" break; case 389: /* block_param: f_rest_arg ',' f_arg opt_block_args_tail */ -#line 3673 "mrbgems/mruby-compiler/core/parse.y" +#line 3677 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, 0, (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9992 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10008 "mrbgems/mruby-compiler/core/y.tab.c" break; case 390: /* block_param: block_args_tail */ -#line 3677 "mrbgems/mruby-compiler/core/parse.y" +#line 3681 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, 0, 0, 0, (yyvsp[0].nd)); } -#line 10000 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10016 "mrbgems/mruby-compiler/core/y.tab.c" break; case 391: /* opt_block_param: none */ -#line 3683 "mrbgems/mruby-compiler/core/parse.y" +#line 3687 "mrbgems/mruby-compiler/core/parse.y" { local_add_blk(p); (yyval.nd) = 0; } -#line 10009 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10025 "mrbgems/mruby-compiler/core/y.tab.c" break; case 392: /* opt_block_param: block_param_def */ -#line 3688 "mrbgems/mruby-compiler/core/parse.y" +#line 3692 "mrbgems/mruby-compiler/core/parse.y" { p->cmd_start = TRUE; (yyval.nd) = (yyvsp[0].nd); } -#line 10018 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10034 "mrbgems/mruby-compiler/core/y.tab.c" break; case 393: /* $@27: %empty */ -#line 3694 "mrbgems/mruby-compiler/core/parse.y" +#line 3698 "mrbgems/mruby-compiler/core/parse.y" {local_add_blk(p);} -#line 10024 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10040 "mrbgems/mruby-compiler/core/y.tab.c" break; case 394: /* block_param_def: '|' $@27 opt_bv_decl '|' */ -#line 3695 "mrbgems/mruby-compiler/core/parse.y" +#line 3699 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 10032 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10048 "mrbgems/mruby-compiler/core/y.tab.c" break; case 395: /* block_param_def: "||" */ -#line 3699 "mrbgems/mruby-compiler/core/parse.y" +#line 3703 "mrbgems/mruby-compiler/core/parse.y" { local_add_blk(p); (yyval.nd) = 0; } -#line 10041 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10057 "mrbgems/mruby-compiler/core/y.tab.c" break; case 396: /* block_param_def: '|' block_param opt_bv_decl '|' */ -#line 3704 "mrbgems/mruby-compiler/core/parse.y" +#line 3708 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-2].nd); } -#line 10049 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10065 "mrbgems/mruby-compiler/core/y.tab.c" break; case 397: /* opt_bv_decl: opt_nl */ -#line 3710 "mrbgems/mruby-compiler/core/parse.y" +#line 3714 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 10057 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10073 "mrbgems/mruby-compiler/core/y.tab.c" break; case 398: /* opt_bv_decl: opt_nl ';' bv_decls opt_nl */ -#line 3714 "mrbgems/mruby-compiler/core/parse.y" +#line 3718 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 10065 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10081 "mrbgems/mruby-compiler/core/y.tab.c" break; case 401: /* bvar: "local variable or method" */ -#line 3724 "mrbgems/mruby-compiler/core/parse.y" +#line 3728 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, (yyvsp[0].id)); new_bv(p, (yyvsp[0].id)); } -#line 10074 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10090 "mrbgems/mruby-compiler/core/y.tab.c" break; case 403: /* f_larglist: '(' f_args opt_bv_decl ')' */ -#line 3732 "mrbgems/mruby-compiler/core/parse.y" +#line 3736 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-2].nd); } -#line 10082 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10098 "mrbgems/mruby-compiler/core/y.tab.c" break; case 404: /* f_larglist: f_args */ -#line 3736 "mrbgems/mruby-compiler/core/parse.y" +#line 3740 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 10090 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10106 "mrbgems/mruby-compiler/core/y.tab.c" break; case 405: /* lambda_body: tLAMBEG compstmt '}' */ -#line 3742 "mrbgems/mruby-compiler/core/parse.y" +#line 3746 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 10098 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10114 "mrbgems/mruby-compiler/core/y.tab.c" break; case 406: /* lambda_body: "'do' for lambda" bodystmt "'end'" */ -#line 3746 "mrbgems/mruby-compiler/core/parse.y" +#line 3750 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 10106 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10122 "mrbgems/mruby-compiler/core/y.tab.c" break; case 407: /* @28: %empty */ -#line 3752 "mrbgems/mruby-compiler/core/parse.y" +#line 3756 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); nvars_nest(p); (yyval.num) = p->lineno; } -#line 10116 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10132 "mrbgems/mruby-compiler/core/y.tab.c" break; case 408: /* do_block: "'do' for block" @28 opt_block_param bodystmt "'end'" */ -#line 3760 "mrbgems/mruby-compiler/core/parse.y" +#line 3764 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_block(p,(yyvsp[-2].nd),(yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-3].num)); local_unnest(p); nvars_unnest(p); } -#line 10127 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10143 "mrbgems/mruby-compiler/core/y.tab.c" break; case 409: /* block_call: command do_block */ -#line 3769 "mrbgems/mruby-compiler/core/parse.y" +#line 3773 "mrbgems/mruby-compiler/core/parse.y" { call_with_block(p, (yyvsp[-1].nd), (yyvsp[0].nd)); (yyval.nd) = (yyvsp[-1].nd); } -#line 10136 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10152 "mrbgems/mruby-compiler/core/y.tab.c" break; case 410: /* block_call: block_call call_op2 operation2 opt_paren_args */ -#line 3774 "mrbgems/mruby-compiler/core/parse.y" +#line 3778 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), (yyvsp[-2].num)); } -#line 10144 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10160 "mrbgems/mruby-compiler/core/y.tab.c" break; case 411: /* block_call: block_call call_op2 operation2 opt_paren_args brace_block */ -#line 3778 "mrbgems/mruby-compiler/core/parse.y" +#line 3782 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-4].nd), (yyvsp[-2].id), (yyvsp[-1].nd), (yyvsp[-3].num)); call_with_block(p, (yyval.nd), (yyvsp[0].nd)); } -#line 10153 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10169 "mrbgems/mruby-compiler/core/y.tab.c" break; case 412: /* block_call: block_call call_op2 operation2 command_args do_block */ -#line 3783 "mrbgems/mruby-compiler/core/parse.y" +#line 3787 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-4].nd), (yyvsp[-2].id), (yyvsp[-1].nd), (yyvsp[-3].num)); call_with_block(p, (yyval.nd), (yyvsp[0].nd)); } -#line 10162 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10178 "mrbgems/mruby-compiler/core/y.tab.c" break; case 413: /* method_call: operation paren_args */ -#line 3790 "mrbgems/mruby-compiler/core/parse.y" +#line 3794 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_fcall(p, (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 10170 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10186 "mrbgems/mruby-compiler/core/y.tab.c" break; case 414: /* method_call: primary_value call_op operation2 opt_paren_args */ -#line 3794 "mrbgems/mruby-compiler/core/parse.y" +#line 3798 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), (yyvsp[-2].num)); } -#line 10178 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10194 "mrbgems/mruby-compiler/core/y.tab.c" break; case 415: /* method_call: primary_value "::" operation2 paren_args */ -#line 3798 "mrbgems/mruby-compiler/core/parse.y" +#line 3802 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), tCOLON2); } -#line 10186 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10202 "mrbgems/mruby-compiler/core/y.tab.c" break; case 416: /* method_call: primary_value "::" operation3 */ -#line 3802 "mrbgems/mruby-compiler/core/parse.y" +#line 3806 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, tCOLON2); } -#line 10194 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10210 "mrbgems/mruby-compiler/core/y.tab.c" break; case 417: /* method_call: primary_value call_op paren_args */ -#line 3806 "mrbgems/mruby-compiler/core/parse.y" +#line 3810 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), MRB_SYM(call), (yyvsp[0].nd), (yyvsp[-1].num)); } -#line 10202 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10218 "mrbgems/mruby-compiler/core/y.tab.c" break; case 418: /* method_call: primary_value "::" paren_args */ -#line 3810 "mrbgems/mruby-compiler/core/parse.y" +#line 3814 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), MRB_SYM(call), (yyvsp[0].nd), tCOLON2); } -#line 10210 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10226 "mrbgems/mruby-compiler/core/y.tab.c" break; case 419: /* method_call: "'super'" paren_args */ -#line 3814 "mrbgems/mruby-compiler/core/parse.y" +#line 3818 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_super(p, (yyvsp[0].nd)); } -#line 10218 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10234 "mrbgems/mruby-compiler/core/y.tab.c" break; case 420: /* method_call: "'super'" */ -#line 3818 "mrbgems/mruby-compiler/core/parse.y" +#line 3822 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_zsuper(p); } -#line 10226 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10242 "mrbgems/mruby-compiler/core/y.tab.c" break; case 421: /* method_call: primary_value '[' opt_call_args ']' */ -#line 3822 "mrbgems/mruby-compiler/core/parse.y" +#line 3826 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), intern_op(aref), (yyvsp[-1].nd), '.'); } -#line 10234 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10250 "mrbgems/mruby-compiler/core/y.tab.c" break; case 422: /* @29: %empty */ -#line 3828 "mrbgems/mruby-compiler/core/parse.y" +#line 3832 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); nvars_nest(p); (yyval.num) = p->lineno; } -#line 10244 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10260 "mrbgems/mruby-compiler/core/y.tab.c" break; case 423: /* brace_block: '{' @29 opt_block_param compstmt '}' */ -#line 3835 "mrbgems/mruby-compiler/core/parse.y" +#line 3839 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_block(p,(yyvsp[-2].nd),(yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-3].num)); local_unnest(p); nvars_unnest(p); } -#line 10255 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10271 "mrbgems/mruby-compiler/core/y.tab.c" break; case 424: /* @30: %empty */ -#line 3842 "mrbgems/mruby-compiler/core/parse.y" +#line 3846 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); nvars_nest(p); (yyval.num) = p->lineno; } -#line 10265 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10281 "mrbgems/mruby-compiler/core/y.tab.c" break; case 425: /* brace_block: "'do'" @30 opt_block_param bodystmt "'end'" */ -#line 3849 "mrbgems/mruby-compiler/core/parse.y" +#line 3853 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_block(p,(yyvsp[-2].nd),(yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-3].num)); local_unnest(p); nvars_unnest(p); } -#line 10276 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10292 "mrbgems/mruby-compiler/core/y.tab.c" break; case 426: /* case_body: "'when'" args then compstmt cases */ -#line 3860 "mrbgems/mruby-compiler/core/parse.y" +#line 3864 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = cons(cons((yyvsp[-3].nd), (yyvsp[-1].nd)), (yyvsp[0].nd)); } -#line 10284 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10300 "mrbgems/mruby-compiler/core/y.tab.c" break; case 427: /* cases: opt_else */ -#line 3866 "mrbgems/mruby-compiler/core/parse.y" +#line 3870 "mrbgems/mruby-compiler/core/parse.y" { if ((yyvsp[0].nd)) { (yyval.nd) = cons(cons(0, (yyvsp[0].nd)), 0); @@ -10293,622 +10309,622 @@ YYLTYPE yylloc = yyloc_default; (yyval.nd) = 0; } } -#line 10297 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10313 "mrbgems/mruby-compiler/core/y.tab.c" break; case 429: /* in_clauses: opt_else */ -#line 3880 "mrbgems/mruby-compiler/core/parse.y" +#line 3884 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd) ? list1(new_in(p, NULL, NULL, (yyvsp[0].nd), FALSE)) : 0; } -#line 10305 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10321 "mrbgems/mruby-compiler/core/y.tab.c" break; case 430: /* $@31: %empty */ -#line 3883 "mrbgems/mruby-compiler/core/parse.y" +#line 3887 "mrbgems/mruby-compiler/core/parse.y" {p->in_kwarg--;} -#line 10311 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10327 "mrbgems/mruby-compiler/core/y.tab.c" break; case 431: /* in_clauses: "'in'" p_expr $@31 then compstmt in_clauses */ -#line 3884 "mrbgems/mruby-compiler/core/parse.y" +#line 3888 "mrbgems/mruby-compiler/core/parse.y" { node *in_clause = new_in(p, (yyvsp[-4].nd), NULL, (yyvsp[-1].nd), FALSE); (yyval.nd) = cons(in_clause, (yyvsp[0].nd)); } -#line 10320 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10336 "mrbgems/mruby-compiler/core/y.tab.c" break; case 432: /* $@32: %empty */ -#line 3888 "mrbgems/mruby-compiler/core/parse.y" +#line 3892 "mrbgems/mruby-compiler/core/parse.y" {p->in_kwarg--;} -#line 10326 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10342 "mrbgems/mruby-compiler/core/y.tab.c" break; case 433: /* in_clauses: "'in'" p_expr $@32 "'if' modifier" expr_value then compstmt in_clauses */ -#line 3889 "mrbgems/mruby-compiler/core/parse.y" +#line 3893 "mrbgems/mruby-compiler/core/parse.y" { node *in_clause = new_in(p, (yyvsp[-6].nd), (yyvsp[-3].nd), (yyvsp[-1].nd), FALSE); (yyval.nd) = cons(in_clause, (yyvsp[0].nd)); } -#line 10335 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10351 "mrbgems/mruby-compiler/core/y.tab.c" break; case 434: /* $@33: %empty */ -#line 3893 "mrbgems/mruby-compiler/core/parse.y" +#line 3897 "mrbgems/mruby-compiler/core/parse.y" {p->in_kwarg--;} -#line 10341 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10357 "mrbgems/mruby-compiler/core/y.tab.c" break; case 435: /* in_clauses: "'in'" p_expr $@33 "'unless' modifier" expr_value then compstmt in_clauses */ -#line 3894 "mrbgems/mruby-compiler/core/parse.y" +#line 3898 "mrbgems/mruby-compiler/core/parse.y" { node *in_clause = new_in(p, (yyvsp[-6].nd), (yyvsp[-3].nd), (yyvsp[-1].nd), TRUE); (yyval.nd) = cons(in_clause, (yyvsp[0].nd)); } -#line 10350 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10366 "mrbgems/mruby-compiler/core/y.tab.c" break; case 437: /* p_expr: p_args_head p_as */ -#line 3905 "mrbgems/mruby-compiler/core/parse.y" +#line 3909 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_array(p, push((yyvsp[-1].nd), (yyvsp[0].nd)), 0, 0); } -#line 10358 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10374 "mrbgems/mruby-compiler/core/y.tab.c" break; case 438: /* p_expr: p_args_head p_rest */ -#line 3909 "mrbgems/mruby-compiler/core/parse.y" +#line 3913 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_array(p, (yyvsp[-1].nd), (yyvsp[0].nd), 0); } -#line 10366 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10382 "mrbgems/mruby-compiler/core/y.tab.c" break; case 439: /* p_expr: p_args_head p_rest ',' p_args_post */ -#line 3913 "mrbgems/mruby-compiler/core/parse.y" +#line 3917 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_array(p, (yyvsp[-3].nd), (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10374 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10390 "mrbgems/mruby-compiler/core/y.tab.c" break; case 440: /* p_expr: p_rest */ -#line 3917 "mrbgems/mruby-compiler/core/parse.y" +#line 3921 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_array(p, 0, (yyvsp[0].nd), 0); } -#line 10382 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10398 "mrbgems/mruby-compiler/core/y.tab.c" break; case 441: /* p_expr: p_rest ',' p_args_post */ -#line 3921 "mrbgems/mruby-compiler/core/parse.y" +#line 3925 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_array(p, 0, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10390 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10406 "mrbgems/mruby-compiler/core/y.tab.c" break; case 442: /* p_expr: p_hash_elems */ -#line 3925 "mrbgems/mruby-compiler/core/parse.y" +#line 3929 "mrbgems/mruby-compiler/core/parse.y" { /* Brace-less hash pattern: in a:, b: x */ (yyval.nd) = new_pat_hash(p, (yyvsp[0].nd), 0); } -#line 10399 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10415 "mrbgems/mruby-compiler/core/y.tab.c" break; case 443: /* p_expr: p_hash_elems ',' p_kwrest */ -#line 3930 "mrbgems/mruby-compiler/core/parse.y" +#line 3934 "mrbgems/mruby-compiler/core/parse.y" { /* Brace-less hash pattern with kwrest: in a:, **rest */ (yyval.nd) = new_pat_hash(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10408 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10424 "mrbgems/mruby-compiler/core/y.tab.c" break; case 444: /* p_expr: p_kwrest */ -#line 3935 "mrbgems/mruby-compiler/core/parse.y" +#line 3939 "mrbgems/mruby-compiler/core/parse.y" { /* Brace-less kwrest only: in **rest */ (yyval.nd) = new_pat_hash(p, 0, (yyvsp[0].nd)); } -#line 10417 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10433 "mrbgems/mruby-compiler/core/y.tab.c" break; case 445: /* p_args_head: p_as ',' */ -#line 3943 "mrbgems/mruby-compiler/core/parse.y" +#line 3947 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[-1].nd)); } -#line 10425 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10441 "mrbgems/mruby-compiler/core/y.tab.c" break; case 446: /* p_args_head: p_args_head p_as ',' */ -#line 3947 "mrbgems/mruby-compiler/core/parse.y" +#line 3951 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[-1].nd)); } -#line 10433 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10449 "mrbgems/mruby-compiler/core/y.tab.c" break; case 447: /* p_args_post: p_as */ -#line 3954 "mrbgems/mruby-compiler/core/parse.y" +#line 3958 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 10441 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10457 "mrbgems/mruby-compiler/core/y.tab.c" break; case 448: /* p_args_post: p_args_post ',' p_as */ -#line 3958 "mrbgems/mruby-compiler/core/parse.y" +#line 3962 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10449 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10465 "mrbgems/mruby-compiler/core/y.tab.c" break; case 450: /* p_as: p_alt "=>" "local variable or method" */ -#line 3965 "mrbgems/mruby-compiler/core/parse.y" +#line 3969 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_as(p, (yyvsp[-2].nd), (yyvsp[0].id)); } -#line 10457 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10473 "mrbgems/mruby-compiler/core/y.tab.c" break; case 452: /* p_alt: p_alt '|' p_value */ -#line 3972 "mrbgems/mruby-compiler/core/parse.y" +#line 3976 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_alt(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10465 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10481 "mrbgems/mruby-compiler/core/y.tab.c" break; case 454: /* p_value: numeric */ -#line 3979 "mrbgems/mruby-compiler/core/parse.y" +#line 3983 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_value(p, (yyvsp[0].nd)); } -#line 10473 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10489 "mrbgems/mruby-compiler/core/y.tab.c" break; case 455: /* p_value: symbol */ -#line 3983 "mrbgems/mruby-compiler/core/parse.y" +#line 3987 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_value(p, (yyvsp[0].nd)); } -#line 10481 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10497 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 456: /* p_value: tSTRING */ -#line 3987 "mrbgems/mruby-compiler/core/parse.y" + case 456: /* p_value: string */ +#line 3991 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_pat_value(p, new_str(p, list1((yyvsp[0].nd)))); + (yyval.nd) = new_pat_value(p, new_str(p, (yyvsp[0].nd))); } -#line 10489 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10505 "mrbgems/mruby-compiler/core/y.tab.c" break; case 457: /* p_value: "'nil'" */ -#line 3991 "mrbgems/mruby-compiler/core/parse.y" +#line 3995 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_value(p, new_nil(p)); } -#line 10497 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10513 "mrbgems/mruby-compiler/core/y.tab.c" break; case 458: /* p_value: "'true'" */ -#line 3995 "mrbgems/mruby-compiler/core/parse.y" +#line 3999 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_value(p, new_true(p)); } -#line 10505 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10521 "mrbgems/mruby-compiler/core/y.tab.c" break; case 459: /* p_value: "'false'" */ -#line 3999 "mrbgems/mruby-compiler/core/parse.y" +#line 4003 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_value(p, new_false(p)); } -#line 10513 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10529 "mrbgems/mruby-compiler/core/y.tab.c" break; case 460: /* p_value: p_const */ -#line 4003 "mrbgems/mruby-compiler/core/parse.y" +#line 4007 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_value(p, (yyvsp[0].nd)); } -#line 10521 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10537 "mrbgems/mruby-compiler/core/y.tab.c" break; case 463: /* p_value: '^' "local variable or method" */ -#line 4009 "mrbgems/mruby-compiler/core/parse.y" +#line 4013 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_pin(p, (yyvsp[0].id)); } -#line 10529 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10545 "mrbgems/mruby-compiler/core/y.tab.c" break; case 464: /* p_array: "[" p_array_body ']' */ -#line 4016 "mrbgems/mruby-compiler/core/parse.y" +#line 4020 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 10537 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10553 "mrbgems/mruby-compiler/core/y.tab.c" break; case 465: /* p_array: "[" ']' */ -#line 4020 "mrbgems/mruby-compiler/core/parse.y" +#line 4024 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_array(p, 0, 0, 0); } -#line 10545 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10561 "mrbgems/mruby-compiler/core/y.tab.c" break; case 466: /* p_array_body: p_array_elems */ -#line 4027 "mrbgems/mruby-compiler/core/parse.y" +#line 4031 "mrbgems/mruby-compiler/core/parse.y" { /* Just pre elements, no rest */ (yyval.nd) = new_pat_array(p, (yyvsp[0].nd), 0, 0); } -#line 10554 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10570 "mrbgems/mruby-compiler/core/y.tab.c" break; case 467: /* p_array_body: p_array_elems ',' p_rest */ -#line 4032 "mrbgems/mruby-compiler/core/parse.y" +#line 4036 "mrbgems/mruby-compiler/core/parse.y" { /* Pre elements + rest, no post */ (yyval.nd) = new_pat_array(p, (yyvsp[-2].nd), (yyvsp[0].nd), 0); } -#line 10563 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10579 "mrbgems/mruby-compiler/core/y.tab.c" break; case 468: /* p_array_body: p_array_elems ',' p_rest ',' p_array_elems */ -#line 4037 "mrbgems/mruby-compiler/core/parse.y" +#line 4041 "mrbgems/mruby-compiler/core/parse.y" { /* Pre + rest + post */ (yyval.nd) = new_pat_array(p, (yyvsp[-4].nd), (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10572 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10588 "mrbgems/mruby-compiler/core/y.tab.c" break; case 469: /* p_array_body: p_rest */ -#line 4042 "mrbgems/mruby-compiler/core/parse.y" +#line 4046 "mrbgems/mruby-compiler/core/parse.y" { /* Just rest, no pre or post */ (yyval.nd) = new_pat_array(p, 0, (yyvsp[0].nd), 0); } -#line 10581 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10597 "mrbgems/mruby-compiler/core/y.tab.c" break; case 470: /* p_array_body: p_rest ',' p_array_elems */ -#line 4047 "mrbgems/mruby-compiler/core/parse.y" +#line 4051 "mrbgems/mruby-compiler/core/parse.y" { /* Rest + post, no pre */ (yyval.nd) = new_pat_array(p, 0, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10590 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10606 "mrbgems/mruby-compiler/core/y.tab.c" break; case 471: /* p_array_body: p_rest ',' p_array_elems ',' p_rest */ -#line 4052 "mrbgems/mruby-compiler/core/parse.y" +#line 4056 "mrbgems/mruby-compiler/core/parse.y" { /* Find pattern: [*pre, elems, *post] */ (yyval.nd) = new_pat_find(p, (yyvsp[-4].nd), (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10599 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10615 "mrbgems/mruby-compiler/core/y.tab.c" break; case 472: /* p_array_elems: p_as */ -#line 4060 "mrbgems/mruby-compiler/core/parse.y" +#line 4064 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 10607 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10623 "mrbgems/mruby-compiler/core/y.tab.c" break; case 473: /* p_array_elems: p_array_elems ',' p_as */ -#line 4064 "mrbgems/mruby-compiler/core/parse.y" +#line 4068 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10615 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10631 "mrbgems/mruby-compiler/core/y.tab.c" break; case 474: /* p_rest: "*" "local variable or method" */ -#line 4071 "mrbgems/mruby-compiler/core/parse.y" +#line 4075 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_var(p, (yyvsp[0].id)); } -#line 10623 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10639 "mrbgems/mruby-compiler/core/y.tab.c" break; case 475: /* p_rest: "*" */ -#line 4075 "mrbgems/mruby-compiler/core/parse.y" +#line 4079 "mrbgems/mruby-compiler/core/parse.y" { /* Anonymous rest pattern */ (yyval.nd) = (node*)-1; } -#line 10632 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10648 "mrbgems/mruby-compiler/core/y.tab.c" break; case 476: /* p_const: "constant" */ -#line 4083 "mrbgems/mruby-compiler/core/parse.y" +#line 4087 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_const(p, (yyvsp[0].id)); } -#line 10640 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10656 "mrbgems/mruby-compiler/core/y.tab.c" break; case 477: /* p_const: p_const "::" "constant" */ -#line 4087 "mrbgems/mruby-compiler/core/parse.y" +#line 4091 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_colon2(p, (yyvsp[-2].nd), (yyvsp[0].id)); } -#line 10648 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10664 "mrbgems/mruby-compiler/core/y.tab.c" break; case 478: /* p_const: tCOLON3 "constant" */ -#line 4091 "mrbgems/mruby-compiler/core/parse.y" +#line 4095 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_colon3(p, (yyvsp[0].id)); } -#line 10656 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10672 "mrbgems/mruby-compiler/core/y.tab.c" break; case 479: /* p_hash: tLBRACE p_hash_body '}' */ -#line 4098 "mrbgems/mruby-compiler/core/parse.y" +#line 4102 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 10664 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10680 "mrbgems/mruby-compiler/core/y.tab.c" break; case 480: /* p_hash: tLBRACE '}' */ -#line 4102 "mrbgems/mruby-compiler/core/parse.y" +#line 4106 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_hash(p, 0, 0); } -#line 10672 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10688 "mrbgems/mruby-compiler/core/y.tab.c" break; case 481: /* p_hash_body: p_hash_elems */ -#line 4109 "mrbgems/mruby-compiler/core/parse.y" +#line 4113 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_hash(p, (yyvsp[0].nd), 0); } -#line 10680 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10696 "mrbgems/mruby-compiler/core/y.tab.c" break; case 482: /* p_hash_body: p_hash_elems ',' p_kwrest */ -#line 4113 "mrbgems/mruby-compiler/core/parse.y" +#line 4117 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_hash(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10688 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10704 "mrbgems/mruby-compiler/core/y.tab.c" break; case 483: /* p_hash_body: p_kwrest */ -#line 4117 "mrbgems/mruby-compiler/core/parse.y" +#line 4121 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_hash(p, 0, (yyvsp[0].nd)); } -#line 10696 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10712 "mrbgems/mruby-compiler/core/y.tab.c" break; case 484: /* p_hash_elems: p_hash_elem */ -#line 4124 "mrbgems/mruby-compiler/core/parse.y" +#line 4128 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 10704 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10720 "mrbgems/mruby-compiler/core/y.tab.c" break; case 485: /* p_hash_elems: p_hash_elems ',' p_hash_elem */ -#line 4128 "mrbgems/mruby-compiler/core/parse.y" +#line 4132 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10712 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10728 "mrbgems/mruby-compiler/core/y.tab.c" break; case 486: /* p_hash_elem: "local variable or method" "label" p_as */ -#line 4137 "mrbgems/mruby-compiler/core/parse.y" +#line 4141 "mrbgems/mruby-compiler/core/parse.y" { /* {key: pattern} */ (yyval.nd) = cons(new_sym(p, (yyvsp[-2].id)), (yyvsp[0].nd)); } -#line 10721 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10737 "mrbgems/mruby-compiler/core/y.tab.c" break; case 487: /* p_hash_elem: "local variable or method" "label" */ -#line 4142 "mrbgems/mruby-compiler/core/parse.y" +#line 4146 "mrbgems/mruby-compiler/core/parse.y" { /* {key:} shorthand - binds to variable with same name */ (yyval.nd) = cons(new_sym(p, (yyvsp[-1].id)), new_pat_var(p, (yyvsp[-1].id))); } -#line 10730 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10746 "mrbgems/mruby-compiler/core/y.tab.c" break; case 488: /* p_kwrest: "**" "local variable or method" */ -#line 4150 "mrbgems/mruby-compiler/core/parse.y" +#line 4154 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_var(p, (yyvsp[0].id)); } -#line 10738 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10754 "mrbgems/mruby-compiler/core/y.tab.c" break; case 489: /* p_kwrest: "**" "'nil'" */ -#line 4154 "mrbgems/mruby-compiler/core/parse.y" +#line 4158 "mrbgems/mruby-compiler/core/parse.y" { /* **nil - exact match, no extra keys allowed */ (yyval.nd) = (node*)-1; } -#line 10747 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10763 "mrbgems/mruby-compiler/core/y.tab.c" break; case 490: /* p_kwrest: "**" */ -#line 4159 "mrbgems/mruby-compiler/core/parse.y" +#line 4163 "mrbgems/mruby-compiler/core/parse.y" { /* ** - anonymous rest, discards extra keys */ (yyval.nd) = (node*)-2; } -#line 10756 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10772 "mrbgems/mruby-compiler/core/y.tab.c" break; case 491: /* p_var: "local variable or method" */ -#line 4166 "mrbgems/mruby-compiler/core/parse.y" +#line 4170 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_pat_var(p, (yyvsp[0].id)); } -#line 10764 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10780 "mrbgems/mruby-compiler/core/y.tab.c" break; case 492: /* opt_rescue: "'rescue'" exc_list exc_var then compstmt opt_rescue */ -#line 4174 "mrbgems/mruby-compiler/core/parse.y" +#line 4178 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(list3((yyvsp[-4].nd), (yyvsp[-3].nd), (yyvsp[-1].nd))); if ((yyvsp[0].nd)) (yyval.nd) = append((yyval.nd), (yyvsp[0].nd)); } -#line 10773 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10789 "mrbgems/mruby-compiler/core/y.tab.c" break; case 494: /* exc_list: arg */ -#line 4182 "mrbgems/mruby-compiler/core/parse.y" +#line 4186 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 10781 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10797 "mrbgems/mruby-compiler/core/y.tab.c" break; case 497: /* exc_var: "=>" lhs */ -#line 4190 "mrbgems/mruby-compiler/core/parse.y" +#line 4194 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 10789 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10805 "mrbgems/mruby-compiler/core/y.tab.c" break; case 499: /* opt_ensure: "'ensure'" compstmt */ -#line 4197 "mrbgems/mruby-compiler/core/parse.y" +#line 4201 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 10797 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10813 "mrbgems/mruby-compiler/core/y.tab.c" break; case 506: /* string: string string_fragment */ -#line 4211 "mrbgems/mruby-compiler/core/parse.y" +#line 4215 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = append((yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 10805 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10821 "mrbgems/mruby-compiler/core/y.tab.c" break; case 507: /* string_fragment: "character literal" */ -#line 4217 "mrbgems/mruby-compiler/core/parse.y" +#line 4221 "mrbgems/mruby-compiler/core/parse.y" { /* tCHAR is (len . str), wrap as cons list */ (yyval.nd) = list1((yyvsp[0].nd)); } -#line 10814 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10830 "mrbgems/mruby-compiler/core/y.tab.c" break; case 508: /* string_fragment: tSTRING */ -#line 4222 "mrbgems/mruby-compiler/core/parse.y" +#line 4226 "mrbgems/mruby-compiler/core/parse.y" { /* tSTRING is (len . str), wrap as cons list */ (yyval.nd) = list1((yyvsp[0].nd)); } -#line 10823 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10839 "mrbgems/mruby-compiler/core/y.tab.c" break; case 509: /* string_fragment: "string literal" tSTRING */ -#line 4227 "mrbgems/mruby-compiler/core/parse.y" +#line 4231 "mrbgems/mruby-compiler/core/parse.y" { /* $2 is (len . str), wrap as cons list */ (yyval.nd) = list1((yyvsp[0].nd)); } -#line 10832 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10848 "mrbgems/mruby-compiler/core/y.tab.c" break; case 510: /* string_fragment: "string literal" string_rep tSTRING */ -#line 4232 "mrbgems/mruby-compiler/core/parse.y" +#line 4236 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 10840 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10856 "mrbgems/mruby-compiler/core/y.tab.c" break; case 512: /* string_rep: string_rep string_interp */ -#line 4239 "mrbgems/mruby-compiler/core/parse.y" +#line 4243 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = append((yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 10848 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10864 "mrbgems/mruby-compiler/core/y.tab.c" break; case 513: /* string_interp: tSTRING_MID */ -#line 4245 "mrbgems/mruby-compiler/core/parse.y" +#line 4249 "mrbgems/mruby-compiler/core/parse.y" { /* $1 is already in (len . str) format */ (yyval.nd) = list1((yyvsp[0].nd)); } -#line 10857 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10873 "mrbgems/mruby-compiler/core/y.tab.c" break; case 514: /* @34: %empty */ -#line 4250 "mrbgems/mruby-compiler/core/parse.y" +#line 4254 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push_strterm(p); } -#line 10865 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10881 "mrbgems/mruby-compiler/core/y.tab.c" break; case 515: /* string_interp: tSTRING_PART @34 compstmt '}' */ -#line 4255 "mrbgems/mruby-compiler/core/parse.y" +#line 4259 "mrbgems/mruby-compiler/core/parse.y" { pop_strterm(p,(yyvsp[-2].nd)); /* $1 is already in (len . str) format, create (-1 . node) for expression */ node *expr_elem = cons(int_to_node(-1), (yyvsp[-1].nd)); (yyval.nd) = list2((yyvsp[-3].nd), expr_elem); } -#line 10876 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10892 "mrbgems/mruby-compiler/core/y.tab.c" break; case 516: /* string_interp: tLITERAL_DELIM */ -#line 4262 "mrbgems/mruby-compiler/core/parse.y" +#line 4266 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(new_literal_delim(p)); } -#line 10884 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10900 "mrbgems/mruby-compiler/core/y.tab.c" break; case 517: /* string_interp: tHD_LITERAL_DELIM heredoc_bodies */ -#line 4266 "mrbgems/mruby-compiler/core/parse.y" +#line 4270 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(new_literal_delim(p)); } -#line 10892 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10908 "mrbgems/mruby-compiler/core/y.tab.c" break; case 518: /* xstring: tXSTRING_BEG tXSTRING */ -#line 4272 "mrbgems/mruby-compiler/core/parse.y" +#line 4276 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = cons((yyvsp[0].nd), (node*)NULL); } -#line 10900 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10916 "mrbgems/mruby-compiler/core/y.tab.c" break; case 519: /* xstring: tXSTRING_BEG string_rep tXSTRING */ -#line 4276 "mrbgems/mruby-compiler/core/parse.y" +#line 4280 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 10908 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10924 "mrbgems/mruby-compiler/core/y.tab.c" break; case 520: /* regexp: tREGEXP_BEG tREGEXP */ -#line 4282 "mrbgems/mruby-compiler/core/parse.y" +#line 4286 "mrbgems/mruby-compiler/core/parse.y" { node *data = (yyvsp[0].nd); /* ((len . pattern) . (flags . encoding)) */ const char *flags = (const char*)data->cdr->car; @@ -10917,11 +10933,11 @@ YYLTYPE yylloc = yyloc_default; node *pattern_list = cons(data->car, (node*)NULL); (yyval.nd) = new_regx(p, pattern_list, flags, encoding); } -#line 10921 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10937 "mrbgems/mruby-compiler/core/y.tab.c" break; case 521: /* regexp: tREGEXP_BEG string_rep tREGEXP */ -#line 4291 "mrbgems/mruby-compiler/core/parse.y" +#line 4295 "mrbgems/mruby-compiler/core/parse.y" { node *data = (yyvsp[0].nd); /* ((len . pattern) . (flags . encoding)) */ const char *flags = (const char*)data->cdr->car; @@ -10930,47 +10946,47 @@ YYLTYPE yylloc = yyloc_default; node *complete_list = push((yyvsp[-1].nd), data->car); (yyval.nd) = new_regx(p, complete_list, flags, encoding); } -#line 10934 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10950 "mrbgems/mruby-compiler/core/y.tab.c" break; case 525: /* heredoc_body: tHEREDOC_END */ -#line 4309 "mrbgems/mruby-compiler/core/parse.y" +#line 4313 "mrbgems/mruby-compiler/core/parse.y" { parser_heredoc_info *info = parsing_heredoc_info(p); info->doc = push(info->doc, new_str_empty(p)); heredoc_end(p); } -#line 10944 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10960 "mrbgems/mruby-compiler/core/y.tab.c" break; case 526: /* heredoc_body: heredoc_string_rep tHEREDOC_END */ -#line 4315 "mrbgems/mruby-compiler/core/parse.y" +#line 4319 "mrbgems/mruby-compiler/core/parse.y" { heredoc_end(p); } -#line 10952 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10968 "mrbgems/mruby-compiler/core/y.tab.c" break; case 529: /* heredoc_string_interp: tHD_STRING_MID */ -#line 4325 "mrbgems/mruby-compiler/core/parse.y" +#line 4329 "mrbgems/mruby-compiler/core/parse.y" { parser_heredoc_info *info = parsing_heredoc_info(p); info->doc = push(info->doc, (yyvsp[0].nd)); heredoc_treat_nextline(p); } -#line 10962 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10978 "mrbgems/mruby-compiler/core/y.tab.c" break; case 530: /* @35: %empty */ -#line 4331 "mrbgems/mruby-compiler/core/parse.y" +#line 4335 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push_strterm(p); } -#line 10970 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10986 "mrbgems/mruby-compiler/core/y.tab.c" break; case 531: /* heredoc_string_interp: tHD_STRING_PART @35 compstmt '}' */ -#line 4336 "mrbgems/mruby-compiler/core/parse.y" +#line 4340 "mrbgems/mruby-compiler/core/parse.y" { pop_strterm(p, (yyvsp[-2].nd)); parser_heredoc_info *info = parsing_heredoc_info(p); @@ -10978,37 +10994,37 @@ YYLTYPE yylloc = yyloc_default; node *expr_elem = cons(int_to_node(-1), (yyvsp[-1].nd)); info->doc = push(push(info->doc, (yyvsp[-3].nd)), expr_elem); } -#line 10982 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10998 "mrbgems/mruby-compiler/core/y.tab.c" break; case 532: /* words: tWORDS_BEG tSTRING */ -#line 4346 "mrbgems/mruby-compiler/core/parse.y" +#line 4350 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_words(p, list1((yyvsp[0].nd))); } -#line 10990 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11006 "mrbgems/mruby-compiler/core/y.tab.c" break; case 533: /* words: tWORDS_BEG string_rep tSTRING */ -#line 4350 "mrbgems/mruby-compiler/core/parse.y" +#line 4354 "mrbgems/mruby-compiler/core/parse.y" { node *n = (yyvsp[-1].nd); n = push(n, (yyvsp[0].nd)); (yyval.nd) = new_words(p, n); } -#line 11000 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11016 "mrbgems/mruby-compiler/core/y.tab.c" break; case 534: /* symbol: basic_symbol */ -#line 4358 "mrbgems/mruby-compiler/core/parse.y" +#line 4362 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_sym(p, (yyvsp[0].id)); } -#line 11008 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11024 "mrbgems/mruby-compiler/core/y.tab.c" break; case 535: /* symbol: "symbol" "string literal" string_rep tSTRING */ -#line 4362 "mrbgems/mruby-compiler/core/parse.y" +#line 4366 "mrbgems/mruby-compiler/core/parse.y" { node *n = (yyvsp[-1].nd); p->lstate = EXPR_ENDARG; @@ -11020,183 +11036,183 @@ YYLTYPE yylloc = yyloc_default; } (yyval.nd) = new_dsym(p, n); } -#line 11024 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11040 "mrbgems/mruby-compiler/core/y.tab.c" break; case 536: /* symbol: "symbol" "numbered parameter" */ -#line 4374 "mrbgems/mruby-compiler/core/parse.y" +#line 4378 "mrbgems/mruby-compiler/core/parse.y" { mrb_sym sym = intern_numparam((yyvsp[0].num)); (yyval.nd) = new_sym(p, sym); } -#line 11033 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11049 "mrbgems/mruby-compiler/core/y.tab.c" break; case 537: /* basic_symbol: "symbol" sym */ -#line 4381 "mrbgems/mruby-compiler/core/parse.y" +#line 4385 "mrbgems/mruby-compiler/core/parse.y" { p->lstate = EXPR_END; (yyval.id) = (yyvsp[0].id); } -#line 11042 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11058 "mrbgems/mruby-compiler/core/y.tab.c" break; case 542: /* sym: tSTRING */ -#line 4392 "mrbgems/mruby-compiler/core/parse.y" +#line 4396 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = new_strsym(p, (yyvsp[0].nd)); } -#line 11050 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11066 "mrbgems/mruby-compiler/core/y.tab.c" break; case 543: /* sym: "string literal" tSTRING */ -#line 4396 "mrbgems/mruby-compiler/core/parse.y" +#line 4400 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = new_strsym(p, (yyvsp[0].nd)); } -#line 11058 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11074 "mrbgems/mruby-compiler/core/y.tab.c" break; case 544: /* symbols: tSYMBOLS_BEG tSTRING */ -#line 4402 "mrbgems/mruby-compiler/core/parse.y" +#line 4406 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_symbols(p, list1((yyvsp[0].nd))); } -#line 11066 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11082 "mrbgems/mruby-compiler/core/y.tab.c" break; case 545: /* symbols: tSYMBOLS_BEG string_rep tSTRING */ -#line 4406 "mrbgems/mruby-compiler/core/parse.y" +#line 4410 "mrbgems/mruby-compiler/core/parse.y" { node *n = (yyvsp[-1].nd); n = push(n, (yyvsp[0].nd)); (yyval.nd) = new_symbols(p, n); } -#line 11076 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11092 "mrbgems/mruby-compiler/core/y.tab.c" break; case 548: /* numeric: tUMINUS_NUM "integer literal" */ -#line 4416 "mrbgems/mruby-compiler/core/parse.y" +#line 4420 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_negate(p, (yyvsp[0].nd)); } -#line 11084 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11100 "mrbgems/mruby-compiler/core/y.tab.c" break; case 549: /* numeric: tUMINUS_NUM "float literal" */ -#line 4420 "mrbgems/mruby-compiler/core/parse.y" +#line 4424 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_negate(p, (yyvsp[0].nd)); } -#line 11092 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11108 "mrbgems/mruby-compiler/core/y.tab.c" break; case 550: /* variable: "local variable or method" */ -#line 4426 "mrbgems/mruby-compiler/core/parse.y" +#line 4430 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_lvar(p, (yyvsp[0].id)); } -#line 11100 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11116 "mrbgems/mruby-compiler/core/y.tab.c" break; case 551: /* variable: "instance variable" */ -#line 4430 "mrbgems/mruby-compiler/core/parse.y" +#line 4434 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_ivar(p, (yyvsp[0].id)); } -#line 11108 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11124 "mrbgems/mruby-compiler/core/y.tab.c" break; case 552: /* variable: "global variable" */ -#line 4434 "mrbgems/mruby-compiler/core/parse.y" +#line 4438 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_gvar(p, (yyvsp[0].id)); } -#line 11116 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11132 "mrbgems/mruby-compiler/core/y.tab.c" break; case 553: /* variable: "class variable" */ -#line 4438 "mrbgems/mruby-compiler/core/parse.y" +#line 4442 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_cvar(p, (yyvsp[0].id)); } -#line 11124 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11140 "mrbgems/mruby-compiler/core/y.tab.c" break; case 554: /* variable: "constant" */ -#line 4442 "mrbgems/mruby-compiler/core/parse.y" +#line 4446 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_const(p, (yyvsp[0].id)); } -#line 11132 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11148 "mrbgems/mruby-compiler/core/y.tab.c" break; case 555: /* var_lhs: variable */ -#line 4448 "mrbgems/mruby-compiler/core/parse.y" +#line 4452 "mrbgems/mruby-compiler/core/parse.y" { assignable(p, (yyvsp[0].nd)); } -#line 11140 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11156 "mrbgems/mruby-compiler/core/y.tab.c" break; case 556: /* var_lhs: "numbered parameter" */ -#line 4452 "mrbgems/mruby-compiler/core/parse.y" +#line 4456 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[0]), p, "can't assign to numbered parameter"); } -#line 11148 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11164 "mrbgems/mruby-compiler/core/y.tab.c" break; case 557: /* var_ref: variable */ -#line 4458 "mrbgems/mruby-compiler/core/parse.y" +#line 4462 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = var_reference(p, (yyvsp[0].nd)); } -#line 11156 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11172 "mrbgems/mruby-compiler/core/y.tab.c" break; case 558: /* var_ref: "numbered parameter" */ -#line 4462 "mrbgems/mruby-compiler/core/parse.y" +#line 4466 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_nvar(p, (yyvsp[0].num)); } -#line 11164 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11180 "mrbgems/mruby-compiler/core/y.tab.c" break; case 559: /* var_ref: "'nil'" */ -#line 4466 "mrbgems/mruby-compiler/core/parse.y" +#line 4470 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_nil(p); } -#line 11172 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11188 "mrbgems/mruby-compiler/core/y.tab.c" break; case 560: /* var_ref: "'self'" */ -#line 4470 "mrbgems/mruby-compiler/core/parse.y" +#line 4474 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_self(p); } -#line 11180 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11196 "mrbgems/mruby-compiler/core/y.tab.c" break; case 561: /* var_ref: "'true'" */ -#line 4474 "mrbgems/mruby-compiler/core/parse.y" +#line 4478 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_true(p); } -#line 11188 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11204 "mrbgems/mruby-compiler/core/y.tab.c" break; case 562: /* var_ref: "'false'" */ -#line 4478 "mrbgems/mruby-compiler/core/parse.y" +#line 4482 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_false(p); } -#line 11196 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11212 "mrbgems/mruby-compiler/core/y.tab.c" break; case 563: /* var_ref: "'__FILE__'" */ -#line 4482 "mrbgems/mruby-compiler/core/parse.y" +#line 4486 "mrbgems/mruby-compiler/core/parse.y" { const char *fn = mrb_sym_name_len(p->mrb, p->filename_sym, NULL); if (!fn) { @@ -11204,714 +11220,714 @@ YYLTYPE yylloc = yyloc_default; } (yyval.nd) = new_str(p, cons(cons(int_to_node(strlen(fn)), (node*)fn), (node*)NULL)); } -#line 11208 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11224 "mrbgems/mruby-compiler/core/y.tab.c" break; case 564: /* var_ref: "'__LINE__'" */ -#line 4490 "mrbgems/mruby-compiler/core/parse.y" +#line 4494 "mrbgems/mruby-compiler/core/parse.y" { char buf[16]; dump_int(p->lineno, buf); (yyval.nd) = new_int(p, buf, 10, 0); } -#line 11219 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11235 "mrbgems/mruby-compiler/core/y.tab.c" break; case 565: /* var_ref: "'__ENCODING__'" */ -#line 4497 "mrbgems/mruby-compiler/core/parse.y" +#line 4501 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_fcall(p, MRB_SYM(__ENCODING__), 0); } -#line 11227 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11243 "mrbgems/mruby-compiler/core/y.tab.c" break; case 568: /* superclass: %empty */ -#line 4507 "mrbgems/mruby-compiler/core/parse.y" +#line 4511 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 11235 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11251 "mrbgems/mruby-compiler/core/y.tab.c" break; case 569: /* $@36: %empty */ -#line 4511 "mrbgems/mruby-compiler/core/parse.y" +#line 4515 "mrbgems/mruby-compiler/core/parse.y" { p->lstate = EXPR_BEG; p->cmd_start = TRUE; } -#line 11244 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11260 "mrbgems/mruby-compiler/core/y.tab.c" break; case 570: /* superclass: '<' $@36 expr_value term */ -#line 4516 "mrbgems/mruby-compiler/core/parse.y" +#line 4520 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 11252 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11268 "mrbgems/mruby-compiler/core/y.tab.c" break; case 573: /* f_arglist_paren: '(' f_args rparen */ -#line 4532 "mrbgems/mruby-compiler/core/parse.y" +#line 4536 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); p->lstate = EXPR_BEG; p->cmd_start = TRUE; } -#line 11262 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11278 "mrbgems/mruby-compiler/core/y.tab.c" break; case 574: /* f_arglist_paren: '(' f_arg ',' tBDOT3 rparen */ -#line 4538 "mrbgems/mruby-compiler/core/parse.y" +#line 4542 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_dots(p, (yyvsp[-3].nd)); } -#line 11270 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11286 "mrbgems/mruby-compiler/core/y.tab.c" break; case 575: /* f_arglist_paren: '(' tBDOT3 rparen */ -#line 4542 "mrbgems/mruby-compiler/core/parse.y" +#line 4546 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_dots(p, 0); } -#line 11278 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11294 "mrbgems/mruby-compiler/core/y.tab.c" break; case 577: /* f_arglist: f_args term */ -#line 4549 "mrbgems/mruby-compiler/core/parse.y" +#line 4553 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 11286 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11302 "mrbgems/mruby-compiler/core/y.tab.c" break; case 578: /* f_arglist: f_arg ',' tBDOT3 term */ -#line 4553 "mrbgems/mruby-compiler/core/parse.y" +#line 4557 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_dots(p, (yyvsp[-3].nd)); } -#line 11294 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11310 "mrbgems/mruby-compiler/core/y.tab.c" break; case 579: /* f_arglist: "..." term */ -#line 4557 "mrbgems/mruby-compiler/core/parse.y" +#line 4561 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_dots(p, 0); } -#line 11302 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11318 "mrbgems/mruby-compiler/core/y.tab.c" break; case 580: /* f_label: "local variable or method" "label" */ -#line 4563 "mrbgems/mruby-compiler/core/parse.y" +#line 4567 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = (yyvsp[-1].id); local_nest(p); p->lstate = EXPR_MID; /* make newlines significant after label */ } -#line 11312 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11328 "mrbgems/mruby-compiler/core/y.tab.c" break; case 581: /* f_label: "numbered parameter" "label" */ -#line 4569 "mrbgems/mruby-compiler/core/parse.y" +#line 4573 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_numparam((yyvsp[-1].num)); local_nest(p); p->lstate = EXPR_MID; /* make newlines significant after label */ } -#line 11322 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11338 "mrbgems/mruby-compiler/core/y.tab.c" break; case 582: /* f_kw: f_label arg */ -#line 4577 "mrbgems/mruby-compiler/core/parse.y" +#line 4581 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = new_kw_arg(p, (yyvsp[-1].id), cons((yyvsp[0].nd), locals_node(p))); local_unnest(p); } -#line 11332 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11348 "mrbgems/mruby-compiler/core/y.tab.c" break; case 583: /* f_kw: f_label */ -#line 4583 "mrbgems/mruby-compiler/core/parse.y" +#line 4587 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_kw_arg(p, (yyvsp[0].id), 0); local_unnest(p); } -#line 11341 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11357 "mrbgems/mruby-compiler/core/y.tab.c" break; case 584: /* f_block_kw: f_label primary_value */ -#line 4590 "mrbgems/mruby-compiler/core/parse.y" +#line 4594 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = new_kw_arg(p, (yyvsp[-1].id), cons((yyvsp[0].nd), locals_node(p))); local_unnest(p); } -#line 11351 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11367 "mrbgems/mruby-compiler/core/y.tab.c" break; case 585: /* f_block_kw: f_label */ -#line 4596 "mrbgems/mruby-compiler/core/parse.y" +#line 4600 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_kw_arg(p, (yyvsp[0].id), 0); local_unnest(p); } -#line 11360 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11376 "mrbgems/mruby-compiler/core/y.tab.c" break; case 586: /* f_block_kwarg: f_block_kw */ -#line 4603 "mrbgems/mruby-compiler/core/parse.y" +#line 4607 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 11368 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11384 "mrbgems/mruby-compiler/core/y.tab.c" break; case 587: /* f_block_kwarg: f_block_kwarg ',' f_block_kw */ -#line 4607 "mrbgems/mruby-compiler/core/parse.y" +#line 4611 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 11376 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11392 "mrbgems/mruby-compiler/core/y.tab.c" break; case 588: /* f_kwarg: f_kw */ -#line 4613 "mrbgems/mruby-compiler/core/parse.y" +#line 4617 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 11384 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11400 "mrbgems/mruby-compiler/core/y.tab.c" break; case 589: /* f_kwarg: f_kwarg ',' f_kw */ -#line 4617 "mrbgems/mruby-compiler/core/parse.y" +#line 4621 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 11392 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11408 "mrbgems/mruby-compiler/core/y.tab.c" break; case 592: /* f_kwrest: kwrest_mark "local variable or method" */ -#line 4627 "mrbgems/mruby-compiler/core/parse.y" +#line 4631 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = (yyvsp[0].id); } -#line 11400 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11416 "mrbgems/mruby-compiler/core/y.tab.c" break; case 593: /* f_kwrest: kwrest_mark */ -#line 4631 "mrbgems/mruby-compiler/core/parse.y" +#line 4635 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(pow); } -#line 11408 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11424 "mrbgems/mruby-compiler/core/y.tab.c" break; case 594: /* args_tail: f_kwarg ',' f_kwrest opt_f_block_arg */ -#line 4637 "mrbgems/mruby-compiler/core/parse.y" +#line 4641 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].id)); } -#line 11416 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11432 "mrbgems/mruby-compiler/core/y.tab.c" break; case 595: /* args_tail: f_kwarg opt_f_block_arg */ -#line 4641 "mrbgems/mruby-compiler/core/parse.y" +#line 4645 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, (yyvsp[-1].nd), 0, (yyvsp[0].id)); } -#line 11424 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11440 "mrbgems/mruby-compiler/core/y.tab.c" break; case 596: /* args_tail: f_kwrest opt_f_block_arg */ -#line 4645 "mrbgems/mruby-compiler/core/parse.y" +#line 4649 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, 0, (yyvsp[-1].id), (yyvsp[0].id)); } -#line 11432 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11448 "mrbgems/mruby-compiler/core/y.tab.c" break; case 597: /* args_tail: f_block_arg */ -#line 4649 "mrbgems/mruby-compiler/core/parse.y" +#line 4653 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, 0, 0, (yyvsp[0].id)); } -#line 11440 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11456 "mrbgems/mruby-compiler/core/y.tab.c" break; case 598: /* opt_args_tail: ',' args_tail */ -#line 4655 "mrbgems/mruby-compiler/core/parse.y" +#line 4659 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 11448 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11464 "mrbgems/mruby-compiler/core/y.tab.c" break; case 599: /* opt_args_tail: ',' */ -#line 4659 "mrbgems/mruby-compiler/core/parse.y" +#line 4663 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, 0, 0, 0); } -#line 11456 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11472 "mrbgems/mruby-compiler/core/y.tab.c" break; case 600: /* opt_args_tail: %empty */ -#line 4663 "mrbgems/mruby-compiler/core/parse.y" +#line 4667 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, 0, 0, 0); } -#line 11464 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11480 "mrbgems/mruby-compiler/core/y.tab.c" break; case 601: /* f_args: f_arg ',' f_optarg ',' f_rest_arg opt_args_tail */ -#line 4669 "mrbgems/mruby-compiler/core/parse.y" +#line 4673 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-5].nd), (yyvsp[-3].nd), (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 11472 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11488 "mrbgems/mruby-compiler/core/y.tab.c" break; case 602: /* f_args: f_arg ',' f_optarg ',' f_rest_arg ',' f_arg opt_args_tail */ -#line 4673 "mrbgems/mruby-compiler/core/parse.y" +#line 4677 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-7].nd), (yyvsp[-5].nd), (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 11480 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11496 "mrbgems/mruby-compiler/core/y.tab.c" break; case 603: /* f_args: f_arg ',' f_optarg opt_args_tail */ -#line 4677 "mrbgems/mruby-compiler/core/parse.y" +#line 4681 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-3].nd), (yyvsp[-1].nd), 0, 0, (yyvsp[0].nd)); } -#line 11488 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11504 "mrbgems/mruby-compiler/core/y.tab.c" break; case 604: /* f_args: f_arg ',' f_optarg ',' f_arg opt_args_tail */ -#line 4681 "mrbgems/mruby-compiler/core/parse.y" +#line 4685 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-5].nd), (yyvsp[-3].nd), 0, (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 11496 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11512 "mrbgems/mruby-compiler/core/y.tab.c" break; case 605: /* f_args: f_arg ',' f_rest_arg opt_args_tail */ -#line 4685 "mrbgems/mruby-compiler/core/parse.y" +#line 4689 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-3].nd), 0, (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 11504 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11520 "mrbgems/mruby-compiler/core/y.tab.c" break; case 606: /* f_args: f_arg ',' f_rest_arg ',' f_arg opt_args_tail */ -#line 4689 "mrbgems/mruby-compiler/core/parse.y" +#line 4693 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-5].nd), 0, (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 11512 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11528 "mrbgems/mruby-compiler/core/y.tab.c" break; case 607: /* f_args: f_arg opt_args_tail */ -#line 4693 "mrbgems/mruby-compiler/core/parse.y" +#line 4697 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-1].nd), 0, 0, 0, (yyvsp[0].nd)); } -#line 11520 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11536 "mrbgems/mruby-compiler/core/y.tab.c" break; case 608: /* f_args: f_optarg ',' f_rest_arg opt_args_tail */ -#line 4697 "mrbgems/mruby-compiler/core/parse.y" +#line 4701 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-3].nd), (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 11528 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11544 "mrbgems/mruby-compiler/core/y.tab.c" break; case 609: /* f_args: f_optarg ',' f_rest_arg ',' f_arg opt_args_tail */ -#line 4701 "mrbgems/mruby-compiler/core/parse.y" +#line 4705 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-5].nd), (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 11536 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11552 "mrbgems/mruby-compiler/core/y.tab.c" break; case 610: /* f_args: f_optarg opt_args_tail */ -#line 4705 "mrbgems/mruby-compiler/core/parse.y" +#line 4709 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-1].nd), 0, 0, (yyvsp[0].nd)); } -#line 11544 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11560 "mrbgems/mruby-compiler/core/y.tab.c" break; case 611: /* f_args: f_optarg ',' f_arg opt_args_tail */ -#line 4709 "mrbgems/mruby-compiler/core/parse.y" +#line 4713 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-3].nd), 0, (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 11552 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11568 "mrbgems/mruby-compiler/core/y.tab.c" break; case 612: /* f_args: f_rest_arg opt_args_tail */ -#line 4713 "mrbgems/mruby-compiler/core/parse.y" +#line 4717 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, 0, (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 11560 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11576 "mrbgems/mruby-compiler/core/y.tab.c" break; case 613: /* f_args: f_rest_arg ',' f_arg opt_args_tail */ -#line 4717 "mrbgems/mruby-compiler/core/parse.y" +#line 4721 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, 0, (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 11568 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11584 "mrbgems/mruby-compiler/core/y.tab.c" break; case 614: /* f_args: args_tail */ -#line 4721 "mrbgems/mruby-compiler/core/parse.y" +#line 4725 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, 0, 0, 0, (yyvsp[0].nd)); } -#line 11576 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11592 "mrbgems/mruby-compiler/core/y.tab.c" break; case 615: /* f_args: %empty */ -#line 4725 "mrbgems/mruby-compiler/core/parse.y" +#line 4729 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, 0); (yyval.nd) = new_args(p, 0, 0, 0, 0, 0); } -#line 11585 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11601 "mrbgems/mruby-compiler/core/y.tab.c" break; case 616: /* f_bad_arg: "constant" */ -#line 4732 "mrbgems/mruby-compiler/core/parse.y" +#line 4736 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[0]), p, "formal argument cannot be a constant"); (yyval.nd) = 0; } -#line 11594 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11610 "mrbgems/mruby-compiler/core/y.tab.c" break; case 617: /* f_bad_arg: "instance variable" */ -#line 4737 "mrbgems/mruby-compiler/core/parse.y" +#line 4741 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[0]), p, "formal argument cannot be an instance variable"); (yyval.nd) = 0; } -#line 11603 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11619 "mrbgems/mruby-compiler/core/y.tab.c" break; case 618: /* f_bad_arg: "global variable" */ -#line 4742 "mrbgems/mruby-compiler/core/parse.y" +#line 4746 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[0]), p, "formal argument cannot be a global variable"); (yyval.nd) = 0; } -#line 11612 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11628 "mrbgems/mruby-compiler/core/y.tab.c" break; case 619: /* f_bad_arg: "class variable" */ -#line 4747 "mrbgems/mruby-compiler/core/parse.y" +#line 4751 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[0]), p, "formal argument cannot be a class variable"); (yyval.nd) = 0; } -#line 11621 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11637 "mrbgems/mruby-compiler/core/y.tab.c" break; case 620: /* f_bad_arg: "numbered parameter" */ -#line 4752 "mrbgems/mruby-compiler/core/parse.y" +#line 4756 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[0]), p, "formal argument cannot be a numbered parameter"); (yyval.nd) = 0; } -#line 11630 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11646 "mrbgems/mruby-compiler/core/y.tab.c" break; case 621: /* f_norm_arg: f_bad_arg */ -#line 4759 "mrbgems/mruby-compiler/core/parse.y" +#line 4763 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = 0; } -#line 11638 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11654 "mrbgems/mruby-compiler/core/y.tab.c" break; case 622: /* f_norm_arg: "local variable or method" */ -#line 4763 "mrbgems/mruby-compiler/core/parse.y" +#line 4767 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, (yyvsp[0].id)); (yyval.id) = (yyvsp[0].id); } -#line 11647 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11663 "mrbgems/mruby-compiler/core/y.tab.c" break; case 623: /* f_arg_item: f_norm_arg */ -#line 4770 "mrbgems/mruby-compiler/core/parse.y" +#line 4774 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_lvar(p, (yyvsp[0].id)); } -#line 11655 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11671 "mrbgems/mruby-compiler/core/y.tab.c" break; case 624: /* @37: %empty */ -#line 4774 "mrbgems/mruby-compiler/core/parse.y" +#line 4778 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = local_switch(p); } -#line 11663 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11679 "mrbgems/mruby-compiler/core/y.tab.c" break; case 625: /* f_arg_item: tLPAREN @37 f_margs rparen */ -#line 4778 "mrbgems/mruby-compiler/core/parse.y" +#line 4782 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_marg(p, (yyvsp[-1].nd)); local_resume(p, (yyvsp[-2].nd)); local_add_f(p, 0); } -#line 11673 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11689 "mrbgems/mruby-compiler/core/y.tab.c" break; case 626: /* f_arg: f_arg_item */ -#line 4786 "mrbgems/mruby-compiler/core/parse.y" +#line 4790 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 11681 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11697 "mrbgems/mruby-compiler/core/y.tab.c" break; case 627: /* f_arg: f_arg ',' f_arg_item */ -#line 4790 "mrbgems/mruby-compiler/core/parse.y" +#line 4794 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 11689 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11705 "mrbgems/mruby-compiler/core/y.tab.c" break; case 628: /* f_opt_asgn: "local variable or method" '=' */ -#line 4796 "mrbgems/mruby-compiler/core/parse.y" +#line 4800 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, (yyvsp[-1].id)); local_nest(p); (yyval.id) = (yyvsp[-1].id); } -#line 11699 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11715 "mrbgems/mruby-compiler/core/y.tab.c" break; case 629: /* f_opt: f_opt_asgn arg */ -#line 4804 "mrbgems/mruby-compiler/core/parse.y" +#line 4808 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = cons(sym_to_node((yyvsp[-1].id)), cons((yyvsp[0].nd), locals_node(p))); local_unnest(p); } -#line 11709 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11725 "mrbgems/mruby-compiler/core/y.tab.c" break; case 630: /* f_block_opt: f_opt_asgn primary_value */ -#line 4812 "mrbgems/mruby-compiler/core/parse.y" +#line 4816 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = cons(sym_to_node((yyvsp[-1].id)), cons((yyvsp[0].nd), locals_node(p))); local_unnest(p); } -#line 11719 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11735 "mrbgems/mruby-compiler/core/y.tab.c" break; case 631: /* f_block_optarg: f_block_opt */ -#line 4820 "mrbgems/mruby-compiler/core/parse.y" +#line 4824 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 11727 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11743 "mrbgems/mruby-compiler/core/y.tab.c" break; case 632: /* f_block_optarg: f_block_optarg ',' f_block_opt */ -#line 4824 "mrbgems/mruby-compiler/core/parse.y" +#line 4828 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 11735 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11751 "mrbgems/mruby-compiler/core/y.tab.c" break; case 633: /* f_optarg: f_opt */ -#line 4830 "mrbgems/mruby-compiler/core/parse.y" +#line 4834 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 11743 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11759 "mrbgems/mruby-compiler/core/y.tab.c" break; case 634: /* f_optarg: f_optarg ',' f_opt */ -#line 4834 "mrbgems/mruby-compiler/core/parse.y" +#line 4838 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 11751 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11767 "mrbgems/mruby-compiler/core/y.tab.c" break; case 637: /* f_rest_arg: restarg_mark "local variable or method" */ -#line 4844 "mrbgems/mruby-compiler/core/parse.y" +#line 4848 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, (yyvsp[0].id)); (yyval.id) = (yyvsp[0].id); } -#line 11760 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11776 "mrbgems/mruby-compiler/core/y.tab.c" break; case 638: /* f_rest_arg: restarg_mark */ -#line 4849 "mrbgems/mruby-compiler/core/parse.y" +#line 4853 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(mul); local_add_f(p, (yyval.id)); } -#line 11769 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11785 "mrbgems/mruby-compiler/core/y.tab.c" break; case 641: /* f_block_arg: blkarg_mark "local variable or method" */ -#line 4860 "mrbgems/mruby-compiler/core/parse.y" +#line 4864 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = (yyvsp[0].id); } -#line 11777 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11793 "mrbgems/mruby-compiler/core/y.tab.c" break; case 642: /* f_block_arg: blkarg_mark "'nil'" */ -#line 4864 "mrbgems/mruby-compiler/core/parse.y" +#line 4868 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = MRB_SYM(nil); } -#line 11785 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11801 "mrbgems/mruby-compiler/core/y.tab.c" break; case 643: /* f_block_arg: blkarg_mark */ -#line 4868 "mrbgems/mruby-compiler/core/parse.y" +#line 4872 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = intern_op(and); } -#line 11793 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11809 "mrbgems/mruby-compiler/core/y.tab.c" break; case 644: /* opt_f_block_arg: ',' f_block_arg */ -#line 4874 "mrbgems/mruby-compiler/core/parse.y" +#line 4878 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = (yyvsp[0].id); } -#line 11801 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11817 "mrbgems/mruby-compiler/core/y.tab.c" break; case 645: /* opt_f_block_arg: ',' */ -#line 4878 "mrbgems/mruby-compiler/core/parse.y" +#line 4882 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = 0; } -#line 11809 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11825 "mrbgems/mruby-compiler/core/y.tab.c" break; case 646: /* opt_f_block_arg: none */ -#line 4882 "mrbgems/mruby-compiler/core/parse.y" +#line 4886 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = 0; } -#line 11817 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11833 "mrbgems/mruby-compiler/core/y.tab.c" break; case 647: /* singleton: var_ref */ -#line 4888 "mrbgems/mruby-compiler/core/parse.y" +#line 4892 "mrbgems/mruby-compiler/core/parse.y" { prohibit_literals(p, (yyvsp[0].nd)); (yyval.nd) = (yyvsp[0].nd); if (!(yyval.nd)) (yyval.nd) = new_nil(p); } -#line 11827 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11843 "mrbgems/mruby-compiler/core/y.tab.c" break; case 648: /* $@38: %empty */ -#line 4893 "mrbgems/mruby-compiler/core/parse.y" +#line 4897 "mrbgems/mruby-compiler/core/parse.y" {p->lstate = EXPR_BEG;} -#line 11833 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11849 "mrbgems/mruby-compiler/core/y.tab.c" break; case 649: /* singleton: '(' $@38 expr rparen */ -#line 4894 "mrbgems/mruby-compiler/core/parse.y" +#line 4898 "mrbgems/mruby-compiler/core/parse.y" { prohibit_literals(p, (yyvsp[-1].nd)); (yyval.nd) = (yyvsp[-1].nd); } -#line 11842 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11858 "mrbgems/mruby-compiler/core/y.tab.c" break; case 651: /* assoc_list: assocs trailer */ -#line 4902 "mrbgems/mruby-compiler/core/parse.y" +#line 4906 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 11850 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11866 "mrbgems/mruby-compiler/core/y.tab.c" break; case 652: /* assocs: assoc */ -#line 4908 "mrbgems/mruby-compiler/core/parse.y" +#line 4912 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 11858 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11874 "mrbgems/mruby-compiler/core/y.tab.c" break; case 653: /* assocs: assocs comma assoc */ -#line 4912 "mrbgems/mruby-compiler/core/parse.y" +#line 4916 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 11866 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11882 "mrbgems/mruby-compiler/core/y.tab.c" break; case 654: /* assoc: arg "=>" arg */ -#line 4918 "mrbgems/mruby-compiler/core/parse.y" +#line 4922 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[-2].nd)); void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = cons((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 11876 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11892 "mrbgems/mruby-compiler/core/y.tab.c" break; case 655: /* assoc: "local variable or method" "label" arg */ -#line 4924 "mrbgems/mruby-compiler/core/parse.y" +#line 4928 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = cons(new_sym(p, (yyvsp[-2].id)), (yyvsp[0].nd)); } -#line 11885 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11901 "mrbgems/mruby-compiler/core/y.tab.c" break; case 656: /* assoc: "local variable or method" "label" */ -#line 4929 "mrbgems/mruby-compiler/core/parse.y" +#line 4933 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = cons(new_sym(p, (yyvsp[-1].id)), label_reference(p, (yyvsp[-1].id))); } -#line 11893 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11909 "mrbgems/mruby-compiler/core/y.tab.c" break; case 657: /* assoc: "numbered parameter" "label" */ -#line 4933 "mrbgems/mruby-compiler/core/parse.y" +#line 4937 "mrbgems/mruby-compiler/core/parse.y" { mrb_sym sym = intern_numparam((yyvsp[-1].num)); (yyval.nd) = cons(new_sym(p, sym), label_reference(p, sym)); } -#line 11902 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11918 "mrbgems/mruby-compiler/core/y.tab.c" break; case 658: /* assoc: "numbered parameter" "label" arg */ -#line 4938 "mrbgems/mruby-compiler/core/parse.y" +#line 4942 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = cons(new_sym(p, intern_numparam((yyvsp[-2].num))), (yyvsp[0].nd)); } -#line 11911 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11927 "mrbgems/mruby-compiler/core/y.tab.c" break; case 659: /* assoc: string_fragment "label" arg */ -#line 4943 "mrbgems/mruby-compiler/core/parse.y" +#line 4947 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); if ((yyvsp[-2].nd)->cdr) { @@ -11927,75 +11943,75 @@ YYLTYPE yylloc = yyloc_default; (yyval.nd) = cons(new_sym(p, new_strsym(p, (yyvsp[-2].nd)->car)), (yyvsp[0].nd)); } } -#line 11931 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11947 "mrbgems/mruby-compiler/core/y.tab.c" break; case 660: /* assoc: "**" arg */ -#line 4959 "mrbgems/mruby-compiler/core/parse.y" +#line 4963 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = cons(new_kw_rest_args(p, 0), (yyvsp[0].nd)); } -#line 11940 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11956 "mrbgems/mruby-compiler/core/y.tab.c" break; case 661: /* assoc: "**" */ -#line 4964 "mrbgems/mruby-compiler/core/parse.y" +#line 4968 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = cons(new_kw_rest_args(p, 0), new_lvar(p, intern_op(pow))); } -#line 11948 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11964 "mrbgems/mruby-compiler/core/y.tab.c" break; case 674: /* call_op: '.' */ -#line 4990 "mrbgems/mruby-compiler/core/parse.y" +#line 4994 "mrbgems/mruby-compiler/core/parse.y" { (yyval.num) = '.'; } -#line 11956 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11972 "mrbgems/mruby-compiler/core/y.tab.c" break; case 675: /* call_op: "&." */ -#line 4994 "mrbgems/mruby-compiler/core/parse.y" +#line 4998 "mrbgems/mruby-compiler/core/parse.y" { (yyval.num) = 0; } -#line 11964 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11980 "mrbgems/mruby-compiler/core/y.tab.c" break; case 677: /* call_op2: "::" */ -#line 5001 "mrbgems/mruby-compiler/core/parse.y" +#line 5005 "mrbgems/mruby-compiler/core/parse.y" { (yyval.num) = tCOLON2; } -#line 11972 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11988 "mrbgems/mruby-compiler/core/y.tab.c" break; case 686: /* term: ';' */ -#line 5022 "mrbgems/mruby-compiler/core/parse.y" +#line 5026 "mrbgems/mruby-compiler/core/parse.y" {yyerrok;} -#line 11978 "mrbgems/mruby-compiler/core/y.tab.c" +#line 11994 "mrbgems/mruby-compiler/core/y.tab.c" break; case 688: /* nl: '\n' */ -#line 5027 "mrbgems/mruby-compiler/core/parse.y" +#line 5031 "mrbgems/mruby-compiler/core/parse.y" { p->lineno += (yyvsp[0].num); p->column = 0; } -#line 11987 "mrbgems/mruby-compiler/core/y.tab.c" +#line 12003 "mrbgems/mruby-compiler/core/y.tab.c" break; case 692: /* none: %empty */ -#line 5039 "mrbgems/mruby-compiler/core/parse.y" +#line 5043 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 11995 "mrbgems/mruby-compiler/core/y.tab.c" +#line 12011 "mrbgems/mruby-compiler/core/y.tab.c" break; -#line 11999 "mrbgems/mruby-compiler/core/y.tab.c" +#line 12015 "mrbgems/mruby-compiler/core/y.tab.c" default: break; } @@ -12228,7 +12244,7 @@ YYLTYPE yylloc = yyloc_default; return yyresult; } -#line 5043 "mrbgems/mruby-compiler/core/parse.y" +#line 5047 "mrbgems/mruby-compiler/core/parse.y" #define pylval (*((YYSTYPE*)(p->ylval))) @@ -14847,6 +14863,10 @@ mrb_parser_set_filename(struct mrb_parser_state *p, const char *f) sym = mrb_intern_cstr(p->mrb, f); p->filename_sym = sym; + /* Save current lineno so that AST nodes produced from a bison lookahead + across the file boundary (in partial_hook) can recover the correct + line in init_var_header instead of recording lineno=0. */ + p->prev_file_lineno = p->lineno; p->lineno = (p->filename_table_length > 0)? 0 : 1; for (i = 0; i < p->filename_table_length; i++) { @@ -15879,7 +15899,8 @@ dump_node(mrb_state *mrb, node *tree, int offset) node *item = list->car; if (item->car == 0 && item->cdr == 0) { /* Skip separator (0 . 0) */ - } else if (item->car && item->cdr) { + } + else if (item->car && item->cdr) { /* String item: (len . str) */ dump_prefix(offset+1, lineno); int len = node_to_int(item->car); diff --git a/mrbgems/mruby-dir/mrbgem.rake b/mrbgems/mruby-dir/mrbgem.rake index 71ed21cbd5..ed05e14d1e 100644 --- a/mrbgems/mruby-dir/mrbgem.rake +++ b/mrbgems/mruby-dir/mrbgem.rake @@ -1,35 +1,4 @@ MRuby::Gem::Specification.new('mruby-dir') do |spec| spec.license = 'MIT and MIT-like license' spec.authors = ['Internet Initiative Japan Inc.', 'Kevlin Henney'] - - # Check if HAL gem is loaded - # HAL gems must be explicitly specified in build config (recommended) or via auto-selection below - spec.build.gems.one? { |g| g.name =~ /^hal-.*-dir$/ } or begin - # No HAL found - determine appropriate error message or auto-load - suggested_hal = if ENV['MRUBY_DIR_HAL'] - ENV['MRUBY_DIR_HAL'] - elsif spec.for_windows? - 'hal-win-dir' - elsif RUBY_PLATFORM =~ /linux|darwin|bsd/ - 'hal-posix-dir' - else - nil - end - - if suggested_hal - # Auto-load HAL gem for convenience (for development) - # This works because HAL gems declare dependency on mruby-dir - warn "mruby-dir: No HAL specified, loading #{suggested_hal} (explicit selection recommended)" - spec.build.gem core: suggested_hal - else - # Unknown platform - fail with helpful message - fail "mruby-dir: No HAL available for platform '#{RUBY_PLATFORM}'.\n" \ - "Please specify HAL gem explicitly in your build config:\n" \ - " conf.gem core: 'hal-posix-dir' # For Linux/macOS/BSD\n" \ - " conf.gem core: 'hal-win-dir' # For Windows\n" \ - "Or set environment variable:\n" \ - " MRUBY_DIR_HAL=hal-myplatform-dir\n" \ - "See mrbgems/mruby-dir/README.md for creating custom HAL." - end - end end diff --git a/mrbgems/hal-posix-dir/src/dir_hal.c b/mrbgems/mruby-dir/ports/posix/dir_hal.c similarity index 90% rename from mrbgems/hal-posix-dir/src/dir_hal.c rename to mrbgems/mruby-dir/ports/posix/dir_hal.c index 2ad1c33c71..898cef32a7 100644 --- a/mrbgems/hal-posix-dir/src/dir_hal.c +++ b/mrbgems/mruby-dir/ports/posix/dir_hal.c @@ -173,21 +173,3 @@ mrb_hal_dir_final(mrb_state *mrb) (void)mrb; /* No cleanup needed for POSIX */ } - -/* - * Gem initialization - */ - -void -mrb_hal_posix_dir_gem_init(mrb_state *mrb) -{ - (void)mrb; - /* HAL interface functions are called by mruby-dir gem */ -} - -void -mrb_hal_posix_dir_gem_final(mrb_state *mrb) -{ - (void)mrb; - /* Cleanup handled by mrb_hal_dir_final called from mruby-dir */ -} diff --git a/mrbgems/hal-win-dir/src/dir_hal.c b/mrbgems/mruby-dir/ports/win/dir_hal.c similarity index 84% rename from mrbgems/hal-win-dir/src/dir_hal.c rename to mrbgems/mruby-dir/ports/win/dir_hal.c index db2123951b..8bc3b1be26 100644 --- a/mrbgems/hal-win-dir/src/dir_hal.c +++ b/mrbgems/mruby-dir/ports/win/dir_hal.c @@ -214,36 +214,3 @@ mrb_hal_dir_final(mrb_state *mrb) (void)mrb; /* No cleanup needed for Windows */ } - -/* - * Gem initialization - */ - -void -mrb_hal_win_dir_gem_init(mrb_state *mrb) -{ - (void)mrb; - /* HAL interface functions are called by mruby-dir gem */ -} - -void -mrb_hal_win_dir_gem_final(mrb_state *mrb) -{ - (void)mrb; - /* Cleanup handled by mrb_hal_dir_final called from mruby-dir */ -} - -/* -** Portions derived from dirent.c by Kevlin Henney: -** -** Copyright Kevlin Henney, 1997, 2003, 2012. All rights reserved. -** -** Permission to use, copy, modify, and distribute this software and its -** documentation for any purpose is hereby granted without fee, provided -** that this copyright and permissions notice appear in all copies and -** derivatives. -** -** This software is supplied "as is" without express or implied warranty. -** -** But that said, if there are any problems please get in touch. -*/ diff --git a/mrbgems/mruby-enum-lazy/mrblib/lazy.rb b/mrbgems/mruby-enum-lazy/mrblib/lazy.rb index 140faf65b4..25e728004e 100644 --- a/mrbgems/mruby-enum-lazy/mrblib/lazy.rb +++ b/mrbgems/mruby-enum-lazy/mrblib/lazy.rb @@ -349,6 +349,26 @@ def uniq(&block) } end + # + # call-seq: + # lazy.tap_each {|obj| block } -> lazy_enumerator + # + # Yields each element to the block for side effects (e.g. logging, + # debugging) and passes it through unmodified. + # + # (1..Float::INFINITY).lazy + # .tap_each {|i| puts "saw: #{i}" } + # .select(&:even?) + # .first(3) + # #=> [2, 4, 6] (prints "saw: 1", "saw: 2", ... along the way) + # + def tap_each(&block) + Lazy.new(self){|yielder, val| + block.call(val) + yielder << val + } + end + # # call-seq: # lazy.force -> array diff --git a/mrbgems/mruby-enum-lazy/test/lazy.rb b/mrbgems/mruby-enum-lazy/test/lazy.rb index a3090864a9..0464441ee6 100644 --- a/mrbgems/mruby-enum-lazy/test/lazy.rb +++ b/mrbgems/mruby-enum-lazy/test/lazy.rb @@ -64,6 +64,19 @@ def a.b(b=nil) assert_equal [0, 1, 5, 6], lazy_grep_v.first(4) end +assert("Enumerator::Lazy#tap_each") do + seen = [] + result = [1, 2, 3, 4, 5].lazy.tap_each{|x| seen << x }.select{|x| x % 2 == 0 }.force + assert_equal [2, 4], result + assert_equal [1, 2, 3, 4, 5], seen +end + +assert("Enumerator::Lazy#tap_each laziness") do + seen = [] + [1, 2, 3, 4, 5].lazy.tap_each{|x| seen << x }.first(3) + assert_equal [1, 2, 3], seen +end + assert("Enumerator::Lazy#zip with cycle") do e1 = [1, 2, 3].cycle e2 = [:a, :b].cycle diff --git a/mrbgems/mruby-env/README.md b/mrbgems/mruby-env/README.md new file mode 100644 index 0000000000..37f8f894d8 --- /dev/null +++ b/mrbgems/mruby-env/README.md @@ -0,0 +1,33 @@ +# mruby-env + +ENV object for environment variable access. + +This gem is a built-in replacement for [iij/mruby-env](https://github.com/iij/mruby-env), +providing a superset of its API. + +## Methods + +`[]`, `[]=`, `assoc`, `clear`, `delete`, `each`, `each_key`, +`each_value`, `empty?`, `fetch`, `filter`/`select`, `freeze`, +`has_key?`/`include?`/`key?`/`member?`, `has_value?`/`value?`, +`inspect`, `key`, `keys`, `length`/`size`, `merge!`/`update`, +`rassoc`, `reject`, `replace`, `slice`, `store`, `to_a`, `to_h`, +`to_s`, `values` + +ENV includes `Enumerable` via `mruby-enumerator`. + +## Example + +```ruby +ENV["MY_VAR"] = "hello" +ENV["MY_VAR"] #=> "hello" +ENV.delete("MY_VAR") #=> "hello" + +ENV.keys #=> ["PATH", "HOME", ...] +ENV.each { |k, v| puts "#{k}=#{v}" } +ENV.select { |k, v| k.start_with?("RUBY") } +``` + +## License + +MIT License - see the mruby license. diff --git a/mrbgems/mruby-env/mrbgem.rake b/mrbgems/mruby-env/mrbgem.rake new file mode 100644 index 0000000000..8ea123e3b3 --- /dev/null +++ b/mrbgems/mruby-env/mrbgem.rake @@ -0,0 +1,6 @@ +MRuby::Gem::Specification.new('mruby-env') do |spec| + spec.license = 'MIT' + spec.authors = ['mruby developers'] + spec.summary = 'ENV object for environment variable access' + spec.add_dependency('mruby-enumerator', :core => 'mruby-enumerator') +end diff --git a/mrbgems/mruby-env/mrblib/env.rb b/mrbgems/mruby-env/mrblib/env.rb new file mode 100644 index 0000000000..e8183dd8a6 --- /dev/null +++ b/mrbgems/mruby-env/mrblib/env.rb @@ -0,0 +1,153 @@ +class << ENV + def each(&block) + return to_enum(:each) unless block + keys.each do |k| + v = self[k] + block.call([k, v]) if v + end + self + end + alias each_pair each + + def each_key(&block) + return to_enum(:each_key) unless block + keys.each(&block) + self + end + + def each_value(&block) + return to_enum(:each_value) unless block + __values.each(&block) + self + end + + def values + __values + end + + def delete(key, &block) + val = __delete(key) + if val.nil? && block + block.call(key) + else + val + end + end + + def clear + __clear + self + end + + def fetch(key, *args, &block) + val = self[key] + return val unless val.nil? + if block + block.call(key) + elsif args.length > 0 + args[0] + else + raise KeyError, "key not found: \"#{key}\"" + end + end + + def store(key, val) + self[key] = val + end + + def to_h(&block) + h = {} + each do |k, v| + if block + k, v = block.call(k, v) + end + h[k] = v + end + h + end + + def to_a + each_pair.to_a + end + + def inspect + to_h.inspect + end + + def empty? + size == 0 + end + + def has_value?(val) + __values.include?(val) + end + alias value? has_value? + + def assoc(key) + val = self[key] + val.nil? ? nil : [key, val] + end + + def rassoc(val) + each do |k, v| + return [k, v] if v == val + end + nil + end + + def key(val) + each do |k, v| + return k if v == val + end + nil + end + + def replace(hash) + clear + hash.each do |k, v| + self[k] = v + end + self + end + + def update(hash, &block) + hash.each do |k, v| + if block && key?(k) + self[k] = block.call(k, self[k], v) + else + self[k] = v + end + end + self + end + alias merge! update + + def select(&block) + return to_enum(:select) unless block + to_h.select(&block) + end + alias filter select + + def reject(&block) + return to_enum(:reject) unless block + to_h.reject(&block) + end + + def slice(*keys) + h = {} + keys.each do |k| + v = self[k] + h[k] = v unless v.nil? + end + h + end + + alias include? key? + alias has_key? key? + alias member? key? + alias length size + + def freeze + raise TypeError, "cannot freeze ENV" + end +end diff --git a/mrbgems/mruby-env/src/env.c b/mrbgems/mruby-env/src/env.c new file mode 100644 index 0000000000..7a621b466a --- /dev/null +++ b/mrbgems/mruby-env/src/env.c @@ -0,0 +1,223 @@ +/* +** env.c - ENV object for environment variable access +*/ + +#include +#include +#include +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#define environ _environ +#else +#ifdef __APPLE__ +#include +#define environ (*_NSGetEnviron()) +#else +extern char **environ; +#endif +#endif + +static void +env_check_key(mrb_state *mrb, mrb_value key) +{ + mrb_ensure_string_type(mrb, key); + if (memchr(RSTRING_PTR(key), '=', RSTRING_LEN(key))) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "bad environment variable name: contains '='"); + } + if (memchr(RSTRING_PTR(key), '\0', RSTRING_LEN(key))) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "bad environment variable name: contains null byte"); + } +} + +/* ENV[] */ +static mrb_value +mrb_env_aref(mrb_state *mrb, mrb_value self) +{ + mrb_value key; + const char *val; + + mrb_get_args(mrb, "o", &key); + env_check_key(mrb, key); + val = getenv(RSTRING_PTR(key)); + if (val == NULL) return mrb_nil_value(); + return mrb_str_new_cstr(mrb, val); +} + +/* ENV[]= */ +static mrb_value +mrb_env_aset(mrb_state *mrb, mrb_value self) +{ + mrb_value key, val; + + mrb_get_args(mrb, "oo", &key, &val); + env_check_key(mrb, key); + + if (mrb_nil_p(val)) { +#ifdef _WIN32 + _putenv_s(RSTRING_PTR(key), ""); +#else + unsetenv(RSTRING_PTR(key)); +#endif + return mrb_nil_value(); + } + + mrb_ensure_string_type(mrb, val); + if (memchr(RSTRING_PTR(val), '\0', RSTRING_LEN(val))) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "bad environment variable value: contains null byte"); + } +#ifdef _WIN32 + _putenv_s(RSTRING_PTR(key), RSTRING_PTR(val)); +#else + setenv(RSTRING_PTR(key), RSTRING_PTR(val), 1); +#endif + return val; +} + +/* ENV.__delete(key) - returns old value or nil */ +static mrb_value +mrb_env_delete(mrb_state *mrb, mrb_value self) +{ + mrb_value key; + const char *val; + mrb_value result; + + mrb_get_args(mrb, "o", &key); + env_check_key(mrb, key); + val = getenv(RSTRING_PTR(key)); + if (val == NULL) return mrb_nil_value(); + result = mrb_str_new_cstr(mrb, val); +#ifdef _WIN32 + _putenv_s(RSTRING_PTR(key), ""); +#else + unsetenv(RSTRING_PTR(key)); +#endif + return result; +} + +/* ENV.keys */ +static mrb_value +mrb_env_keys(mrb_state *mrb, mrb_value self) +{ + mrb_value ary = mrb_ary_new(mrb); + char **env; + int ai = mrb_gc_arena_save(mrb); + + for (env = environ; *env != NULL; env++) { + char *eq = strchr(*env, '='); + if (eq) { + mrb_ary_push(mrb, ary, mrb_str_new(mrb, *env, (mrb_int)(eq - *env))); + mrb_gc_arena_restore(mrb, ai); + } + } + return ary; +} + +/* ENV.__values */ +static mrb_value +mrb_env_values(mrb_state *mrb, mrb_value self) +{ + mrb_value ary = mrb_ary_new(mrb); + char **env; + int ai = mrb_gc_arena_save(mrb); + + for (env = environ; *env != NULL; env++) { + char *eq = strchr(*env, '='); + if (eq) { + mrb_ary_push(mrb, ary, mrb_str_new_cstr(mrb, eq + 1)); + mrb_gc_arena_restore(mrb, ai); + } + } + return ary; +} + +/* ENV.size */ +static mrb_value +mrb_env_size(mrb_state *mrb, mrb_value self) +{ + mrb_int count = 0; + char **env; + + for (env = environ; *env != NULL; env++) { + count++; + } + return mrb_fixnum_value(count); +} + +/* ENV.key? */ +static mrb_value +mrb_env_has_key(mrb_state *mrb, mrb_value self) +{ + mrb_value key; + + mrb_get_args(mrb, "o", &key); + env_check_key(mrb, key); + return mrb_bool_value(getenv(RSTRING_PTR(key)) != NULL); +} + +/* ENV.to_s */ +static mrb_value +mrb_env_to_s(mrb_state *mrb, mrb_value self) +{ + return mrb_str_new_lit(mrb, "ENV"); +} + +/* ENV.__clear */ +static mrb_value +mrb_env_clear(mrb_state *mrb, mrb_value self) +{ + while (*environ) { + char *eq = strchr(*environ, '='); + if (eq) { + mrb_int len = (mrb_int)(eq - *environ); + char *key = (char*)mrb_malloc(mrb, len + 1); + memcpy(key, *environ, len); + key[len] = '\0'; +#ifdef _WIN32 + _putenv_s(key, ""); +#else + unsetenv(key); +#endif + mrb_free(mrb, key); + } + else { + break; + } + } + return self; +} + +void +mrb_mruby_env_gem_init(mrb_state *mrb) +{ + mrb_value env = mrb_obj_new(mrb, mrb->object_class, 0, NULL); + struct RObject *eobj = mrb_obj_ptr(env); + + mrb_define_global_const(mrb, "ENV", env); + + mrb_define_singleton_method_id(mrb, eobj, MRB_OPSYM(aref), mrb_env_aref, MRB_ARGS_REQ(1)); + mrb_define_singleton_method_id(mrb, eobj, MRB_OPSYM(aset), mrb_env_aset, MRB_ARGS_REQ(2)); + mrb_define_singleton_method_id(mrb, eobj, MRB_SYM(__delete), mrb_env_delete, MRB_ARGS_REQ(1)); + mrb_define_singleton_method_id(mrb, eobj, MRB_SYM(keys), mrb_env_keys, MRB_ARGS_NONE()); + mrb_define_singleton_method_id(mrb, eobj, MRB_SYM(__values), mrb_env_values, MRB_ARGS_NONE()); + mrb_define_singleton_method_id(mrb, eobj, MRB_SYM(size), mrb_env_size, MRB_ARGS_NONE()); + mrb_define_singleton_method_id(mrb, eobj, MRB_SYM_Q(key), mrb_env_has_key, MRB_ARGS_REQ(1)); + mrb_define_singleton_method_id(mrb, eobj, MRB_SYM(to_s), mrb_env_to_s, MRB_ARGS_NONE()); + mrb_define_singleton_method_id(mrb, eobj, MRB_SYM(__clear), mrb_env_clear, MRB_ARGS_NONE()); + + /* include Enumerable in ENV's singleton class */ + { + struct RClass *sc = mrb_singleton_class_ptr(mrb, env); + mrb_include_module(mrb, sc, mrb_module_get_id(mrb, MRB_SYM(Enumerable))); + } +} + +void +mrb_mruby_env_gem_final(mrb_state *mrb) +{ +} diff --git a/mrbgems/mruby-env/test/env.rb b/mrbgems/mruby-env/test/env.rb new file mode 100644 index 0000000000..3acdddad69 --- /dev/null +++ b/mrbgems/mruby-env/test/env.rb @@ -0,0 +1,243 @@ +## +# ENV test + +assert('ENV class') do + assert_equal Object, ENV.class +end + +assert('ENV.to_s') do + assert_equal "ENV", ENV.to_s +end + +assert('ENV[] and ENV[]=') do + ENV["MRUBY_ENV_TEST_A"] = "hello" + assert_equal "hello", ENV["MRUBY_ENV_TEST_A"] + ENV["MRUBY_ENV_TEST_A"] = nil + assert_nil ENV["MRUBY_ENV_TEST_A"] +end + +assert('ENV[] returns nil for missing key') do + assert_nil ENV["MRUBY_ENV_TEST_NONEXISTENT"] +end + +assert('ENV.store') do + ENV.store("MRUBY_ENV_TEST_B", "world") + assert_equal "world", ENV["MRUBY_ENV_TEST_B"] + ENV.delete("MRUBY_ENV_TEST_B") +end + +assert('ENV.delete') do + ENV["MRUBY_ENV_TEST_C"] = "val" + assert_equal "val", ENV.delete("MRUBY_ENV_TEST_C") + assert_nil ENV["MRUBY_ENV_TEST_C"] +end + +assert('ENV.delete returns nil for missing key') do + assert_nil ENV.delete("MRUBY_ENV_TEST_NONEXISTENT") +end + +assert('ENV.delete with block') do + result = ENV.delete("MRUBY_ENV_TEST_NONEXISTENT") { |k| "missing: #{k}" } + assert_equal "missing: MRUBY_ENV_TEST_NONEXISTENT", result +end + +assert('ENV.key?') do + ENV["MRUBY_ENV_TEST_D"] = "yes" + assert_true ENV.key?("MRUBY_ENV_TEST_D") + assert_false ENV.key?("MRUBY_ENV_TEST_NONEXISTENT") + ENV.delete("MRUBY_ENV_TEST_D") +end + +assert('ENV.include?/has_key?/member?') do + ENV["MRUBY_ENV_TEST_E"] = "val" + assert_true ENV.include?("MRUBY_ENV_TEST_E") + assert_true ENV.has_key?("MRUBY_ENV_TEST_E") + assert_true ENV.member?("MRUBY_ENV_TEST_E") + ENV.delete("MRUBY_ENV_TEST_E") +end + +assert('ENV.keys') do + ENV["MRUBY_ENV_TEST_F"] = "val" + k = ENV.keys + assert_kind_of Array, k + assert_true k.include?("MRUBY_ENV_TEST_F") + ENV.delete("MRUBY_ENV_TEST_F") +end + +assert('ENV.values') do + ENV["MRUBY_ENV_TEST_G"] = "unique_val_42" + v = ENV.values + assert_kind_of Array, v + assert_true v.include?("unique_val_42") + ENV.delete("MRUBY_ENV_TEST_G") +end + +assert('ENV.size/length') do + s1 = ENV.size + ENV["MRUBY_ENV_TEST_H"] = "val" + assert_equal s1 + 1, ENV.size + assert_equal ENV.size, ENV.length + ENV.delete("MRUBY_ENV_TEST_H") +end + +assert('ENV.empty?') do + assert_equal [true, false].include?(ENV.empty?), true +end + +assert('ENV.has_value?/value?') do + ENV["MRUBY_ENV_TEST_I"] = "unique_find_me" + assert_true ENV.has_value?("unique_find_me") + assert_true ENV.value?("unique_find_me") + assert_false ENV.has_value?("MRUBY_ENV_NEVER_EXISTS_VALUE") + ENV.delete("MRUBY_ENV_TEST_I") +end + +assert('ENV.each/each_pair') do + ENV["MRUBY_ENV_TEST_J"] = "iter_val" + found = false + ENV.each do |k, v| + if k == "MRUBY_ENV_TEST_J" && v == "iter_val" + found = true + end + end + assert_true found + ENV.delete("MRUBY_ENV_TEST_J") +end + +assert('ENV.each_key') do + ENV["MRUBY_ENV_TEST_K"] = "val" + found = false + ENV.each_key { |k| found = true if k == "MRUBY_ENV_TEST_K" } + assert_true found + ENV.delete("MRUBY_ENV_TEST_K") +end + +assert('ENV.each_value') do + ENV["MRUBY_ENV_TEST_L"] = "val_each_v" + found = false + ENV.each_value { |v| found = true if v == "val_each_v" } + assert_true found + ENV.delete("MRUBY_ENV_TEST_L") +end + +assert('ENV.fetch') do + ENV["MRUBY_ENV_TEST_M"] = "fetch_val" + assert_equal "fetch_val", ENV.fetch("MRUBY_ENV_TEST_M") + assert_equal "default", ENV.fetch("MRUBY_ENV_TEST_NONE", "default") + assert_equal "block", ENV.fetch("MRUBY_ENV_TEST_NONE") { "block" } + assert_raise(KeyError) { ENV.fetch("MRUBY_ENV_TEST_NONE") } + ENV.delete("MRUBY_ENV_TEST_M") +end + +assert('ENV.to_h') do + ENV["MRUBY_ENV_TEST_N"] = "to_h_val" + h = ENV.to_h + assert_kind_of Hash, h + assert_equal "to_h_val", h["MRUBY_ENV_TEST_N"] + ENV.delete("MRUBY_ENV_TEST_N") +end + +assert('ENV.to_a') do + ENV["MRUBY_ENV_TEST_O"] = "to_a_val" + a = ENV.to_a + assert_kind_of Array, a + assert_true a.include?(["MRUBY_ENV_TEST_O", "to_a_val"]) + ENV.delete("MRUBY_ENV_TEST_O") +end + +assert('ENV.assoc') do + ENV["MRUBY_ENV_TEST_P"] = "assoc_val" + assert_equal ["MRUBY_ENV_TEST_P", "assoc_val"], ENV.assoc("MRUBY_ENV_TEST_P") + assert_nil ENV.assoc("MRUBY_ENV_TEST_NONEXISTENT") + ENV.delete("MRUBY_ENV_TEST_P") +end + +assert('ENV.rassoc') do + ENV["MRUBY_ENV_TEST_Q"] = "rassoc_unique_val" + result = ENV.rassoc("rassoc_unique_val") + assert_equal "MRUBY_ENV_TEST_Q", result[0] + assert_equal "rassoc_unique_val", result[1] + assert_nil ENV.rassoc("MRUBY_ENV_NEVER_EXISTS_VALUE") + ENV.delete("MRUBY_ENV_TEST_Q") +end + +assert('ENV.key') do + ENV["MRUBY_ENV_TEST_R"] = "key_search_val" + assert_equal "MRUBY_ENV_TEST_R", ENV.key("key_search_val") + assert_nil ENV.key("MRUBY_ENV_NEVER_EXISTS_VALUE") + ENV.delete("MRUBY_ENV_TEST_R") +end + +assert('ENV.inspect') do + s = ENV.inspect + assert_kind_of String, s +end + +assert('ENV.select/filter') do + ENV["MRUBY_ENV_TEST_S"] = "sel_val" + result = ENV.select { |k, v| k == "MRUBY_ENV_TEST_S" } + assert_kind_of Hash, result + assert_equal "sel_val", result["MRUBY_ENV_TEST_S"] + ENV.delete("MRUBY_ENV_TEST_S") +end + +assert('ENV.reject') do + ENV["MRUBY_ENV_TEST_T"] = "rej_val" + result = ENV.reject { |k, v| k != "MRUBY_ENV_TEST_T" } + assert_equal({"MRUBY_ENV_TEST_T" => "rej_val"}, result) + ENV.delete("MRUBY_ENV_TEST_T") +end + +assert('ENV.replace') do + ENV["MRUBY_ENV_TEST_U1"] = "old1" + ENV.replace({"MRUBY_ENV_TEST_U2" => "new2"}) + assert_nil ENV["MRUBY_ENV_TEST_U1"] + assert_equal "new2", ENV["MRUBY_ENV_TEST_U2"] + ENV.delete("MRUBY_ENV_TEST_U2") +end + +assert('ENV.update/merge!') do + ENV["MRUBY_ENV_TEST_V"] = "orig" + ENV.update({"MRUBY_ENV_TEST_V" => "updated", "MRUBY_ENV_TEST_W" => "new"}) + assert_equal "updated", ENV["MRUBY_ENV_TEST_V"] + assert_equal "new", ENV["MRUBY_ENV_TEST_W"] + ENV.delete("MRUBY_ENV_TEST_V") + ENV.delete("MRUBY_ENV_TEST_W") +end + +assert('ENV.update with block') do + ENV["MRUBY_ENV_TEST_X"] = "old" + ENV.update({"MRUBY_ENV_TEST_X" => "new"}) { |k, o, n| "#{o}_#{n}" } + assert_equal "old_new", ENV["MRUBY_ENV_TEST_X"] + ENV.delete("MRUBY_ENV_TEST_X") +end + +assert('ENV.slice') do + ENV["MRUBY_ENV_TEST_Y"] = "s1" + ENV["MRUBY_ENV_TEST_Z"] = "s2" + h = ENV.slice("MRUBY_ENV_TEST_Y", "MRUBY_ENV_TEST_Z", "MRUBY_ENV_TEST_NONEXISTENT") + assert_equal({"MRUBY_ENV_TEST_Y" => "s1", "MRUBY_ENV_TEST_Z" => "s2"}, h) + ENV.delete("MRUBY_ENV_TEST_Y") + ENV.delete("MRUBY_ENV_TEST_Z") +end + +assert('ENV.freeze raises TypeError') do + assert_raise(TypeError) { ENV.freeze } +end + +assert('ENV raises TypeError for non-string key') do + assert_raise(TypeError) { ENV[123] } +end + +assert('ENV raises TypeError for non-string value') do + assert_raise(TypeError) { ENV["MRUBY_ENV_TEST_ERR"] = 123 } +end + +assert('ENV.each returns enumerator without block') do + e = ENV.each + assert_kind_of Enumerator, e +end + +assert('ENV is Enumerable') do + assert_true ENV.is_a?(Enumerable) +end diff --git a/mrbgems/mruby-eval/src/eval.c b/mrbgems/mruby-eval/src/eval.c index def92a6917..24fee236f0 100644 --- a/mrbgems/mruby-eval/src/eval.c +++ b/mrbgems/mruby-eval/src/eval.c @@ -323,26 +323,31 @@ f_eval(mrb_state *mrb, mrb_value self) * k.instance_eval("@secret = 5") #=> 5 */ static mrb_value -f_instance_eval(mrb_state *mrb, mrb_value self) +object_eval(mrb_state *mrb, mrb_value self, mrb_bool class_eval) { - if (!mrb_block_given_p(mrb)) { - const char *s; - mrb_int len; - const char *file = NULL; - mrb_int line = 1; - - mrb_get_args(mrb, "s|zi", &s, &len, &file, &line); - struct RClass *c = mrb_singleton_class_ptr(mrb, self); - struct RProc *proc = create_proc_from_string(mrb, s, len, mrb_nil_value(), file, line); - MRB_PROC_SET_TARGET_CLASS(proc, c); - mrb_assert(!MRB_PROC_CFUNC_P(proc)); - mrb_vm_ci_target_class_set(mrb->c->ci, c); - return eval_irep(mrb, self, proc); - } - else { + if (mrb_block_given_p(mrb)) { mrb_get_args(mrb, ""); - return mrb_obj_instance_eval(mrb, self); + return class_eval ? mrb_mod_module_eval(mrb, self) : mrb_obj_instance_eval(mrb, self); } + + const char *s; + mrb_int len; + const char *file = NULL; + mrb_int line = 1; + mrb_get_args(mrb, "s|zi", &s, &len, &file, &line); + + struct RClass *c = class_eval ? mrb_class_ptr(self) : mrb_singleton_class_ptr(mrb, self); + struct RProc *proc = create_proc_from_string(mrb, s, len, mrb_nil_value(), file, line); + MRB_PROC_SET_TARGET_CLASS(proc, c); + mrb_assert(!MRB_PROC_CFUNC_P(proc)); + mrb_vm_ci_target_class_set(mrb->c->ci, c); + return eval_irep(mrb, self, proc); +} + +static mrb_value +f_instance_eval(mrb_state *mrb, mrb_value self) +{ + return object_eval(mrb, self, FALSE); } /* @@ -369,23 +374,7 @@ f_instance_eval(mrb_state *mrb, mrb_value self) static mrb_value f_class_eval(mrb_state *mrb, mrb_value self) { - if (!mrb_block_given_p(mrb)) { - const char *s; - mrb_int len; - const char *file = NULL; - mrb_int line = 1; - - mrb_get_args(mrb, "s|zi", &s, &len, &file, &line); - struct RProc *proc = create_proc_from_string(mrb, s, len, mrb_nil_value(), file, line); - MRB_PROC_SET_TARGET_CLASS(proc, mrb_class_ptr(self)); - mrb_assert(!MRB_PROC_CFUNC_P(proc)); - mrb_vm_ci_target_class_set(mrb->c->ci, mrb_class_ptr(self)); - return eval_irep(mrb, self, proc); - } - else { - mrb_get_args(mrb, ""); - return mrb_mod_module_eval(mrb, self); - } + return object_eval(mrb, self, TRUE); } /* diff --git a/mrbgems/mruby-io/README.md b/mrbgems/mruby-io/README.md index 597aeee98f..390289e29e 100644 --- a/mrbgems/mruby-io/README.md +++ b/mrbgems/mruby-io/README.md @@ -33,8 +33,8 @@ Add the line below to your build configuration. | IO.write | | | | IO#<< | | | | IO#advise | | | -| IO#autoclose= | | | -| IO#autoclose? | | | +| IO#autoclose= | o | | +| IO#autoclose? | o | | | IO#binmode | | | | IO#binmode? | | | | IO#bytes | | obsolete | diff --git a/mrbgems/mruby-io/include/io_hal.h b/mrbgems/mruby-io/include/io_hal.h index 53bde4dead..cce23f3ac3 100644 --- a/mrbgems/mruby-io/include/io_hal.h +++ b/mrbgems/mruby-io/include/io_hal.h @@ -12,7 +12,6 @@ #define MRUBY_IO_HAL_H #include -#include /* * Platform-independent type definitions @@ -20,25 +19,25 @@ /* File status structure - platform-independent representation */ typedef struct mrb_io_stat { - uint64_t st_dev; /* Device ID */ - uint64_t st_ino; /* Inode number */ - uint32_t st_mode; /* File mode/permissions */ - uint32_t st_nlink; /* Number of hard links */ - uint32_t st_uid; /* User ID */ - uint32_t st_gid; /* Group ID */ - uint64_t st_rdev; /* Device ID (if special file) */ - int64_t st_size; /* File size in bytes */ - int64_t st_atime; /* Last access time */ - int64_t st_mtime; /* Last modification time */ - int64_t st_ctime; /* Last status change time */ - int64_t st_blksize; /* Block size for filesystem I/O */ - int64_t st_blocks; /* Number of 512B blocks allocated */ + mrb_int st_dev; /* Device ID */ + mrb_int st_ino; /* Inode number */ + mrb_int st_mode; /* File mode/permissions */ + mrb_int st_nlink; /* Number of hard links */ + mrb_int st_uid; /* User ID */ + mrb_int st_gid; /* Group ID */ + mrb_int st_rdev; /* Device ID (if special file) */ + mrb_int st_size; /* File size in bytes */ + mrb_int st_atime; /* Last access time */ + mrb_int st_mtime; /* Last modification time */ + mrb_int st_ctime; /* Last status change time */ + mrb_int st_blksize; /* Block size for filesystem I/O */ + mrb_int st_blocks; /* Number of 512B blocks allocated */ } mrb_io_stat; /* Timeval structure for select() */ typedef struct mrb_io_timeval { - int64_t tv_sec; /* Seconds */ - int64_t tv_usec; /* Microseconds */ + mrb_int tv_sec; /* Seconds */ + mrb_int tv_usec; /* Microseconds */ } mrb_io_timeval; /* File descriptor set for select() */ @@ -120,7 +119,7 @@ int mrb_hal_io_lstat(mrb_state *mrb, const char *path, mrb_io_stat *st); * @param mode New permission mode * @return 0 on success, -1 on error (sets errno) */ -int mrb_hal_io_chmod(mrb_state *mrb, const char *path, uint32_t mode); +int mrb_hal_io_chmod(mrb_state *mrb, const char *path, mrb_int mode); /** * Set/get file creation mask @@ -129,7 +128,7 @@ int mrb_hal_io_chmod(mrb_state *mrb, const char *path, uint32_t mode); * @param mask New umask value (if < 0, only returns current value) * @return Previous umask value */ -uint32_t mrb_hal_io_umask(mrb_state *mrb, int32_t mask); +mrb_int mrb_hal_io_umask(mrb_state *mrb, mrb_int mask); /** * Truncate file to specified length @@ -139,7 +138,7 @@ uint32_t mrb_hal_io_umask(mrb_state *mrb, int32_t mask); * @param length New file length * @return 0 on success, -1 on error (sets errno) */ -int mrb_hal_io_ftruncate(mrb_state *mrb, int fd, int64_t length); +int mrb_hal_io_ftruncate(mrb_state *mrb, int fd, mrb_int length); /** * Apply or remove advisory lock on file @@ -189,7 +188,7 @@ int mrb_hal_io_symlink(mrb_state *mrb, const char *target, const char *linkpath) * @param bufsize Buffer size * @return Number of bytes placed in buf, -1 on error (sets errno) */ -int64_t mrb_hal_io_readlink(mrb_state *mrb, const char *path, char *buf, size_t bufsize); +mrb_int mrb_hal_io_readlink(mrb_state *mrb, const char *path, char *buf, size_t bufsize); /** * Resolve pathname to absolute path @@ -242,7 +241,7 @@ const char* mrb_hal_io_gethome(mrb_state *mrb, const char *username); * @param mode Creation mode (used if O_CREAT is set) * @return File descriptor on success, -1 on error (sets errno) */ -int mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, uint32_t mode); +int mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, mrb_int mode); /** * Close file descriptor @@ -262,7 +261,7 @@ int mrb_hal_io_close(mrb_state *mrb, int fd); * @param count Maximum bytes to read * @return Number of bytes read, 0 on EOF, -1 on error (sets errno) */ -int64_t mrb_hal_io_read(mrb_state *mrb, int fd, void *buf, size_t count); +mrb_int mrb_hal_io_read(mrb_state *mrb, int fd, void *buf, size_t count); /** * Write to file descriptor @@ -273,7 +272,7 @@ int64_t mrb_hal_io_read(mrb_state *mrb, int fd, void *buf, size_t count); * @param count Number of bytes to write * @return Number of bytes written, -1 on error (sets errno) */ -int64_t mrb_hal_io_write(mrb_state *mrb, int fd, const void *buf, size_t count); +mrb_int mrb_hal_io_write(mrb_state *mrb, int fd, const void *buf, size_t count); /** * Reposition file offset @@ -284,7 +283,7 @@ int64_t mrb_hal_io_write(mrb_state *mrb, int fd, const void *buf, size_t count); * @param whence Reference point (MRB_IO_SEEK_SET/CUR/END) * @return New offset from beginning of file, -1 on error (sets errno) */ -int64_t mrb_hal_io_lseek(mrb_state *mrb, int fd, int64_t offset, int whence); +mrb_int mrb_hal_io_lseek(mrb_state *mrb, int fd, mrb_int offset, int whence); /** * Duplicate file descriptor diff --git a/mrbgems/mruby-io/mrbgem.rake b/mrbgems/mruby-io/mrbgem.rake index 6663666dfe..b9118713b7 100644 --- a/mrbgems/mruby-io/mrbgem.rake +++ b/mrbgems/mruby-io/mrbgem.rake @@ -6,29 +6,7 @@ MRuby::Gem::Specification.new('mruby-io') do |spec| spec.build.defines << "HAVE_MRUBY_IO_GEM" spec.add_test_dependency 'mruby-time', core: 'mruby-time' - # Check if HAL gem is loaded - # HAL gems must be explicitly specified in build config (recommended) or via auto-selection below - spec.build.gems.one? { |g| g.name =~ /^hal-.*-io$/ } or begin - # No HAL found - determine appropriate error message or auto-load - suggested_hal = if spec.for_windows? - 'hal-win-io' - elsif RUBY_PLATFORM =~ /linux|darwin|bsd/ - 'hal-posix-io' - else - nil - end - - if suggested_hal - # Auto-load HAL gem for convenience (for development) - # This works because HAL gems declare dependency on mruby-io - warn "mruby-io: No HAL specified, loading #{suggested_hal} (explicit selection recommended)" - spec.build.gem core: suggested_hal - else - # Unknown platform - fail with helpful message - fail "mruby-io: No HAL available for platform '#{RUBY_PLATFORM}'.\n" \ - "Please specify HAL gem explicitly in your build config:\n" \ - " conf.gem core: 'hal-posix-io' # For Linux/macOS/BSD\n" \ - " conf.gem core: 'hal-win-io' # For Windows" - end + if spec.for_windows? + spec.linker.libraries << "ws2_32" end end diff --git a/mrbgems/hal-posix-io/src/io_hal.c b/mrbgems/mruby-io/ports/posix/io_hal.c similarity index 86% rename from mrbgems/hal-posix-io/src/io_hal.c rename to mrbgems/mruby-io/ports/posix/io_hal.c index 2283fdd7ed..254bc6c6c6 100644 --- a/mrbgems/hal-posix-io/src/io_hal.c +++ b/mrbgems/mruby-io/ports/posix/io_hal.c @@ -67,25 +67,25 @@ convert_stat(const struct stat *src, mrb_io_stat *dst) #undef st_mtime #undef st_ctime - dst->st_dev = (uint64_t)src->st_dev; - dst->st_ino = (uint64_t)src->st_ino; - dst->st_mode = (uint32_t)src->st_mode; - dst->st_nlink = (uint32_t)src->st_nlink; - dst->st_uid = (uint32_t)src->st_uid; - dst->st_gid = (uint32_t)src->st_gid; - dst->st_rdev = (uint64_t)src->st_rdev; - dst->st_size = (int64_t)src->st_size; - dst->st_atime = (int64_t)atime_val; - dst->st_mtime = (int64_t)mtime_val; - dst->st_ctime = (int64_t)ctime_val; + dst->st_dev = (mrb_int)src->st_dev; + dst->st_ino = (mrb_int)src->st_ino; + dst->st_mode = (mrb_int)src->st_mode; + dst->st_nlink = (mrb_int)src->st_nlink; + dst->st_uid = (mrb_int)src->st_uid; + dst->st_gid = (mrb_int)src->st_gid; + dst->st_rdev = (mrb_int)src->st_rdev; + dst->st_size = (mrb_int)src->st_size; + dst->st_atime = (mrb_int)atime_val; + dst->st_mtime = (mrb_int)mtime_val; + dst->st_ctime = (mrb_int)ctime_val; #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE - dst->st_blksize = (int64_t)src->st_blksize; + dst->st_blksize = (mrb_int)src->st_blksize; #else dst->st_blksize = 512; #endif #ifdef HAVE_STRUCT_STAT_ST_BLOCKS - dst->st_blocks = (int64_t)src->st_blocks; + dst->st_blocks = (mrb_int)src->st_blocks; #else dst->st_blocks = (dst->st_size + 511) / 512; #endif @@ -135,14 +135,14 @@ mrb_hal_io_lstat(mrb_state *mrb, const char *path, mrb_io_stat *st) } int -mrb_hal_io_chmod(mrb_state *mrb, const char *path, uint32_t mode) +mrb_hal_io_chmod(mrb_state *mrb, const char *path, mrb_int mode) { (void)mrb; return chmod(path, (mode_t)mode); } -uint32_t -mrb_hal_io_umask(mrb_state *mrb, int32_t mask) +mrb_int +mrb_hal_io_umask(mrb_state *mrb, mrb_int mask) { mode_t old; (void)mrb; @@ -155,11 +155,11 @@ mrb_hal_io_umask(mrb_state *mrb, int32_t mask) else { old = umask((mode_t)mask); } - return (uint32_t)old; + return (mrb_int)old; } int -mrb_hal_io_ftruncate(mrb_state *mrb, int fd, int64_t length) +mrb_hal_io_ftruncate(mrb_state *mrb, int fd, mrb_int length) { (void)mrb; return ftruncate(fd, (off_t)length); @@ -200,14 +200,11 @@ mrb_hal_io_symlink(mrb_state *mrb, const char *target, const char *linkpath) return symlink(target, linkpath); } -int64_t +mrb_int mrb_hal_io_readlink(mrb_state *mrb, const char *path, char *buf, size_t bufsize) { - ssize_t rc; (void)mrb; - - rc = readlink(path, buf, bufsize); - return (int64_t)rc; + return (mrb_int)readlink(path, buf, bufsize); } char* @@ -262,7 +259,7 @@ mrb_hal_io_gethome(mrb_state *mrb, const char *username) */ int -mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, uint32_t mode) +mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, mrb_int mode) { int fd; (void)mrb; @@ -292,30 +289,23 @@ mrb_hal_io_close(mrb_state *mrb, int fd) return close(fd); } -int64_t +mrb_int mrb_hal_io_read(mrb_state *mrb, int fd, void *buf, size_t count) { - ssize_t n; (void)mrb; - - n = read(fd, buf, count); - return (int64_t)n; + return (mrb_int)read(fd, buf, count); } -int64_t +mrb_int mrb_hal_io_write(mrb_state *mrb, int fd, const void *buf, size_t count) { - ssize_t n; (void)mrb; - - n = write(fd, buf, count); - return (int64_t)n; + return (mrb_int)write(fd, buf, count); } -int64_t -mrb_hal_io_lseek(mrb_state *mrb, int fd, int64_t offset, int whence) +mrb_int +mrb_hal_io_lseek(mrb_state *mrb, int fd, mrb_int offset, int whence) { - off_t pos; int posix_whence; (void)mrb; @@ -329,8 +319,7 @@ mrb_hal_io_lseek(mrb_state *mrb, int fd, int64_t offset, int whence) return -1; } - pos = lseek(fd, (off_t)offset, posix_whence); - return (int64_t)pos; + return (mrb_int)lseek(fd, (off_t)offset, posix_whence); } int @@ -582,21 +571,3 @@ mrb_hal_io_final(mrb_state *mrb) (void)mrb; /* No special cleanup needed for POSIX */ } - -/* - * Gem initialization - */ - -void -mrb_hal_posix_io_gem_init(mrb_state *mrb) -{ - (void)mrb; - /* HAL interface functions are called by mruby-io gem */ -} - -void -mrb_hal_posix_io_gem_final(mrb_state *mrb) -{ - (void)mrb; - /* Cleanup handled by mrb_hal_io_final called from mruby-io */ -} diff --git a/mrbgems/hal-win-io/src/io_hal.c b/mrbgems/mruby-io/ports/win/io_hal.c similarity index 89% rename from mrbgems/hal-win-io/src/io_hal.c rename to mrbgems/mruby-io/ports/win/io_hal.c index f3429b62a7..3d60673952 100644 --- a/mrbgems/hal-win-io/src/io_hal.c +++ b/mrbgems/mruby-io/ports/win/io_hal.c @@ -34,17 +34,17 @@ static void convert_stat(const struct _stat64 *src, mrb_io_stat *dst) { - dst->st_dev = (uint64_t)src->st_dev; - dst->st_ino = (uint64_t)src->st_ino; - dst->st_mode = (uint32_t)src->st_mode; - dst->st_nlink = (uint32_t)src->st_nlink; + dst->st_dev = (mrb_int)src->st_dev; + dst->st_ino = (mrb_int)src->st_ino; + dst->st_mode = (mrb_int)src->st_mode; + dst->st_nlink = (mrb_int)src->st_nlink; dst->st_uid = 0; /* Windows doesn't have Unix-style UIDs */ dst->st_gid = 0; /* Windows doesn't have Unix-style GIDs */ - dst->st_rdev = (uint64_t)src->st_rdev; - dst->st_size = (int64_t)src->st_size; - dst->st_atime = (int64_t)src->st_atime; - dst->st_mtime = (int64_t)src->st_mtime; - dst->st_ctime = (int64_t)src->st_ctime; + dst->st_rdev = (mrb_int)src->st_rdev; + dst->st_size = (mrb_int)src->st_size; + dst->st_atime = (mrb_int)src->st_atime; + dst->st_mtime = (mrb_int)src->st_mtime; + dst->st_ctime = (mrb_int)src->st_ctime; dst->st_blksize = 512; dst->st_blocks = (dst->st_size + 511) / 512; } @@ -114,14 +114,14 @@ mrb_hal_io_lstat(mrb_state *mrb, const char *path, mrb_io_stat *st) } int -mrb_hal_io_chmod(mrb_state *mrb, const char *path, uint32_t mode) +mrb_hal_io_chmod(mrb_state *mrb, const char *path, mrb_int mode) { (void)mrb; return _chmod(path, (int)mode); } -uint32_t -mrb_hal_io_umask(mrb_state *mrb, int32_t mask) +mrb_int +mrb_hal_io_umask(mrb_state *mrb, mrb_int mask) { int old; (void)mrb; @@ -134,14 +134,14 @@ mrb_hal_io_umask(mrb_state *mrb, int32_t mask) else { old = _umask((int)mask); } - return (uint32_t)old; + return (mrb_int)old; } int -mrb_hal_io_ftruncate(mrb_state *mrb, int fd, int64_t length) +mrb_hal_io_ftruncate(mrb_state *mrb, int fd, mrb_int length) { (void)mrb; - return _chsize_s(fd, length); + return _chsize_s(fd, (__int64)length); } int @@ -207,7 +207,7 @@ mrb_hal_io_symlink(mrb_state *mrb, const char *target, const char *linkpath) return -1; /* not reached */ } -int64_t +mrb_int mrb_hal_io_readlink(mrb_state *mrb, const char *path, char *buf, size_t bufsize) { (void)path; @@ -280,7 +280,7 @@ mrb_hal_io_gethome(mrb_state *mrb, const char *username) */ int -mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, uint32_t mode) +mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, mrb_int mode) { int fd; (void)mrb; @@ -306,30 +306,23 @@ mrb_hal_io_close(mrb_state *mrb, int fd) return _close(fd); } -int64_t +mrb_int mrb_hal_io_read(mrb_state *mrb, int fd, void *buf, size_t count) { - int n; (void)mrb; - - n = _read(fd, buf, (unsigned int)count); - return (int64_t)n; + return (mrb_int)_read(fd, buf, (unsigned int)count); } -int64_t +mrb_int mrb_hal_io_write(mrb_state *mrb, int fd, const void *buf, size_t count) { - int n; (void)mrb; - - n = _write(fd, buf, (unsigned int)count); - return (int64_t)n; + return (mrb_int)_write(fd, buf, (unsigned int)count); } -int64_t -mrb_hal_io_lseek(mrb_state *mrb, int fd, int64_t offset, int whence) +mrb_int +mrb_hal_io_lseek(mrb_state *mrb, int fd, mrb_int offset, int whence) { - __int64 pos; int win_whence; (void)mrb; @@ -343,8 +336,7 @@ mrb_hal_io_lseek(mrb_state *mrb, int fd, int64_t offset, int whence) return -1; } - pos = _lseeki64(fd, (__int64)offset, win_whence); - return (int64_t)pos; + return (mrb_int)_lseeki64(fd, (__int64)offset, win_whence); } int @@ -626,21 +618,3 @@ mrb_hal_io_final(mrb_state *mrb) /* Cleanup Winsock */ WSACleanup(); } - -/* - * Gem initialization - */ - -void -mrb_hal_win_io_gem_init(mrb_state *mrb) -{ - (void)mrb; - /* HAL interface functions are called by mruby-io gem */ -} - -void -mrb_hal_win_io_gem_final(mrb_state *mrb) -{ - (void)mrb; - /* Cleanup handled by mrb_hal_io_final called from mruby-io */ -} diff --git a/mrbgems/mruby-io/src/file.c b/mrbgems/mruby-io/src/file.c index 096578b210..d5f7816b34 100644 --- a/mrbgems/mruby-io/src/file.c +++ b/mrbgems/mruby-io/src/file.c @@ -114,14 +114,13 @@ static mrb_value mrb_file_s_umask(mrb_state *mrb, mrb_value klass) { - mrb_int mask; - uint32_t omask; + mrb_int mask, omask; if (mrb_get_args(mrb, "|i", &mask) == 0) { omask = mrb_hal_io_umask(mrb, -1); } else { - omask = mrb_hal_io_umask(mrb, (int32_t)mask); + omask = mrb_hal_io_umask(mrb, mask); } return mrb_fixnum_value(omask); } @@ -281,85 +280,58 @@ mrb_file_dirname(mrb_state *mrb, mrb_value klass) static mrb_value mrb_file_basename(mrb_state *mrb, mrb_value klass) { -#if defined(_WIN32) - char bname[_MAX_DIR]; - char extname[_MAX_EXT]; - char *path; - const char *suffix = NULL; - - mrb_get_args(mrb, "z|z", &path, &suffix); - size_t ridx = strlen(path); - if (ridx > 0) { - ridx--; - while (ridx > 0 && (path[ridx] == '/' || path[ridx] == '\\')) { - path[ridx] = '\0'; - ridx--; - } - if (ridx == 0 && path[0] == '/') { - mrb_value result = mrb_str_new_cstr(mrb, path); - if (suffix && *suffix) { - mrb_int blen = RSTRING_LEN(result); - mrb_int slen = strlen(suffix); - if (blen > slen && memcmp(RSTRING_PTR(result) + blen - slen, suffix, slen) == 0) { - mrb_str_resize(mrb, result, blen - slen); - } - } - return result; - } - } - _splitpath((const char*)path, NULL, NULL, bname, extname); - mrb_value buffer = mrb_str_new_cstr(mrb, bname); - mrb_str_cat_cstr(mrb, buffer, extname); - if (suffix && *suffix) { - mrb_int blen = RSTRING_LEN(buffer); - mrb_int slen = strlen(suffix); - if (blen > slen && memcmp(RSTRING_PTR(buffer) + blen - slen, suffix, slen) == 0) { - mrb_str_resize(mrb, buffer, blen - slen); - } - } - return buffer; -#else - char *path; + const char *path; const char *suffix = NULL; mrb_get_args(mrb, "z|z", &path, &suffix); - // Copy path to a local buffer to avoid modifying the original string - size_t len = strlen(path); - if (len == 0) { + const char *endp = path + strlen(path); + if (path == endp) { return mrb_str_new_lit(mrb, "."); } +#ifdef _WIN32 + if (UNC_PATH_P(path)) { + path += 2; + SKIP_DIRSEP(path); + NEXT_DIRSEP(path); // skip server name + SKIP_DIRSEP(path); + NEXT_DIRSEP(path); // skip share name + } + else if (DRIVE_LETTER_P(path)) { + path += 2; + if (path == endp) { + return mrb_str_new_lit(mrb, ""); + } + } +#endif // _WIN32 + // Remove trailing slashes (except when path is only "/") - while (len > 1 && path[len - 1] == '/') { - len--; + while (path < endp && DIRSEP_P(endp[-1])) { + endp--; } // Find the last path separator - ssize_t base = len - 1; - while (base >= 0 && path[base] != '/') { + const char *base = endp; + while (path < base && !DIRSEP_P(base[-1])) { base--; } - base++; // move to the first character after '/' // If path is all slashes, return "/" - if ((size_t)base == len) { + if (base == endp) { return mrb_str_new_lit(mrb, "/"); } - mrb_value result = mrb_str_new(mrb, path + base, len - base); - // Suffix removal (CRuby compatible) if (suffix && *suffix) { - mrb_int blen = RSTRING_LEN(result); + mrb_int blen = endp - base; mrb_int slen = strlen(suffix); - if (blen > slen && memcmp(RSTRING_PTR(result) + blen - slen, suffix, slen) == 0) { - mrb_str_resize(mrb, result, blen - slen); + if (blen > slen && memcmp(endp - slen, suffix, slen) == 0) { + endp -= slen; } } - return result; -#endif + return mrb_str_new(mrb, base, endp - base); } /* @@ -658,18 +630,6 @@ mrb_file_absolute_path_p(mrb_state *mrb, mrb_value klass) return mrb_bool_value(path_absolute_p(RSTRING_CSTR(mrb, path))); } -#define TIME_OVERFLOW_P(a) (sizeof(time_t) >= sizeof(mrb_int) && ((a) > MRB_INT_MAX || (a) < MRB_INT_MIN)) -#define TIME_T_UINT (~(time_t)0 > 0) -#if defined(MRB_USE_BITINT) -#define TIME_BIGTIME(mrb, a) \ - return (TIME_T_UINT ? mrb_bint_new_uint64((mrb), (uint64_t)(a)) \ - : mrb_bint_new_int64(mrb, (int64_t)(a))) -#elif !defined(MRB_NO_FLOAT) -#define TIME_BIGTIME(mrb,a) return mrb_float_value((mrb), (mrb_float)(a)) -#else -#define TIME_BIGTIME(mrb, a) mrb_raise(mrb, E_IO_ERROR, #a " overflow") -#endif - static mrb_value mrb_file_atime(mrb_state *mrb, mrb_value self) { @@ -679,10 +639,7 @@ mrb_file_atime(mrb_state *mrb, mrb_value self) mrb->c->ci->mid = 0; if (mrb_hal_io_fstat(mrb, fd, &st) == -1) mrb_sys_fail(mrb, "atime"); - if (TIME_OVERFLOW_P(st.st_atime)) { - TIME_BIGTIME(mrb, st.st_atime); - } - return mrb_int_value(mrb, (mrb_int)st.st_atime); + return mrb_int_value(mrb, st.st_atime); } static mrb_value @@ -694,10 +651,7 @@ mrb_file_ctime(mrb_state *mrb, mrb_value self) mrb->c->ci->mid = 0; if (mrb_hal_io_fstat(mrb, fd, &st) == -1) mrb_sys_fail(mrb, "ctime"); - if (TIME_OVERFLOW_P(st.st_ctime)) { - TIME_BIGTIME(mrb, st.st_ctime); - } - return mrb_int_value(mrb, (mrb_int)st.st_ctime); + return mrb_int_value(mrb, st.st_ctime); } static mrb_value @@ -709,10 +663,7 @@ mrb_file_mtime(mrb_state *mrb, mrb_value self) mrb->c->ci->mid = 0; if (mrb_hal_io_fstat(mrb, fd, &st) == -1) mrb_sys_fail(mrb, "mtime"); - if (TIME_OVERFLOW_P(st.st_mtime)) { - TIME_BIGTIME(mrb, st.st_mtime); - } - return mrb_int_value(mrb, (mrb_int)st.st_mtime); + return mrb_int_value(mrb, st.st_mtime); } /* @@ -775,22 +726,13 @@ mrb_file_size(mrb_state *mrb, mrb_value self) if (mrb_hal_io_fstat(mrb, fd, &st) == -1) { mrb_sys_fail(mrb, "fstat"); } - - if (sizeof(st.st_size) >= sizeof(mrb_int) && st.st_size > MRB_INT_MAX) { -#ifdef MRB_NO_FLOAT - mrb_raise(mrb, E_RUNTIME_ERROR, "File#size too large for MRB_NO_FLOAT"); -#else - return mrb_float_value(mrb, (mrb_float)st.st_size); -#endif - } - - return mrb_int_value(mrb, (mrb_int)st.st_size); + return mrb_int_value(mrb, st.st_size); } static int mrb_ftruncate(mrb_state *mrb, int fd, mrb_int length) { - return mrb_hal_io_ftruncate(mrb, fd, (int64_t)length); + return mrb_hal_io_ftruncate(mrb, fd, length); } /* @@ -865,7 +807,7 @@ mrb_file_s_chmod(mrb_state *mrb, mrb_value klass) mrb_ensure_string_type(mrb, filenames[i]); const char *utf8_path = RSTRING_CSTR(mrb, filenames[i]); char *path = mrb_locale_from_utf8(utf8_path, -1); - if (mrb_hal_io_chmod(mrb, path, (uint32_t)mode) == -1) { + if (mrb_hal_io_chmod(mrb, path, mode) == -1) { mrb_locale_free(path); mrb_sys_fail(mrb, utf8_path); } @@ -900,7 +842,7 @@ mrb_file_s_readlink(mrb_state *mrb, mrb_value klass) /* Use mrb_temp_alloc for exception safety - GC will clean up on exception */ char *buf = (char*)mrb_temp_alloc(mrb, PATH_MAX); - int64_t rc = mrb_hal_io_readlink(mrb, tmp, buf, PATH_MAX); + mrb_int rc = mrb_hal_io_readlink(mrb, tmp, buf, PATH_MAX); mrb_locale_free(tmp); if (rc == -1) { mrb_sys_fail(mrb, path); diff --git a/mrbgems/mruby-io/src/file_test.c b/mrbgems/mruby-io/src/file_test.c index 00d8b00a4f..e5c824b981 100644 --- a/mrbgems/mruby-io/src/file_test.c +++ b/mrbgems/mruby-io/src/file_test.c @@ -20,23 +20,6 @@ extern struct mrb_data_type mrb_io_type; -/* Helper function to convert int64_t to mrb_value with overflow handling */ -static mrb_value -mrb_int64_value(mrb_state *mrb, int64_t val) -{ - if (sizeof(val) >= sizeof(mrb_int) && val > MRB_INT_MAX) { -#ifdef MRB_USE_BIGINT - return mrb_bint_new_int64(mrb, val); -#elif !defined(MRB_NO_FLOAT) - return mrb_float_value(mrb, (mrb_float)val); -#else - mrb_raise(mrb, E_RANGE_ERROR, "value too large for this platform"); -#endif - } - - return mrb_int_value(mrb, (mrb_int)val); -} - static int mrb_stat0(mrb_state *mrb, mrb_value obj, mrb_io_stat *st, int do_lstat) { @@ -340,7 +323,7 @@ mrb_filetest_s_size(mrb_state *mrb, mrb_value klass) if (mrb_stat(mrb, obj, &st) < 0) mrb_sys_fail(mrb, "mrb_stat"); - return mrb_int64_value(mrb, st.st_size); + return mrb_int_value(mrb, st.st_size); } /* @@ -366,7 +349,7 @@ mrb_filetest_s_size_p(mrb_state *mrb, mrb_value klass) if (st.st_size == 0) return mrb_nil_value(); - return mrb_int64_value(mrb, st.st_size); + return mrb_int_value(mrb, st.st_size); } void diff --git a/mrbgems/mruby-io/src/io.c b/mrbgems/mruby-io/src/io.c index 58b3094a26..87d6f9a46b 100644 --- a/mrbgems/mruby-io/src/io.c +++ b/mrbgems/mruby-io/src/io.c @@ -110,7 +110,7 @@ io_set_process_status(mrb_state *mrb, pid_t pid, int status) } } if (c_status != NULL) { - v = mrb_funcall_id(mrb, mrb_obj_value(c_status), MRB_SYM(new), 2, mrb_fixnum_value(pid), mrb_fixnum_value(status)); + v = mrb_funcall_argv2(mrb, mrb_obj_value(c_status), MRB_SYM(new), mrb_fixnum_value(pid), mrb_fixnum_value(status)); } else { v = mrb_fixnum_value(WEXITSTATUS(status)); @@ -911,18 +911,12 @@ io_syswrite(mrb_state *mrb, mrb_value io) /* end */ static mrb_int -fd_write(mrb_state *mrb, int fd, mrb_value str) +fd_write_buf(mrb_state *mrb, int fd, const char *ptr, mrb_int len) { - fssize_t n; - - str = mrb_obj_as_string(mrb, str); - fssize_t len = (fssize_t)RSTRING_LEN(str); if (len == 0) return 0; - - const char *ptr = RSTRING_PTR(str); fssize_t sum = 0; - while (sum < len) { - n = write(fd, ptr + sum, len - sum); + while (sum < (fssize_t)len) { + fssize_t n = write(fd, ptr + sum, (size_t)(len - sum)); if (n == -1) { if (errno == EINTR) continue; mrb_sys_fail(mrb, "syswrite"); @@ -932,6 +926,15 @@ fd_write(mrb_state *mrb, int fd, mrb_value str) return len; } +static mrb_int +fd_write(mrb_state *mrb, int fd, mrb_value str) +{ + str = mrb_obj_as_string(mrb, str); + return fd_write_buf(mrb, fd, RSTRING_PTR(str), RSTRING_LEN(str)); +} + +#define FD_WRITE_LIT(mrb, fd, s) fd_write_buf(mrb, fd, "" s "", sizeof(s) - 1) + /* Helper function to prepare IO object for writing by adjusting buffer state */ static void io_prepare_write(mrb_state *mrb, struct mrb_io *fptr) @@ -987,28 +990,35 @@ io_puts_str(mrb_state *mrb, int fd, mrb_value str) /* Add newline if string doesn't end with one */ if (len == 0 || ptr[len-1] != '\n') { - mrb_value newline = mrb_str_new_lit(mrb, "\n"); - fd_write(mrb, fd, newline); + FD_WRITE_LIT(mrb, fd, "\n"); } } +/* Maximum nesting depth for puts with arrays; guards against cyclic and + pathologically deep arrays causing C stack overflow. */ +#define IO_PUTS_MAX_DEPTH 16 + /* Recursive helper for puts with arrays */ static void -io_puts_ary(mrb_state *mrb, int fd, mrb_value ary) +io_puts_ary(mrb_state *mrb, int fd, mrb_value ary, int depth) { + if (depth >= IO_PUTS_MAX_DEPTH) { + FD_WRITE_LIT(mrb, fd, "[...]\n"); + return; + } + mrb_int len = RARRAY_LEN(ary); if (len == 0) { /* Empty array - write a single newline */ - mrb_value newline = mrb_str_new_lit(mrb, "\n"); - fd_write(mrb, fd, newline); + FD_WRITE_LIT(mrb, fd, "\n"); return; } for (mrb_int i = 0; i < len; i++) { mrb_value elem = RARRAY_PTR(ary)[i]; if (mrb_array_p(elem)) { - io_puts_ary(mrb, fd, elem); /* Recursive call for nested arrays */ + io_puts_ary(mrb, fd, elem, depth + 1); } else { io_puts_str(mrb, fd, elem); @@ -1031,8 +1041,7 @@ io_puts(mrb_state *mrb, mrb_value io) if (argc == 0) { /* No arguments - just write a newline */ - mrb_value newline = mrb_str_new_lit(mrb, "\n"); - fd_write(mrb, fd, newline); + FD_WRITE_LIT(mrb, fd, "\n"); return mrb_nil_value(); } @@ -1040,7 +1049,7 @@ io_puts(mrb_state *mrb, mrb_value io) for (mrb_int i = 0; i < argc; i++) { mrb_value arg = argv[i]; if (mrb_array_p(arg)) { - io_puts_ary(mrb, fd, arg); + io_puts_ary(mrb, fd, arg, 0); } else { io_puts_str(mrb, fd, arg); @@ -1268,14 +1277,14 @@ time2timeval(mrb_state *mrb, mrb_value time) switch (mrb_type(time)) { case MRB_TT_INTEGER: - t.tv_sec = (int64_t)mrb_integer(time); + t.tv_sec = mrb_integer(time); t.tv_usec = 0; break; #ifndef MRB_NO_FLOAT case MRB_TT_FLOAT: - t.tv_sec = (int64_t)mrb_float(time); - t.tv_usec = (int64_t)((mrb_float(time) - t.tv_sec) * 1000000.0); + t.tv_sec = (mrb_int)mrb_float(time); + t.tv_usec = (mrb_int)((mrb_float(time) - t.tv_sec) * 1000000.0); break; #endif @@ -2195,6 +2204,51 @@ io_flush(mrb_state *mrb, mrb_value io) return io; } +/* + * call-seq: + * ios.autoclose = bool -> bool + * + * Sets the autoclose flag. + * + * If the autoclose flag is set, the underlying file descriptor(s) of +ios+ + * will be closed when +ios+ is closed (explicitly via +#close+, or implicitly + * when the +IO+ object is garbage-collected). When unset, the file + * descriptor(s) are left open. + * + * f = File.open("testfile") + * IO.for_fd(f.fileno).autoclose = false + */ +static mrb_value +io_set_autoclose(mrb_state *mrb, mrb_value io) +{ + struct mrb_io *fptr = io_get_open_fptr(mrb, io); + mrb_bool b; + + mrb_get_args(mrb, "b", &b); + fptr->close_fd = b; + fptr->close_fd2 = b; + return mrb_bool_value(b); +} + +/* + * call-seq: + * ios.autoclose? -> true or false + * + * Returns +true+ if the underlying file descriptor of +ios+ will be closed + * when +ios+ is closed, otherwise +false+. + * + * f = File.open("testfile") + * f.autoclose? #=> true + * f.autoclose = false + * f.autoclose? #=> false + */ +static mrb_value +io_autoclose_p(mrb_state *mrb, mrb_value io) +{ + struct mrb_io *fptr = io_get_open_fptr(mrb, io); + return mrb_bool_value(fptr->close_fd); +} + /* ---------------------------*/ static const mrb_mt_entry io_rom_entries[] = { MRB_MT_ENTRY(io_init, MRB_SYM(initialize), MRB_ARGS_ARG(1,2)), @@ -2233,6 +2287,8 @@ static const mrb_mt_entry io_rom_entries[] = { MRB_MT_ENTRY(io_pwrite, MRB_SYM(pwrite), MRB_ARGS_ANY()), /* Ruby 2.5 feature */ MRB_MT_ENTRY(io_getbyte, MRB_SYM(getbyte), MRB_ARGS_NONE()), MRB_MT_ENTRY(io_readbyte, MRB_SYM(readbyte), MRB_ARGS_NONE()), + MRB_MT_ENTRY(io_set_autoclose, MRB_SYM_E(autoclose), MRB_ARGS_REQ(1)), + MRB_MT_ENTRY(io_autoclose_p, MRB_SYM_Q(autoclose), MRB_ARGS_NONE()), }; void diff --git a/mrbgems/mruby-io/src/mruby_io_gem.c b/mrbgems/mruby-io/src/mruby_io_gem.c index 87b7e8aac5..4e01442a20 100644 --- a/mrbgems/mruby-io/src/mruby_io_gem.c +++ b/mrbgems/mruby-io/src/mruby_io_gem.c @@ -1,4 +1,5 @@ #include +#include void mrb_init_io(mrb_state *mrb); void mrb_init_file(mrb_state *mrb); @@ -9,6 +10,7 @@ void mrb_init_file_test(mrb_state *mrb); void mrb_mruby_io_gem_init(mrb_state* mrb) { + mrb_hal_io_init(mrb); mrb_init_io(mrb); DONE; mrb_init_file(mrb); DONE; mrb_init_file_test(mrb); DONE; @@ -18,4 +20,5 @@ mrb_mruby_io_gem_init(mrb_state* mrb) void mrb_mruby_io_gem_final(mrb_state* mrb) { + mrb_hal_io_final(mrb); } diff --git a/mrbgems/mruby-io/test/file.rb b/mrbgems/mruby-io/test/file.rb index b65f7f8f87..8da7eb14c4 100644 --- a/mrbgems/mruby-io/test/file.rb +++ b/mrbgems/mruby-io/test/file.rb @@ -57,6 +57,40 @@ def assert_dirname_with_level(path, results) assert_equal 'foo.rb', File.basename('foo.rb', '.RB') # case-sensitive end +if MRubyIOTestUtil.win? + assert('File.basename (for Windows)') do + assert_equal '/', File.basename('/') + assert_equal '/', File.basename('//a') + assert_equal '/', File.basename('//a/') + assert_equal '/', File.basename('//a/b') + assert_equal '/', File.basename('//a/b/') + assert_equal 'c', File.basename('//a/b/c') + assert_equal 'c', File.basename('//a/b/c/') + assert_equal '/', File.basename("\\\\a\\b") + assert_equal '', File.basename('c:') + assert_equal '/', File.basename('c:/') + assert_equal 'a', File.basename('c:/a') + assert_equal 'a', File.basename('c:/a/') + assert_equal 'b', File.basename('c:/a/b') + assert_equal '/', File.basename("c:\\") + end +else + assert('File.basename (for generic)') do + assert_equal '/', File.basename('/') + assert_equal 'a', File.basename('//a') + assert_equal 'a', File.basename('//a/') + assert_equal 'b', File.basename('//a/b') + assert_equal 'b', File.basename('//a/b/') + assert_equal 'c', File.basename('//a/b/c') + assert_equal 'c', File.basename('//a/b/c/') + assert_equal 'c:', File.basename('c:') + assert_equal 'c:', File.basename('c:/') + assert_equal 'a', File.basename('c:/a') + assert_equal 'a', File.basename('c:/a/') + assert_equal 'b', File.basename('c:/a/b') + end +end + assert('File.dirname') do assert_equal '.', File.dirname('') assert_equal '.', File.dirname('a') diff --git a/mrbgems/mruby-io/test/io.rb b/mrbgems/mruby-io/test/io.rb index 38af70e9a2..2aa7480dbd 100644 --- a/mrbgems/mruby-io/test/io.rb +++ b/mrbgems/mruby-io/test/io.rb @@ -665,4 +665,14 @@ def io._buf end end +assert('IO#autoclose?, IO#autoclose=') do + io = IO.new(IO.sysopen($mrbtest_io_rfname), "r") + assert_true io.autoclose? + io.autoclose = false + assert_false io.autoclose? + io.autoclose = true + assert_true io.autoclose? + io.close +end + MRubyIOTestUtil.io_test_cleanup diff --git a/mrbgems/mruby-method/src/method.c b/mrbgems/mruby-method/src/method.c index ad0b63fcf8..b0fc6019f0 100644 --- a/mrbgems/mruby-method/src/method.c +++ b/mrbgems/mruby-method/src/method.c @@ -85,6 +85,7 @@ method_missing_prepare(mrb_state *mrb, mrb_sym *mid, mrb_value recv, struct RCla const struct RProc *proc; if (MRB_METHOD_FUNC_P(m)) { struct RProc *p = mrb_proc_new_cfunc(mrb, MRB_METHOD_FUNC(m)); + mrb_proc_set_cfunc_aspec(p, MRB_MT_ASPEC(m.flags)); MRB_PROC_SET_TARGET_CLASS(p, *tc); proc = p; } @@ -393,9 +394,7 @@ method_search_vm(mrb_state *mrb, struct RClass **cp, mrb_sym mid) return MRB_METHOD_PROC(m); struct RProc *proc = mrb_proc_new_cfunc(mrb, MRB_METHOD_FUNC(m)); - if (MRB_MT_ASPEC(m.flags) == 0) { - proc->flags |= MRB_PROC_NOARG; - } + mrb_proc_set_cfunc_aspec(proc, MRB_MT_ASPEC(m.flags)); return proc; } @@ -636,7 +635,7 @@ search_method_owner(mrb_state *mrb, struct RClass *c, mrb_value obj, mrb_sym nam if (!mrb_respond_to(mrb, obj, MRB_SYM_Q(respond_to_missing))) { return FALSE; } - mrb_value ret = mrb_funcall_id(mrb, obj, MRB_SYM_Q(respond_to_missing), 2, mrb_symbol_value(name), mrb_true_value()); + mrb_value ret = mrb_funcall_argv2(mrb, obj, MRB_SYM_Q(respond_to_missing), mrb_symbol_value(name), mrb_true_value()); if (!mrb_test(ret)) { return FALSE; } diff --git a/mrbgems/mruby-method/test/method.rb b/mrbgems/mruby-method/test/method.rb index 005403bc57..93ac30e600 100644 --- a/mrbgems/mruby-method/test/method.rb +++ b/mrbgems/mruby-method/test/method.rb @@ -60,7 +60,7 @@ def run assert_equal(-3, method(:mo7).arity) assert_equal(1, method(:ma1).arity) - assert_equal(-1, method(:__send__).arity) + assert_equal(-2, method(:__send__).arity) assert_equal(-1, method(:nothing).arity) end @@ -512,3 +512,131 @@ def m(x, y, z, *w, u:, v:, **opts, &blk) assert_raise(ArgumentError) { BasicObject.instance_method(:__id__).bind_call nil, 1 } assert_raise(ArgumentError) { BasicObject.instance_method(:__id__).bind_call nil, opts: 1 } end + +assert 'Method#parameters and #arity on aliased methods' do + # Regression: an alias proc carries the original method's name in body.mid + # (not an irep), with `upper` pointing at the original proc. Both + # mrb_proc_parameters (mruby-proc-ext) and mrb_proc_arity (core src/proc.c) + # used to fall into their irep branch for alias procs and dereference body.mid + # as an mrb_irep* -> SEGV / misaligned read. Both must resolve through `upper`. + # + # The literals below match CRuby for these positional/optional/rest/block + # signatures (where mruby and CRuby agree), so this is independent ground + # truth, not merely "alias == original". + c = Class.new { + def f0; end + def f1(a); end + def fopt(a, b = 1); end + def frest(a, *b); end + def fblk(a, &b); end + def fmix(a, b = 1, *c, &d); end + alias_method :a0, :f0 + alias_method :a1, :f1 + alias_method :aopt, :fopt + alias_method :arest, :frest + alias_method :ablk, :fblk + alias_method :amix, :fmix + } + cases = [ + # name, parameters, arity + [:a0, [], 0], + [:a1, [[:req, :a]], 1], + [:aopt, [[:req, :a], [:opt, :b]], -2], + [:arest, [[:req, :a], [:rest, :b]], -2], + [:ablk, [[:req, :a], [:block, :b]], 1], + [:amix, [[:req, :a], [:opt, :b], [:rest, :c], [:block, :d]], -2], + ] + cases.each do |name, params, arity| + u = c.instance_method(name) + assert_equal params, u.parameters + assert_equal arity, u.arity + # the bound Method goes through the same proc paths and must agree + b = c.new.method(name) + assert_equal params, b.parameters + assert_equal arity, b.arity + end + # alias must equal the original it points at (parameters and arity) + { a0: :f0, a1: :f1, aopt: :fopt, arest: :frest, ablk: :fblk, amix: :fmix }.each do |al, orig| + assert_equal c.instance_method(orig).parameters, c.instance_method(al).parameters + assert_equal c.instance_method(orig).arity, c.instance_method(al).arity + end + + # Alias-of-an-alias collapses to one proc at creation; must still resolve. + chain = Class.new { + def orig(x, y) end + alias_method :a1, :orig + alias_method :a2, :a1 + } + assert_equal [[:req, :x], [:req, :y]], chain.instance_method(:a2).parameters + assert_equal 2, chain.instance_method(:a2).arity + + # Aliasing a C method does NOT create an alias proc (it reuses the original + # cfunc method), so it must behave exactly like the original and never crash. + c2 = Class.new(String) { alias_method :up2, :upcase } + assert_equal String.instance_method(:upcase).parameters, + c2.instance_method(:up2).parameters + assert_equal String.instance_method(:upcase).arity, + c2.instance_method(:up2).arity +end + +assert 'Method/UnboundMethod on C-defined (native) methods' do + # C methods have no irep: arity/parameters come from the packed argument spec + # (caspec) and source_location is always nil. This exercises the + # MRB_PROC_CFUNC_P branches of mrb_proc_arity / mrb_proc_parameters and the + # cfunc path of method_search_vm -- none of which the suite covered before. + + # source_location is nil for genuinely C-defined methods (not mrblib ones). + assert_nil "x".method(:upcase).source_location + assert_nil 1.method(:+).source_location + assert_nil [].method(:push).source_location + assert_nil String.instance_method(:upcase).source_location + + # arity: documented values for these core C methods. + assert_equal 0, "x".method(:upcase).arity # no args + assert_equal 1, 1.method(:+).arity # one required + assert_equal(-1, [].method(:push).arity) # variadic (rest) + assert_equal(-1, [].method(:first).arity) # optional + + # parameters: always an Array of Arrays, never crashes; a no-arg C method + # gives []. C-method parameter *kinds* are an approximation (names are absent, + # and required args surface as :opt for non-strict procs), so we assert the + # stable shape rather than pinning exact kind labels -- except :rest, which is + # meaningful: a variadic C method must expose a rest parameter. + assert_equal [], "x".method(:upcase).parameters + [1.method(:+), [].method(:push), {}.method(:[]), [].method(:first)].each do |m| + ps = m.parameters + assert_true ps.is_a?(Array) + ps.each { |p| assert_true p.is_a?(Array) } + end + assert_true [].method(:push).parameters.any? { |entry| entry[0] == :rest } + + # identity / metadata on a C method. + m = "abc".method(:upcase) + assert_equal String, m.owner + assert_equal :upcase, m.name + assert_equal "abc", m.receiver + assert_equal "#", m.to_s + assert_equal "#", String.instance_method(:upcase).to_s + + # behaviour: the C function actually runs via call / [] / bind / bind_call / + # unbind+rebind. + assert_equal 5, 2.method(:+).call(3) + assert_equal 5, 2.method(:+)[3] + assert_equal 5, Integer.instance_method(:+).bind_call(2, 3) + assert_equal 5, Integer.instance_method(:+).bind(2).call(3) + assert_equal 11, 5.method(:+).unbind.bind(10).call(1) + + # eql?: equal only when bound to the SAME receiver object and same definition. + s = "cat" + assert_true s.method(:upcase) == s.method(:upcase) + assert_false s.method(:upcase) == "cat".method(:upcase) # distinct receivers + assert_false s.method(:upcase) == s.method(:downcase) + + # super_method resolves across C methods (Integer#to_s -> BasicObject#to_s). + sm = 5.method(:to_s).super_method + assert_false sm.nil? + assert_equal :to_s, sm.name + + # binding a C UnboundMethod to an incompatible receiver still raises cleanly. + assert_raise(TypeError) { String.instance_method(:upcase).bind(42) } +end diff --git a/mrbgems/mruby-numeric-ext/src/numeric_ext.c b/mrbgems/mruby-numeric-ext/src/numeric_ext.c index 1c80a5055a..13159b75df 100644 --- a/mrbgems/mruby-numeric-ext/src/numeric_ext.c +++ b/mrbgems/mruby-numeric-ext/src/numeric_ext.c @@ -84,16 +84,20 @@ mrb_value mrb_int_pow(mrb_state *mrb, mrb_value x, mrb_value y); static mrb_int mrb_int_gcd(mrb_int x, mrb_int y) { - if (x < 0) x = -x; - if (y < 0) y = -y; - - while (y != 0) { - mrb_int temp = y; - y = x % y; - x = temp; + /* Negate via unsigned so MRB_INT_MIN doesn't overflow. + The cast back at the end produces MRB_INT_MIN only when the + true result is 2^63 (gcd of MRB_INT_MIN with itself or 0); + callers detect that case from the negative return value. */ + mrb_uint ux = (x < 0) ? -(mrb_uint)x : (mrb_uint)x; + mrb_uint uy = (y < 0) ? -(mrb_uint)y : (mrb_uint)y; + + while (uy != 0) { + mrb_uint temp = uy; + uy = ux % uy; + ux = temp; } - return x; + return (mrb_int)ux; } /* @@ -122,7 +126,11 @@ int_gcd(mrb_state *mrb, mrb_value x) if (!mrb_integer_p(y)) { mrb_raisef(mrb, E_TYPE_ERROR, "can't convert %Y into Integer", y); } - return mrb_int_value(mrb, mrb_int_gcd(mrb_integer(x), mrb_integer(y))); + mrb_int g = mrb_int_gcd(mrb_integer(x), mrb_integer(y)); + /* g < 0 only when the mathematical result is 2^63 (= |MRB_INT_MIN|), + which does not fit in mrb_int. */ + if (g < 0) mrb_int_overflow(mrb, "gcd"); + return mrb_int_value(mrb, g); } /* @@ -158,6 +166,10 @@ int_lcm(mrb_state *mrb, mrb_value x) if (a == 0 || b == 0) return mrb_int_value(mrb, 0); + /* Negation of MRB_INT_MIN is UB and the lcm with any non-zero + operand would not fit in mrb_int anyway. */ + if (a == MRB_INT_MIN || b == MRB_INT_MIN) mrb_int_overflow(mrb, "lcm"); + gcd_val = mrb_int_gcd(a, b); if (a < 0) a = -a; if (b < 0) b = -b; diff --git a/mrbgems/mruby-proc-ext/mrblib/proc.rb b/mrbgems/mruby-proc-ext/mrblib/proc.rb index 4e7dfac576..320549ce70 100644 --- a/mrbgems/mruby-proc-ext/mrblib/proc.rb +++ b/mrbgems/mruby-proc-ext/mrblib/proc.rb @@ -75,9 +75,22 @@ def curry(arity=self.arity) if lambda? type = :lambda self_arity = self.arity - if (self_arity >= 0 && arity != self_arity) || - (self_arity < 0 && abs[self_arity] > arity) - raise ArgumentError, "wrong number of arguments (given #{arity}, expected #{abs[self_arity]})" + min_req = abs[self_arity] + if self_arity < 0 + max_arity = 0 + has_rest = false + self.parameters.each do |p| + case p[0] + when :rest, :keyrest then has_rest = true + when :req, :opt then max_arity += 1 + end + end + if arity < min_req || (!has_rest && arity > max_arity) + expected = (!has_rest && max_arity != min_req) ? "#{min_req}..#{max_arity}" : min_req.to_s + raise ArgumentError, "wrong number of arguments (given #{arity}, expected #{expected})" + end + elsif arity != self_arity + raise ArgumentError, "wrong number of arguments (given #{arity}, expected #{self_arity})" end end diff --git a/mrbgems/mruby-proc-ext/src/proc.c b/mrbgems/mruby-proc-ext/src/proc.c index 14c42a790b..4149e0304b 100644 --- a/mrbgems/mruby-proc-ext/src/proc.c +++ b/mrbgems/mruby-proc-ext/src/proc.c @@ -182,13 +182,38 @@ mrb_proc_parameters(mrb_state *mrb, mrb_value self) }; int i; const struct RProc *proc = mrb_proc_ptr(self); + /* An alias proc carries no irep of its own: body.mid holds the aliased + method's name and `upper` points at the original proc (see mrb_alias_method + in class.c). Without this, the else branch below reads body.mid as an + mrb_irep* and dereferences it -> misaligned read / SEGV. Resolve to the + underlying proc, exactly as method_to_s already does, so an aliased method + reports the original's parameters. Alias chains are collapsed at creation, + but loop (as mruby-method does) and bail to empty on a broken chain. */ + while (MRB_PROC_ALIAS_P(proc)) { + proc = proc->upper; + if (!proc) return mrb_ary_new(mrb); + } + mrb_aspec aspec; + mrb_bool has_lv = TRUE; if (MRB_PROC_CFUNC_P(proc)) { - /* TODO: cfunc aspec is not implemented yet - C functions don't store argument spec info */ - return mrb_ary_new(mrb); + uint32_t caspec_bits = proc->flags & MRB_PROC_CASPEC_MASK; + if (caspec_bits != 0) { + aspec = mrb_proc_decompress_caspec(caspec_bits); + } + else if (MRB_PROC_NOARG_P(proc)) { + aspec = 0; + } + else { + return mrb_ary_new(mrb); + } + has_lv = FALSE; } - const struct mrb_irep *irep = proc->body.irep; - if (!irep || !irep->lv || *irep->iseq != OP_ENTER) { - return mrb_ary_new(mrb); + else { + const struct mrb_irep *irep = proc->body.irep; + if (!irep || !irep->lv || *irep->iseq != OP_ENTER) { + return mrb_ary_new(mrb); + } + aspec = PEEK_W(irep->iseq+1); } if (!MRB_PROC_STRICT_P(proc)) { @@ -196,7 +221,6 @@ mrb_proc_parameters(mrb_state *mrb, mrb_value self) parameters_list[3].name = MRB_SYM(opt); } - mrb_aspec aspec = PEEK_W(irep->iseq+1); parameters_list[0].size = MRB_ASPEC_REQ(aspec); parameters_list[1].size = MRB_ASPEC_OPT(aspec); parameters_list[2].size = MRB_ASPEC_REST(aspec); @@ -214,18 +238,19 @@ mrb_proc_parameters(mrb_state *mrb, mrb_value self) mrb_value krest = mrb_nil_value(); mrb_value block = mrb_nil_value(); + const mrb_sym *lv = has_lv ? proc->body.irep->lv : NULL; for (i = 0, p = parameters_list; p->name; p++) { mrb_value sname = mrb_symbol_value(p->name); for (int j = 0; j < p->size; i++, j++) { mrb_value a = mrb_ary_new(mrb); mrb_ary_push(mrb, a, sname); - if (i < max && irep->lv[i]) { - mrb_ary_push(mrb, a, mrb_symbol_value(irep->lv[i])); + if (lv && i < max && lv[i]) { + mrb_ary_push(mrb, a, mrb_symbol_value(lv[i])); } if (p->name == MRB_SYM(block)) { - if (irep->lv[i+1]) { - mrb_ary_push(mrb, a, mrb_symbol_value(irep->lv[i+1])); + if (lv && lv[i+1]) { + mrb_ary_push(mrb, a, mrb_symbol_value(lv[i+1])); } block = a; continue; } diff --git a/mrbgems/mruby-proc-ext/test/proc.rb b/mrbgems/mruby-proc-ext/test/proc.rb index 7a105323ac..fcbeb49624 100644 --- a/mrbgems/mruby-proc-ext/test/proc.rb +++ b/mrbgems/mruby-proc-ext/test/proc.rb @@ -64,6 +64,13 @@ def enable_debug_info? assert_false(proc{}.curry.lambda?) assert_true(lambda{}.curry.lambda?) + + # #2855: lambda with optional param: curry must validate against max arity + l = lambda {|a, b=nil|} + assert_raise(ArgumentError) { l.curry(3) } # over max + assert_kind_of Proc, l.curry(2) # within range + assert_kind_of Proc, l.curry(1) # at min + assert_kind_of Proc, lambda {|a, *b|}.curry(99) # rest -> unbounded end assert('Proc#parameters') do diff --git a/mrbgems/mruby-random/src/random.c b/mrbgems/mruby-random/src/random.c index b65170ae80..e77f137fc1 100644 --- a/mrbgems/mruby-random/src/random.c +++ b/mrbgems/mruby-random/src/random.c @@ -154,14 +154,45 @@ rand_i(rand_state *t, mrb_int max) return (mrb_int)(r % (uint32_t)max); } +/* Full-width unsigned random value in [0, 2**MRB_INT_BIT). */ +static mrb_uint +rand_uint(rand_state *t) +{ +#ifdef MRB_INT64 + return ((mrb_uint)rand_uint32(t) << 32) | rand_uint32(t); +#else + return (mrb_uint)rand_uint32(t); +#endif +} + +/* Uniform unsigned value in [0, span) without modulo bias. + span == 0 selects the entire domain [0, 2**MRB_INT_BIT). */ +static mrb_uint +rand_u(rand_state *t, mrb_uint span) +{ + if (span == 0) return rand_uint(t); + mrb_uint threshold = (mrb_uint)(-span) % span; /* == 2**MRB_INT_BIT % span */ + mrb_uint r; + do { + r = rand_uint(t); + } while (r < threshold); + return r % span; +} + static mrb_value rand_range_int(mrb_state *mrb, rand_state *t, mrb_int begin, mrb_int end, mrb_bool excl) { - mrb_int span = end - begin + (excl ? 0 : 1); - if (span <= 0) + /* Reversed or empty range -> nil (as CRuby does). Compare before + subtracting so extreme bounds cannot overflow mrb_int. */ + if (begin > end || (excl && begin == end)) return mrb_nil_value(); - return mrb_int_value(mrb, (rand_i(t, span)) + begin); + /* Candidate count in unsigned arithmetic to avoid signed overflow. + An inclusive full-width range wraps to 0, which rand_u reads as + "the entire domain". */ + mrb_uint span = (mrb_uint)end - (mrb_uint)begin + (excl ? 0 : 1); + mrb_uint r = rand_u(t, span); + return mrb_int_value(mrb, (mrb_int)((mrb_uint)begin + r)); } #ifndef MRB_NO_FLOAT diff --git a/mrbgems/mruby-random/test/random.rb b/mrbgems/mruby-random/test/random.rb index 27fb081fa2..7f007aaa5b 100644 --- a/mrbgems/mruby-random/test/random.rb +++ b/mrbgems/mruby-random/test/random.rb @@ -149,3 +149,33 @@ assert_equal(rand(0.0...0.0), nil) assert_equal(rand(1..0), nil) end + +assert("Kernel#rand integer range overflow") do + # Width-independent guard behaviour for reversed/empty/single ranges. + assert_equal(5, rand(5..5)) # single-element inclusive range + assert_nil(rand(5...5)) # empty exclusive range + assert_nil(rand(10..3)) # reversed inclusive range + assert_nil(rand(10...3)) # reversed exclusive range + 100.times { assert_include(3..7, rand(3..7)) } + + # Wide fixnum ranges whose span exceeds the mrb_int range only exist on + # 64-bit mrb_int builds. Such bounds used to overflow `end - begin` in C + # (UndefinedBehaviorSanitizer signed-integer-overflow), found via a + # minimized mruby_fuzzer testcase. On 32-bit mrb_int (or when bigint + # promotes the bounds) these take a different path, so probe the + # integer-range path first and skip if absent. + hi = ((1 << 62) + (1 << 61)) rescue nil + if hi && (Integer === (rand(-hi..hi) rescue nil)) + lo = -hi + # reversed wide range -> nil; old code overflowed `end - begin`. + assert_nil(rand(hi..lo)) + assert_nil(rand(hi...lo)) + # valid wide range -> in-range Integer; old code overflowed and wrongly + # returned nil instead of a uniform value. + 100.times do + v = rand(lo..hi) + assert_kind_of(Integer, v) + assert_true(lo <= v && v <= hi) + end + end +end diff --git a/mrbgems/mruby-regexp/README.md b/mrbgems/mruby-regexp/README.md new file mode 100644 index 0000000000..1a29ed813f --- /dev/null +++ b/mrbgems/mruby-regexp/README.md @@ -0,0 +1,131 @@ +# mruby-regexp + +Built-in regular expression engine for mruby using a Pike VM (NFA +simulation) with backtracking fallback. + +## Features + +### Pattern Syntax + +- `.` any character (except newline by default) +- `*`, `+`, `?` greedy quantifiers +- `*?`, `+?`, `??` non-greedy quantifiers +- `{n}`, `{n,}`, `{n,m}` repetition counts +- `[abc]`, `[a-z]`, `[^abc]` character classes +- `\d`, `\w`, `\s` digit, word, whitespace shortcuts +- `\D`, `\W`, `\S` negated shortcuts +- `(...)` capture group +- `(?:...)` non-capturing group +- `(?...)` named capture group +- `|` alternation +- `\1`-`\9` backreferences +- `(?=...)` positive lookahead +- `(?!...)` negative lookahead +- `(?<=...)` positive lookbehind (fixed-length only) +- `(? MatchData or nil +re.match?("string") # => true/false +re =~ "string" # => index or nil +re === "string" # => true/false (for case/when) +re.source # => "pattern" +re.options # => flags integer +Regexp.escape("a.b") # => "a\\.b" +Regexp.last_match(n) # => nth capture from last match + +# MatchData +md = /(\w+)@(\w+)/.match("user@host") +md[0] # => "user@host" (full match) +md[1] # => "user" +md[2] # => "host" +md[:name] # named capture access +md.captures # => ["user", "host"] +md.to_a # => ["user@host", "user", "host"] +md.begin(0) # => match start position +md.end(0) # => match end position +md.pre_match # => string before match +md.post_match # => string after match +md.named_captures # => {"name" => "value", ...} + +# String methods +str.match(re) # => MatchData or nil +str.match?(re) # => true/false +str =~ re # => index or nil +str.sub(re, replacement) # replace first occurrence +str.sub(re) { |m| ... } # replace with block +str.gsub(re, replacement) # replace all occurrences +str.gsub(re) { |m| ... } # replace all with block +str.scan(re) # => array of matches +str.split(re) # => array of parts + +# Global variables +$~ # last MatchData +``` + +## Engine Architecture + +The gem uses two execution engines: + +**Pike VM (NFA simulation)**: Used for patterns without +backreferences, non-greedy quantifiers, or lookahead. Guarantees +O(pattern x text) time complexity, making it immune to ReDoS +attacks. + +**Backtracking engine**: Used when patterns contain `\1`-`\9` +backreferences, non-greedy quantifiers (`*?`, `+?`, `??`), or +lookahead assertions (`(?=...)`, `(?!...)`). Protected by a +configurable step limit (`MRB_REGEXP_STEP_LIMIT`, default 1M) to +prevent excessive backtracking. + +The engine is selected automatically at compile time based on +pattern analysis. + +## Limitations + +- **Fixed-length lookbehind only**: `(?<=...)` and `(? +#include + +/* Bytecode instructions for the NFA engine */ +enum re_opcode { + RE_CHAR, /* match literal byte: operand = byte value */ + RE_ANY, /* match any character (. without DOTALL) */ + RE_ANY_NL, /* match any character including newline (. with DOTALL) */ + RE_CLASS, /* match character class: operand = class_id */ + RE_NCLASS, /* match negated character class: operand = class_id */ + RE_MATCH, /* successful match */ + RE_JMP, /* unconditional jump: operand = target offset */ + RE_SPLIT, /* fork: operand = target offset (greedy: try next first) */ + RE_SPLITNG, /* fork: operand = target offset (non-greedy: try jump first) */ + RE_SAVE, /* save capture position: operand = slot number */ + RE_BOL, /* assert beginning of line (^) */ + RE_EOL, /* assert end of line ($) */ + RE_BOT, /* assert beginning of text (\A) */ + RE_EOT, /* assert end of text (\z) */ + RE_EOTNL, /* assert end of text or before final \n (\Z) */ + RE_WBOUND, /* assert word boundary (\b) */ + RE_NWBOUND, /* assert non-word boundary (\B) */ + RE_BACKREF, /* backreference: operand = group number */ + RE_LOOKAHEAD, /* positive lookahead: offset = end of sub-pattern */ + RE_NEG_LOOKAHEAD, /* negative lookahead: offset = end of sub-pattern */ + RE_LOOKBEHIND, /* positive lookbehind: a = byte length, offset = end */ + RE_NEG_LOOKBEHIND, /* negative lookbehind: a = byte length, offset = end */ +}; + +/* Bytecode instruction (4 bytes each for alignment) */ +typedef struct { + uint8_t op; + uint8_t a; /* small operand or class id */ + uint16_t offset; /* jump target or extended operand */ +} re_inst; + +/* Character class bitmap (ASCII range) */ +#define RE_CLASS_BITMAP_SIZE 16 /* 128 bits = 16 bytes for ASCII */ +typedef struct { + uint8_t bitmap[RE_CLASS_BITMAP_SIZE]; /* bitmap for 0-127 */ + mrb_bool negated; + mrb_bool utf8_any; /* match any non-ASCII byte if true */ +} re_charclass; + +/* Named capture entry */ +typedef struct { + const char *name; + uint16_t name_len; + uint16_t group; +} re_named_capture; + +/* Compiled regexp pattern */ +typedef struct mrb_regexp_pattern { + re_inst *code; /* bytecode array */ + uint32_t code_len; /* number of instructions */ + re_charclass *classes; /* character class table */ + uint16_t num_classes; + uint16_t num_captures; /* number of capture groups (including group 0) */ + uint32_t flags; + re_named_capture *named_captures; + char *named_arena; /* owned storage for named_captures[i].name; NULL if num_named == 0 */ + uint16_t num_named; + mrb_bool has_backref; /* true if pattern uses \1-\9 */ + mrb_bool needs_backtrack; /* true if pattern needs backtracking engine */ + uint8_t *prefix; /* literal prefix bytes for fast skip (or NULL) */ + uint8_t prefix_len; /* length of prefix (0 = no prefix) */ + uint8_t first_bytes[16]; /* bitmap of possible first bytes (128-bit, ASCII) */ + mrb_bool has_first_bytes; /* true if first_bytes is usable for skipping */ + mrb_bool is_literal; /* true if pattern is pure literal (no metacharacters) */ + /* Cached VM state for pike_vm (avoids malloc per mrb_re_exec call) */ + uint32_t *cached_visited; /* generation-based visited array */ + void *cached_threads[2]; /* curr/next thread lists */ + int cached_list_capa; /* capacity of cached thread lists */ + mrb_bool cache_in_use; /* re-entrancy guard */ +} mrb_regexp_pattern; + +/* Regexp flags */ +#define RE_FLAG_IGNORECASE 1 +#define RE_FLAG_MULTILINE 2 /* ^ and $ match at \n boundaries */ +#define RE_FLAG_DOTALL 4 /* . matches \n (Ruby's /m for dot behavior) */ +#define RE_FLAG_EXTENDED 8 /* ignore whitespace and #comments in pattern */ + +/* Note: Ruby's /m flag means BOTH multiline anchors AND dotall. + Ruby's /i flag is ignorecase. Ruby's /x flag is extended. */ + +/* Step limit for ReDoS protection */ +#ifndef MRB_REGEXP_STEP_LIMIT +#define MRB_REGEXP_STEP_LIMIT 1000000 +#endif + +/* Recursion-depth limit for bt_match: bounds C stack growth on + patterns like `(?=)+` that recurse without consuming input. */ +#ifndef MRB_REGEXP_RECURSION_LIMIT +#define MRB_REGEXP_RECURSION_LIMIT 1000 +#endif + +/* Maximum captures */ +#define RE_MAX_CAPTURES 32 + +/* Thread struct for Pike VM (also used for cache sizing) */ +typedef struct { + uint32_t pc; + int cap_slot; +} re_thread_cache; + +/* Compile a pattern string into bytecode */ +mrb_regexp_pattern* mrb_re_compile(mrb_state *mrb, const char *pattern, mrb_int len, uint32_t flags); + +/* Free a compiled pattern */ +void mrb_re_free(mrb_state *mrb, mrb_regexp_pattern *pat); + +/* Execute a match. + Returns number of captures filled (0 = no match). + captures[2*n] = start, captures[2*n+1] = end for group n. */ +int mrb_re_exec(mrb_state *mrb, const mrb_regexp_pattern *pat, + const char *str, mrb_int len, mrb_int start, + int *captures, int captures_size); + +/* UTF-8 helpers */ +int mrb_re_utf8_charlen(const char *s, const char *end); +uint32_t mrb_re_utf8_decode(const char *s, int *len); +mrb_bool mrb_re_is_word_char(uint32_t c); + +#endif /* MRB_RE_INTERNAL_H */ diff --git a/mrbgems/mruby-regexp/mrbgem.rake b/mrbgems/mruby-regexp/mrbgem.rake new file mode 100644 index 0000000000..c5879908dc --- /dev/null +++ b/mrbgems/mruby-regexp/mrbgem.rake @@ -0,0 +1,7 @@ +MRuby::Gem::Specification.new('mruby-regexp') do |spec| + spec.license = 'MIT' + spec.authors = 'mruby developers' + spec.summary = 'Regexp class (built-in NFA engine)' + + spec.add_dependency 'mruby-string-ext', :core => 'mruby-string-ext' +end diff --git a/mrbgems/mruby-regexp/mrblib/regexp.rb b/mrbgems/mruby-regexp/mrblib/regexp.rb new file mode 100644 index 0000000000..84740d578b --- /dev/null +++ b/mrbgems/mruby-regexp/mrblib/regexp.rb @@ -0,0 +1,24 @@ +class Regexp + def self.compile(pattern, *args) + new(pattern, *args) + end + + # Return named captures hash: {"name" => group_number, ...} + def named_captures + @named_captures || {} + end + + # options is implemented in C (internal flags -> Ruby constants conversion) + + def self.last_match(n = nil) + md = $~ + return md if n.nil? + md ? md[n] : nil + end + + # named capture info is set via C create_matchdata +end + +class MatchData + # named_captures is implemented in C via md->regexp +end diff --git a/mrbgems/mruby-regexp/mrblib/string_regexp.rb b/mrbgems/mruby-regexp/mrblib/string_regexp.rb new file mode 100644 index 0000000000..e15e507a3a --- /dev/null +++ b/mrbgems/mruby-regexp/mrblib/string_regexp.rb @@ -0,0 +1,109 @@ +class String + # Capture the C-defined String#split under `__split` before the override + # below replaces it, so the override can delegate non-regexp patterns + # back to the core implementation. + alias __split split + + def match(re, pos = 0) + re = Regexp.new(re) if re.is_a?(String) + re.match(self, pos) + end + + def match?(re, pos = 0) + re = Regexp.new(re) if re.is_a?(String) + re.match?(self, pos) + end + + def =~(re) + re =~ self + end + + def sub(pattern, replacement = nil, &block) + pattern = Regexp.new(Regexp.escape(pattern)) if pattern.is_a?(String) + unless block + return pattern.__sub_str(self, replacement.to_s) + end + md = pattern.match(self) + return self.dup unless md + md.pre_match + block.call(md[0]).to_s + md.post_match + end + + def gsub(pattern, replacement = nil, &block) + pattern = Regexp.new(Regexp.escape(pattern)) if pattern.is_a?(String) + unless block + return pattern.__gsub_str(self, replacement.to_s) + end + # block case: keep in Ruby to avoid VM callback from C + parts = [] + rest = self + while rest.length > 0 + md = pattern.match(rest) + break unless md + parts << md.pre_match + parts << block.call(md[0]).to_s + matched_len = md[0].length + if matched_len == 0 + parts << rest[0] if rest.length > 0 + rest = rest[1..-1] || "" + else + rest = md.post_match + end + end + parts << rest + parts.join + end + + def scan(pattern) + pattern = Regexp.new(Regexp.escape(pattern)) if pattern.is_a?(String) + result = pattern.__scan(self) + if block_given? + result.each { |m| yield m } + self + else + result + end + end + + # Regexp-aware split. Falls back to the C-defined split (aliased as + # `__split` in mrb_mruby_regexp_gem_init before this override loads) for + # nil or simple-string patterns; converts string-with-backslash to a + # Regexp and handles regexp patterns in Ruby. + def split(pattern = nil, limit = -1) + return __split(pattern, limit) if pattern.nil? + if pattern.is_a?(String) + return __split(pattern, limit) if pattern.length == 1 || !pattern.include?('\\') + pattern = Regexp.new(Regexp.escape(pattern)) + end + result = [] + rest = self + count = 0 + while rest.length > 0 + if limit > 0 && count >= limit - 1 + result << rest + return result + end + md = pattern.match(rest) + break unless md + result << md.pre_match + rest = md.post_match + count += 1 + # skip zero-length match at beginning + if md[0].length == 0 + if rest.length > 0 + result[-1] = result[-1] + rest[0] + rest = rest[1..-1] || "" + else + break + end + end + end + result << rest + # remove trailing empty strings if no limit + if limit < 0 + while result.length > 0 && result[-1] == "" + result.pop + end + end + result + end +end diff --git a/mrbgems/mruby-regexp/src/re_compile.c b/mrbgems/mruby-regexp/src/re_compile.c new file mode 100644 index 0000000000..edbb968908 --- /dev/null +++ b/mrbgems/mruby-regexp/src/re_compile.c @@ -0,0 +1,1000 @@ +/* +** re_compile.c - regexp pattern compiler +** +** Compiles a regular expression pattern string into bytecode +** for the NFA execution engine. +** +** See Copyright Notice in mruby.h +*/ + +#include "re_internal.h" +#include +#include + +/* Compiler state */ +typedef struct { + mrb_state *mrb; + const char *src; /* pattern source */ + const char *src_end; + const char *p; /* current position */ + re_inst *code; /* instruction array */ + uint32_t code_len; + uint32_t code_capa; + re_charclass *classes; + uint16_t num_classes; + uint16_t class_capa; + uint16_t num_captures; + uint32_t flags; + re_named_capture *named_captures; + uint16_t num_named; + mrb_bool has_backref; + mrb_bool needs_backtrack; + char *stripped; /* allocated buffer for x-mode preprocessing */ +} re_compiler; + +static void compile_alt(re_compiler *c); /* forward */ + +static void +compile_error(re_compiler *c, const char *msg) +{ + /* Format the message before freeing c->stripped (which may alias c->src + in extended mode). c->src is not NUL-terminated, so use %l with the + explicit length from c->src_end. */ + mrb_value emsg = mrb_format(c->mrb, "%s: /%l/", + msg, c->src, (size_t)(c->src_end - c->src)); + + /* Free compile buffers before raising, since mrb_exc_raise longjmps out + and the stack-local re_compiler is abandoned without a chance to clean + up. mrb_free doesn't trigger GC, so emsg stays valid across these. */ + mrb_free(c->mrb, c->code); + c->code = NULL; + mrb_free(c->mrb, c->classes); + c->classes = NULL; + mrb_free(c->mrb, c->named_captures); + c->named_captures = NULL; + if (c->stripped) mrb_free(c->mrb, c->stripped); + c->stripped = NULL; + + mrb_exc_raise(c->mrb, + mrb_exc_new_str(c->mrb, mrb_exc_get_id(c->mrb, MRB_SYM(RegexpError)), emsg)); +} + +static uint32_t +emit(re_compiler *c, uint8_t op, uint8_t a, uint16_t offset) +{ + if (c->code_len >= c->code_capa) { + c->code_capa = c->code_capa ? c->code_capa * 2 : 64; + c->code = (re_inst*)mrb_realloc(c->mrb, c->code, sizeof(re_inst) * c->code_capa); + } + uint32_t pos = c->code_len++; + c->code[pos].op = op; + c->code[pos].a = a; + c->code[pos].offset = offset; + return pos; +} + +static void +patch(re_compiler *c, uint32_t pos, uint16_t offset) +{ + c->code[pos].offset = offset; +} + +/* Insert an instruction at position `pos` by shifting code. + Adjusts all jump offsets >= pos by +1. */ +static void +insert_inst(re_compiler *c, uint32_t pos, uint8_t op, uint8_t a, uint16_t offset) +{ + emit(c, RE_JMP, 0, 0); /* grow array */ + uint32_t len = c->code_len - 1 - pos; + memmove(&c->code[pos + 1], &c->code[pos], sizeof(re_inst) * len); + c->code[pos].op = op; + c->code[pos].a = a; + c->code[pos].offset = offset; + + /* Fix jump targets that point past the insertion point. An offset equal + to `pos` already points to the inserted instruction's new location and + must not be bumped -- bumping it would shift the target to whatever + code got displaced by the insertion (e.g. the body of the quantified + atom), corrupting "skip past this atom" jumps emitted earlier. */ + for (uint32_t i = 0; i < c->code_len; i++) { + if (i == pos) continue; + switch (c->code[i].op) { + case RE_JMP: case RE_SPLIT: case RE_SPLITNG: + if (c->code[i].offset > pos && c->code[i].offset < 0xffff) { + c->code[i].offset++; + } + break; + default: + break; + } + } +} + +static int +peek(re_compiler *c) +{ + if (c->p >= c->src_end) return -1; + return (uint8_t)*c->p; +} + +static int +next_char(re_compiler *c) +{ + if (c->p >= c->src_end) return -1; + return (uint8_t)*c->p++; +} + +/* Class IDs are stored in re_inst.a (uint8_t), so at most 256 distinct + character classes can be encoded. Without this cap, class_capa + (uint16_t) overflows on doubling past 32768 (8 -> 16 -> ... -> 32768 + -> 0), mrb_realloc with size 0 returns NULL, and the next memset + crashes; even before that, the (uint8_t)id cast at emit sites would + silently alias different classes. */ +#define RE_MAX_CLASSES 256 + +static uint16_t +add_class(re_compiler *c) +{ + if (c->num_classes >= RE_MAX_CLASSES) { + compile_error(c, "too many character classes"); + } + if (c->num_classes >= c->class_capa) { + c->class_capa = c->class_capa ? c->class_capa * 2 : 8; + c->classes = (re_charclass*)mrb_realloc(c->mrb, c->classes, sizeof(re_charclass) * c->class_capa); + } + uint16_t id = c->num_classes++; + memset(&c->classes[id], 0, sizeof(re_charclass)); + return id; +} + +static void +class_set_bit(re_charclass *cc, uint8_t ch) +{ + if (ch < 128) { + cc->bitmap[ch >> 3] |= (1 << (ch & 7)); + } +} + +static void +class_set_range(re_charclass *cc, uint8_t lo, uint8_t hi) +{ + for (int i = lo; i <= hi; i++) { + class_set_bit(cc, (uint8_t)i); + } +} + +static void +class_add_shorthand(re_charclass *cc, int ch) +{ + switch (ch) { + case 'd': + class_set_range(cc, '0', '9'); + break; + case 'D': + class_set_range(cc, 0, '0'-1); + class_set_range(cc, '9'+1, 127); + cc->utf8_any = TRUE; + break; + case 'w': + class_set_range(cc, 'a', 'z'); + class_set_range(cc, 'A', 'Z'); + class_set_range(cc, '0', '9'); + class_set_bit(cc, '_'); + break; + case 'W': + for (int i = 0; i < 128; i++) { + if (!mrb_re_is_word_char(i)) class_set_bit(cc, (uint8_t)i); + } + cc->utf8_any = TRUE; + break; + case 's': + class_set_bit(cc, ' '); + class_set_bit(cc, '\t'); + class_set_bit(cc, '\n'); + class_set_bit(cc, '\r'); + class_set_bit(cc, '\f'); + class_set_bit(cc, '\v'); + break; + case 'S': + for (int i = 0; i < 128; i++) { + if (i != ' ' && i != '\t' && i != '\n' && i != '\r' && i != '\f' && i != '\v') + class_set_bit(cc, (uint8_t)i); + } + cc->utf8_any = TRUE; + break; + } +} + +static int +parse_escape(re_compiler *c) +{ + int ch = next_char(c); + if (ch < 0) compile_error(c, "trailing backslash"); + switch (ch) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case 'f': return '\f'; + case 'v': return '\v'; + case 'a': return '\a'; + case 'e': return 0x1b; + case 'b': return '\b'; /* backspace; only reachable inside [...] since the + top-level dispatcher emits RE_WBOUND for `\b` */ + default: return ch; /* literal: \., \\, \/, \(, etc. */ + } +} + +/* Parse [...] character class */ +static void +compile_charclass(re_compiler *c) +{ + uint16_t id = add_class(c); + re_charclass *cc = &c->classes[id]; + mrb_bool negated = FALSE; + + if (peek(c) == '^') { + next_char(c); + negated = TRUE; + } + + mrb_bool first = TRUE; + while (peek(c) != ']' || first) { + int ch; + + if (peek(c) < 0) compile_error(c, "unterminated character class"); + first = FALSE; + + if (peek(c) == '\\') { + next_char(c); + int esc = peek(c); + if (esc == 'd' || esc == 'D' || esc == 'w' || esc == 'W' || esc == 's' || esc == 'S') { + next_char(c); + class_add_shorthand(cc, esc); + continue; + } + ch = parse_escape(c); + } + else { + ch = next_char(c); + } + + /* check for range a-z */ + if (peek(c) == '-' && c->p + 1 < c->src_end && c->p[1] != ']') { + next_char(c); /* skip '-' */ + int hi; + if (peek(c) == '\\') { + next_char(c); + hi = parse_escape(c); + } + else { + hi = next_char(c); + } + if (ch < 128 && hi < 128) { + class_set_range(cc, (uint8_t)ch, (uint8_t)hi); + } + } + else { + if (ch < 128) class_set_bit(cc, (uint8_t)ch); + } + } + next_char(c); /* skip ']' */ + + cc->negated = negated; + emit(c, negated ? RE_NCLASS : RE_CLASS, (uint8_t)id, 0); +} + +/* Maximum value for {n}/{n,m} quantifiers. Each unit becomes (min-1) + + (max-min) emitted copies of the inner atom; the cap keeps both the + parse free of integer overflow and the bytecode size sane. */ +#define RE_MAX_REPEAT 32768 + +/* Parse {n}, {n,}, {n,m} quantifier. Returns min,max via pointers. */ +static mrb_bool +parse_quantifier(re_compiler *c, int *min_out, int *max_out) +{ + const char *save = c->p; + int min = 0, max = -1; + + while (peek(c) >= '0' && peek(c) <= '9') { + min = min * 10 + (next_char(c) - '0'); + if (min > RE_MAX_REPEAT) compile_error(c, "quantifier too large"); + } + if (peek(c) == ',') { + next_char(c); + if (peek(c) >= '0' && peek(c) <= '9') { + max = 0; + while (peek(c) >= '0' && peek(c) <= '9') { + max = max * 10 + (next_char(c) - '0'); + if (max > RE_MAX_REPEAT) compile_error(c, "quantifier too large"); + } + } + /* else max = -1 (unlimited) */ + } + else { + max = min; /* {n} means exactly n */ + } + if (peek(c) != '}') { + c->p = save; /* not a quantifier, treat { as literal */ + return FALSE; + } + next_char(c); /* skip '}' */ + *min_out = min; + *max_out = max; + return TRUE; +} + +/* + * Compute the fixed byte length consumed by bytecode in range [start, end). + * Returns -1 if the pattern has variable length (quantifiers, alternation + * with different-length branches, etc.). + * Used for lookbehind: we need to know exactly how far back to look. + */ +static int +compute_fixed_len(re_compiler *c, uint32_t start, uint32_t end) +{ + int len = 0; + uint32_t pc = start; + + while (pc < end) { + re_inst inst = c->code[pc]; + switch (inst.op) { + case RE_CHAR: + case RE_CLASS: + case RE_NCLASS: + len += 1; + pc++; + break; + case RE_ANY: + case RE_ANY_NL: + /* . matches one character which can be 1-4 bytes in UTF-8. + For ASCII-only mode this is 1 byte; for safety, only allow + if we can determine it's ASCII context. Return -1 for now. */ + return -1; + case RE_SAVE: + pc++; + break; /* zero-width */ + case RE_BOL: case RE_EOL: case RE_BOT: case RE_EOT: case RE_EOTNL: + case RE_WBOUND: case RE_NWBOUND: + pc++; + break; /* zero-width assertions */ + case RE_JMP: + pc = inst.offset; + break; + case RE_SPLIT: { + /* alternation: both branches must have the same fixed length */ + /* branch 1: pc+1 to next JMP before branch 2 */ + /* branch 2: inst.offset to ... */ + /* For simplicity, reject alternation in lookbehind */ + return -1; + } + case RE_MATCH: + return len; + default: + return -1; /* unknown/variable-length instruction */ + } + } + return len; +} + +/* Compile a single atom (character, class, group, etc.) */ +static void +compile_atom(re_compiler *c) +{ + int ch = peek(c); + + switch (ch) { + case '(': + { + next_char(c); + mrb_bool capturing = TRUE; + + const char *cap_name = NULL; + uint16_t cap_name_len = 0; + + if (peek(c) == '?' && c->p + 1 < c->src_end) { + if (c->p[1] == ':') { + next_char(c); next_char(c); /* skip ?: */ + capturing = FALSE; + } + else if (c->p[1] == '=' || c->p[1] == '!') { + /* lookahead (?=...) or (?!...) */ + mrb_bool negative = (c->p[1] == '!'); + next_char(c); next_char(c); /* skip ?= or ?! */ + uint32_t la_pos = emit(c, negative ? RE_NEG_LOOKAHEAD : RE_LOOKAHEAD, 0, 0); + compile_alt(c); + emit(c, RE_MATCH, 0, 0); /* end of lookahead sub-pattern */ + c->code[la_pos].offset = (uint16_t)c->code_len; /* patch: skip past sub-pattern */ + if (peek(c) != ')') compile_error(c, "unmatched '('"); + next_char(c); + c->needs_backtrack = TRUE; /* needs backtracking engine */ + break; /* done with this atom */ + } + else if (c->p[1] == '<' && c->p + 2 < c->src_end && (c->p[2] == '=' || c->p[2] == '!')) { + /* lookbehind (?<=...) or (?p[2] == '!'); + next_char(c); next_char(c); next_char(c); /* skip ?<= or ?code_len; + compile_alt(c); + emit(c, RE_MATCH, 0, 0); + c->code[lb_pos].offset = (uint16_t)c->code_len; + + /* compute fixed byte length of lookbehind sub-pattern */ + int fixed_len = compute_fixed_len(c, sub_start, c->code_len); + if (fixed_len < 0) { + compile_error(c, "lookbehind must be fixed length"); + } + if (fixed_len > 255) { + compile_error(c, "lookbehind too long (max 255 bytes)"); + } + c->code[lb_pos].a = (uint8_t)fixed_len; + + if (peek(c) != ')') compile_error(c, "unmatched '('"); + next_char(c); + c->needs_backtrack = TRUE; /* needs backtracking engine */ + break; + } + else if (c->p[1] == '<' && c->p + 2 < c->src_end && c->p[2] != '=' && c->p[2] != '!') { + next_char(c); next_char(c); /* skip ?< */ + cap_name = c->p; + while (peek(c) != '>' && peek(c) >= 0) next_char(c); + if (peek(c) != '>') compile_error(c, "unterminated named capture"); + cap_name_len = (uint16_t)(c->p - cap_name); + next_char(c); /* skip > */ + } + } + + uint16_t group = 0; + if (capturing) { + if (c->num_captures >= RE_MAX_CAPTURES) { + compile_error(c, "too many capture groups"); + } + group = c->num_captures++; + emit(c, RE_SAVE, 0, group * 2); + if (cap_name) { + /* register named capture */ + c->named_captures = (re_named_capture*)mrb_realloc(c->mrb, c->named_captures, + sizeof(re_named_capture) * (c->num_named + 1)); + c->named_captures[c->num_named].name = cap_name; + c->named_captures[c->num_named].name_len = cap_name_len; + c->named_captures[c->num_named].group = group; + c->num_named++; + } + } + + compile_alt(c); + + if (peek(c) != ')') compile_error(c, "unmatched '('"); + next_char(c); + + if (capturing) { + emit(c, RE_SAVE, 0, group * 2 + 1); + } + } + break; + + case '[': + next_char(c); + compile_charclass(c); + break; + + case '.': + next_char(c); + emit(c, (c->flags & RE_FLAG_DOTALL) ? RE_ANY_NL : RE_ANY, 0, 0); + break; + + case '^': + next_char(c); + emit(c, RE_BOL, 0, 0); + break; + + case '$': + next_char(c); + emit(c, RE_EOL, 0, 0); + break; + + case '\\': + next_char(c); + ch = peek(c); + if (ch >= '1' && ch <= '9') { + next_char(c); + emit(c, RE_BACKREF, (uint8_t)(ch - '0'), 0); + c->has_backref = TRUE; + } + else if (ch == 'd' || ch == 'D' || ch == 'w' || ch == 'W' || ch == 's' || ch == 'S') { + next_char(c); + uint16_t id = add_class(c); + class_add_shorthand(&c->classes[id], ch); + emit(c, (ch >= 'A' && ch <= 'Z') ? RE_NCLASS : RE_CLASS, (uint8_t)id, 0); + } + else if (ch == 'A') { + next_char(c); + emit(c, RE_BOT, 0, 0); + } + else if (ch == 'z') { + next_char(c); + emit(c, RE_EOT, 0, 0); + } + else if (ch == 'Z') { + next_char(c); + emit(c, RE_EOTNL, 0, 0); + } + else if (ch == 'b') { + next_char(c); + emit(c, RE_WBOUND, 0, 0); + } + else if (ch == 'B') { + next_char(c); + emit(c, RE_NWBOUND, 0, 0); + } + else { + ch = parse_escape(c); + if (c->flags & RE_FLAG_IGNORECASE) { + if (ch >= 'A' && ch <= 'Z') { + uint16_t id = add_class(c); + class_set_bit(&c->classes[id], (uint8_t)ch); + class_set_bit(&c->classes[id], (uint8_t)(ch + 32)); + emit(c, RE_CLASS, (uint8_t)id, 0); + break; + } + else if (ch >= 'a' && ch <= 'z') { + uint16_t id = add_class(c); + class_set_bit(&c->classes[id], (uint8_t)ch); + class_set_bit(&c->classes[id], (uint8_t)(ch - 32)); + emit(c, RE_CLASS, (uint8_t)id, 0); + break; + } + } + emit(c, RE_CHAR, (uint8_t)ch, 0); + } + break; + + default: + if (ch < 0 || ch == ')' || ch == '|' || ch == '*' || ch == '+' || ch == '?' || ch == '{') { + return; /* not an atom */ + } + next_char(c); + if ((c->flags & RE_FLAG_IGNORECASE) && ch < 128) { + if (ch >= 'A' && ch <= 'Z') { + uint16_t id = add_class(c); + class_set_bit(&c->classes[id], (uint8_t)ch); + class_set_bit(&c->classes[id], (uint8_t)(ch + 32)); + emit(c, RE_CLASS, (uint8_t)id, 0); + break; + } + else if (ch >= 'a' && ch <= 'z') { + uint16_t id = add_class(c); + class_set_bit(&c->classes[id], (uint8_t)ch); + class_set_bit(&c->classes[id], (uint8_t)(ch - 32)); + emit(c, RE_CLASS, (uint8_t)id, 0); + break; + } + } + emit(c, RE_CHAR, (uint8_t)ch, 0); + break; + } +} + +/* Compile atom with quantifiers (*, +, ?, {n,m}) */ +static void +compile_quantified(re_compiler *c) +{ + uint32_t start = c->code_len; + compile_atom(c); + if (c->code_len == start) return; /* no atom emitted */ + + int ch = peek(c); + if (ch == '*' || ch == '+' || ch == '?') { + next_char(c); + mrb_bool nongreedy = (peek(c) == '?'); + if (nongreedy) { + next_char(c); + c->needs_backtrack = TRUE; + } + + + if (ch == '*') { + /* e* → L: SPLIT(body, end); body; JMP L; end: + SPLIT offset = end (after JMP), patched after JMP is emitted */ + insert_inst(c, start, nongreedy ? RE_SPLITNG : RE_SPLIT, 0, 0); + emit(c, RE_JMP, 0, start); + c->code[start].offset = (uint16_t)c->code_len; /* patch: skip to end */ + } + else if (ch == '+') { + /* e+ → body; SPLIT/SPLITNG(start) + SPLIT: first=pc+1(end), second=offset(start) → non-greedy + SPLITNG: first=offset(start), second=pc+1(end) → greedy */ + emit(c, nongreedy ? RE_SPLIT : RE_SPLITNG, 0, start); + } + else { /* ? */ + /* e? → SPLIT(body, end); body; end: */ + insert_inst(c, start, nongreedy ? RE_SPLITNG : RE_SPLIT, 0, 0); + c->code[start].offset = (uint16_t)c->code_len; /* patch: skip to end */ + } + } + else if (ch == '{') { + const char *save = c->p; + next_char(c); + int min, max; + if (!parse_quantifier(c, &min, &max)) { + c->p = save; + return; /* not a quantifier */ + } + mrb_bool nongreedy = (peek(c) == '?'); + if (nongreedy) { + next_char(c); + c->needs_backtrack = TRUE; + } + + /* For {n,m}: repeat atom min times, then optional (max-min) times */ + uint32_t atom_end = c->code_len; + uint32_t atom_size = atom_end - start; + + /* First, we have one copy already. We need min-1 more mandatory copies. */ + for (int i = 1; i < min; i++) { + for (uint32_t j = 0; j < atom_size; j++) { + emit(c, c->code[start + j].op, c->code[start + j].a, c->code[start + j].offset); + } + } + /* Then optional copies */ + if (max < 0) { + /* {n,} = min copies + * */ + uint32_t loop_start = c->code_len; + uint32_t split_pos = emit(c, nongreedy ? RE_SPLITNG : RE_SPLIT, 0, 0); + for (uint32_t j = 0; j < atom_size; j++) { + emit(c, c->code[start + j].op, c->code[start + j].a, c->code[start + j].offset); + } + emit(c, RE_JMP, 0, loop_start); + patch(c, split_pos, c->code_len); + } + else { + for (int i = min; i < max; i++) { + uint32_t split_pos = emit(c, nongreedy ? RE_SPLITNG : RE_SPLIT, 0, 0); + for (uint32_t j = 0; j < atom_size; j++) { + emit(c, c->code[start + j].op, c->code[start + j].a, c->code[start + j].offset); + } + patch(c, split_pos, c->code_len); + } + } + } +} + +/* Compile a sequence of quantified atoms */ +static void +compile_seq(re_compiler *c) +{ + while (peek(c) >= 0 && peek(c) != ')' && peek(c) != '|') { + compile_quantified(c); + } +} + +/* Compile alternation: seq | seq | ... */ +static void +compile_alt(re_compiler *c) +{ + uint32_t alt_start = c->code_len; + compile_seq(c); + + if (peek(c) != '|') return; + + /* a|b → SPLIT L1 L2; L1: a; JMP END; L2: b; END: + We need to insert SPLIT before already-emitted code for first alt. + Strategy: emit JMP after first alt, then for each subsequent alt, + insert a SPLIT before it by shifting code. */ + + /* Collect all alternatives, then emit SPLIT chain at the end. + This avoids insert_inst offset corruption for multi-way alternation. */ + uint32_t alt_starts[64]; /* start positions of each alternative */ + int num_alts = 0; + alt_starts[num_alts++] = alt_start; + + while (peek(c) == '|') { + next_char(c); + emit(c, RE_JMP, 0, 0); /* placeholder: jump to end */ + alt_starts[num_alts++] = c->code_len; + if (num_alts >= 64) compile_error(c, "too many alternatives"); + compile_seq(c); + } + + if (num_alts <= 1) return; /* shouldn't happen, but safety */ + + /* Now insert SPLIT chain before the alternatives. + For n alternatives: n-1 SPLIT instructions, each pointing to + their respective alternative. */ + uint32_t split_count = (uint32_t)(num_alts - 1); + /* Insert split_count instructions at alt_starts[0] */ + for (uint32_t i = 0; i < split_count; i++) { + insert_inst(c, alt_starts[0], RE_JMP, 0, 0); /* placeholder */ + /* adjust all alt_starts by +1 due to insertion */ + for (int j = 0; j < num_alts; j++) { + alt_starts[j]++; + } + } + + /* Now set up SPLIT chain: each SPLIT tries next instruction or jumps to alt */ + for (uint32_t i = 0; i < split_count; i++) { + uint32_t pos = alt_starts[0] - split_count + i; + c->code[pos].op = RE_SPLIT; + c->code[pos].a = 0; + c->code[pos].offset = (uint16_t)alt_starts[i + 1]; + } + + /* Patch JMPs (they are right before each alt_starts[1..n-1]) to point to end */ + uint32_t end = c->code_len; + for (int i = 1; i < num_alts; i++) { + uint32_t jmp_pos = alt_starts[i] - 1; + c->code[jmp_pos].op = RE_JMP; + c->code[jmp_pos].offset = (uint16_t)end; + } +} + +/* + * Strip whitespace and #comments for extended mode (/x flag). + * Whitespace inside [...] character classes is preserved. + * Escaped characters (\ followed by anything) are preserved. + */ +static char* +strip_extended(mrb_state *mrb, const char *src, mrb_int len, mrb_int *out_len) +{ + char *buf = (char*)mrb_malloc(mrb, len); + mrb_int o = 0; + mrb_bool in_class = FALSE; + const char *end = src + len; + + while (src < end) { + char ch = *src; + if (ch == '\\' && src + 1 < end) { + buf[o++] = *src++; + buf[o++] = *src++; + continue; + } + if (in_class) { + if (ch == ']') in_class = FALSE; + buf[o++] = *src++; + continue; + } + if (ch == '[') { + in_class = TRUE; + buf[o++] = *src++; + continue; + } + if (ch == '#') { + /* skip to end of line */ + while (src < end && *src != '\n') src++; + continue; + } + if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\f' || ch == '\v') { + src++; + continue; + } + buf[o++] = *src++; + } + *out_len = o; + return buf; +} + +/* + * Compute the set of bytes that could be the first consumed byte of a match. + * Walks bytecode from pc=0, following epsilon transitions (SAVE, JMP, SPLIT). + * Returns TRUE if the set is narrower than "any byte" (i.e., useful for skip). + */ +static mrb_bool +first_set_walk(const re_inst *code, uint32_t code_len, + const re_charclass *classes, uint32_t pc, + uint8_t *bm, uint8_t *seen) +{ + while (pc < code_len) { + if (seen[pc]) return TRUE; /* already visited */ + seen[pc] = 1; + switch (code[pc].op) { + case RE_SAVE: + case RE_BOL: case RE_EOL: case RE_BOT: case RE_EOT: case RE_EOTNL: + case RE_WBOUND: case RE_NWBOUND: + pc++; + continue; /* zero-width, keep walking */ + case RE_JMP: + pc = code[pc].offset; + continue; + case RE_SPLIT: + /* both branches: pc+1 and offset */ + if (!first_set_walk(code, code_len, classes, code[pc].offset, bm, seen)) + return FALSE; + pc++; + continue; + case RE_SPLITNG: + if (!first_set_walk(code, code_len, classes, pc + 1, bm, seen)) + return FALSE; + pc = code[pc].offset; + continue; + case RE_CHAR: + if (code[pc].a >= 128) return FALSE; /* non-ASCII: bm covers ASCII only */ + bm[code[pc].a >> 3] |= (1 << (code[pc].a & 7)); + return TRUE; + case RE_CLASS: { + const re_charclass *cc = &classes[code[pc].a]; + for (int i = 0; i < 16; i++) bm[i] |= cc->bitmap[i]; + if (cc->utf8_any) return FALSE; /* non-ASCII possible */ + return TRUE; + } + case RE_NCLASS: { + /* negated class: complement of bitmap. Too many bits; not useful. */ + return FALSE; + } + case RE_ANY: case RE_ANY_NL: + return FALSE; /* any byte possible */ + case RE_MATCH: + /* Reaching MATCH via epsilon transitions means the regex can match + zero characters at any position. Skipping bytes that aren't in the + first-byte set would skip past valid empty-match positions, so the + optimization isn't safe -- bail out and accept any starting byte. */ + return FALSE; + default: + return FALSE; + } + } + /* Walked off the end without hitting MATCH or a consuming op. Treat as + empty-matchable, same as RE_MATCH. */ + return FALSE; +} + +static mrb_bool +compute_first_set(const re_inst *code, uint32_t code_len, + const re_charclass *classes, uint8_t *bm) +{ + uint8_t seen[4096]; + if (code_len >= sizeof(seen)) return FALSE; /* pattern too large */ + memset(seen, 0, code_len + 1); + if (!first_set_walk(code, code_len, classes, 0, bm, seen)) + return FALSE; + /* Check if bitmap is all-ones (no benefit to skip) */ + int set_bits = 0; + for (int i = 0; i < 16; i++) { + for (int b = 0; b < 8; b++) { + if (bm[i] & (1 << b)) set_bits++; + } + } + return set_bits < 96; /* useful only if fewer than 75% of bytes match */ +} + +mrb_regexp_pattern* +mrb_re_compile(mrb_state *mrb, const char *pattern, mrb_int len, uint32_t flags) +{ + re_compiler c; + memset(&c, 0, sizeof(c)); + + if (flags & RE_FLAG_EXTENDED) { + mrb_int slen; + c.stripped = strip_extended(mrb, pattern, len, &slen); + pattern = c.stripped; + len = slen; + } + c.mrb = mrb; + c.src = pattern; + c.src_end = pattern + len; + c.p = pattern; + c.flags = flags; + c.num_captures = 1; /* group 0 = whole match */ + + /* group 0 start */ + emit(&c, RE_SAVE, 0, 0); + + compile_alt(&c); + + if (c.p < c.src_end) { + compile_error(&c, "unmatched ')'"); + } + + /* group 0 end */ + emit(&c, RE_SAVE, 0, 1); + emit(&c, RE_MATCH, 0, 0); + + mrb_regexp_pattern *pat = (mrb_regexp_pattern*)mrb_malloc(mrb, sizeof(mrb_regexp_pattern)); + pat->code = c.code; + pat->code_len = c.code_len; + pat->classes = c.classes; + pat->num_classes = c.num_classes; + pat->num_captures = c.num_captures; + pat->flags = flags; + pat->named_captures = c.named_captures; + pat->named_arena = NULL; + pat->num_named = c.num_named; + + /* Copy capture names into an owned arena. Until this point the names + point into the pattern source (or into c.stripped, which gets freed + below in /x mode). After this loop the regexp owns its names. */ + if (c.num_named > 0) { + size_t total = 0; + for (uint16_t i = 0; i < c.num_named; i++) total += c.named_captures[i].name_len; + if (total > 0) { + pat->named_arena = (char*)mrb_malloc(mrb, total); + size_t off = 0; + for (uint16_t i = 0; i < c.num_named; i++) { + uint16_t n = c.named_captures[i].name_len; + memcpy(pat->named_arena + off, c.named_captures[i].name, n); + pat->named_captures[i].name = pat->named_arena + off; + off += n; + } + } + } + pat->has_backref = c.has_backref; + pat->needs_backtrack = c.needs_backtrack; + + /* Extract literal prefix for fast search skip. + Walk bytecode from the start, skipping SAVE, collecting RE_CHAR. */ + { + uint8_t pbuf[256]; + int plen = 0; + for (uint32_t i = 0; i < pat->code_len && plen < 255; i++) { + if (pat->code[i].op == RE_SAVE) continue; + if (pat->code[i].op == RE_CHAR) { + pbuf[plen++] = pat->code[i].a; + } + else break; + } + if (plen > 0) { + pat->prefix = (uint8_t*)mrb_malloc(mrb, plen); + memcpy(pat->prefix, pbuf, plen); + pat->prefix_len = (uint8_t)plen; + } + else { + pat->prefix = NULL; + pat->prefix_len = 0; + } + } + + /* Check if pattern is pure literal: SAVE CHAR* SAVE MATCH only. + prefix_len already holds the literal char count if so. */ + pat->is_literal = FALSE; + if (pat->prefix_len > 0 && pat->num_captures == 1 && + !pat->has_backref && !pat->needs_backtrack) { + /* bytecode should be: SAVE(0), CHAR*N, SAVE(1), MATCH + = 2 + prefix_len + 2 = prefix_len + 2 instructions + (SAVE(0) at 0, CHARs at 1..N, SAVE(1) at N+1, MATCH at N+2) */ + if (pat->code_len == (uint32_t)(pat->prefix_len + 3) && + pat->code[0].op == RE_SAVE && + pat->code[pat->code_len - 2].op == RE_SAVE && + pat->code[pat->code_len - 1].op == RE_MATCH) { + pat->is_literal = TRUE; + } + } + + /* Compute first-byte bitmap: set of bytes that could start a match. + Used when prefix is empty (e.g. alternation, character class patterns). */ + { + uint8_t bm[16]; + memset(bm, 0, sizeof(bm)); + pat->has_first_bytes = compute_first_set(pat->code, pat->code_len, pat->classes, bm); + if (pat->has_first_bytes) { + memcpy(pat->first_bytes, bm, 16); + } + } + + /* Pre-allocate VM state cache for pike_vm */ + { + int list_capa = (int)pat->code_len * 2 + 16; + pat->cached_visited = (uint32_t*)mrb_calloc(mrb, pat->code_len + 1, sizeof(uint32_t)); + pat->cached_threads[0] = mrb_malloc(mrb, sizeof(re_thread_cache) * list_capa); + pat->cached_threads[1] = mrb_malloc(mrb, sizeof(re_thread_cache) * list_capa); + pat->cached_list_capa = list_capa; + pat->cache_in_use = FALSE; + } + + if (c.stripped) mrb_free(mrb, c.stripped); + return pat; +} + +void +mrb_re_free(mrb_state *mrb, mrb_regexp_pattern *pat) +{ + if (pat) { + mrb_free(mrb, pat->code); + mrb_free(mrb, pat->classes); + mrb_free(mrb, pat->named_captures); + mrb_free(mrb, pat->named_arena); + mrb_free(mrb, pat->prefix); + mrb_free(mrb, pat->cached_visited); + mrb_free(mrb, pat->cached_threads[0]); + mrb_free(mrb, pat->cached_threads[1]); + mrb_free(mrb, pat); + } +} diff --git a/mrbgems/mruby-regexp/src/re_exec.c b/mrbgems/mruby-regexp/src/re_exec.c new file mode 100644 index 0000000000..8f6e047319 --- /dev/null +++ b/mrbgems/mruby-regexp/src/re_exec.c @@ -0,0 +1,625 @@ +/* +** re_exec.c - NFA execution engine (Pike VM) +** +** Executes compiled regexp bytecode using Thompson/Pike NFA simulation. +** O(pattern * text) time complexity guarantees ReDoS resistance. +** +** See Copyright Notice in mruby.h +*/ + +#include "re_internal.h" +#include + +/* + * Skip to the next position where the pattern's literal prefix could match. + * Uses memchr on the first byte for fast scanning, then verifies the rest. + * Returns the found position, or NULL if no match is possible. + */ +static const char* +skip_to_prefix(const mrb_regexp_pattern *pat, const char *sp, const char *str_end) +{ + if (pat->prefix_len == 0) return sp; + + uint8_t first = pat->prefix[0]; + int plen = pat->prefix_len; + + while (sp + plen <= str_end) { + const char *found = (const char*)memchr(sp, first, str_end - sp); + if (!found || found + plen > str_end) return NULL; + if (plen == 1 || memcmp(found + 1, pat->prefix + 1, plen - 1) == 0) { + return found; + } + sp = found + 1; + } + return NULL; +} + +/* Check if a byte is in the first-byte bitmap */ +#define FIRST_BYTE_OK(pat, ch) \ + ((ch) >= 128 || ((pat)->first_bytes[(ch) >> 3] & (1 << ((ch) & 7)))) + +/* Check if character matches a character class */ +static mrb_bool +class_match(const re_charclass *cc, uint8_t ch) +{ + if (ch >= 128) return cc->utf8_any; + return (cc->bitmap[ch >> 3] >> (ch & 7)) & 1; +} + +/* + * Pike VM with optimized thread storage. + * + * Key optimizations vs naive approach: + * - Captures stored in a flat pool, sized to actual ncap (not RE_MAX_CAPTURES) + * - Generation counter for visited[] eliminates per-step memset + * - Threads reference captures by pool index, avoiding 260-byte struct copies + */ + +typedef re_thread_cache re_thread; + +typedef struct { + re_thread *threads; + int count; + int capa; +} re_threadlist; + +/* All Pike VM state */ +typedef struct { + mrb_state *mrb; + const mrb_regexp_pattern *pat; + int ncap; /* actual capture count (num_captures * 2) */ + int *cap_pool; /* flat: cap_pool[slot * ncap .. (slot+1) * ncap) */ + int pool_next; /* next free slot */ + int pool_capa; /* total slots allocated */ + uint32_t *visited; /* generation-based */ + uint32_t gen; + const char *str; + const char *str_end; + mrb_bool matched; + mrb_bool match_only; /* true: skip capture tracking (match? path) */ + int *result_caps; /* best match (ncap ints) */ +} pike_state; + +static int +pool_alloc(pike_state *s) +{ + if (s->pool_next >= s->pool_capa) { + int new_capa = s->pool_capa * 2; + s->cap_pool = (int*)mrb_realloc(s->mrb, s->cap_pool, + sizeof(int) * new_capa * s->ncap); + s->pool_capa = new_capa; + } + return s->pool_next++; +} + +static int +pool_copy(pike_state *s, int src_slot) +{ + int dst = pool_alloc(s); + memcpy(&s->cap_pool[dst * s->ncap], + &s->cap_pool[src_slot * s->ncap], + sizeof(int) * s->ncap); + return dst; +} + +#define CAP(s, slot) (&(s)->cap_pool[(slot) * (s)->ncap]) + +/* Add thread following epsilon transitions. + visited[pc] == gen means already visited this step. */ +static void +add_thread(pike_state *s, re_threadlist *list, + uint32_t pc, int cap_slot, const char *sp) +{ + for (;;) { + if (pc >= s->pat->code_len) return; + if (s->visited[pc] == s->gen) return; + s->visited[pc] = s->gen; + + re_inst inst = s->pat->code[pc]; + switch (inst.op) { + case RE_JMP: + pc = inst.offset; + continue; + + case RE_SPLIT: + { + int cp = s->match_only ? 0 : pool_copy(s, cap_slot); + add_thread(s, list, inst.offset, cp, sp); + } + pc++; + continue; + + case RE_SPLITNG: + { + int cp = s->match_only ? 0 : pool_copy(s, cap_slot); + add_thread(s, list, pc + 1, cp, sp); + } + pc = inst.offset; + continue; + + case RE_SAVE: + if (!s->match_only) { + CAP(s, cap_slot)[inst.offset] = (int)(sp - s->str); + } + pc++; + continue; + + case RE_BOL: + if (sp == s->str || ((s->pat->flags & RE_FLAG_MULTILINE) && sp > s->str && sp[-1] == '\n')) { + pc++; continue; + } + return; + + case RE_EOL: + if (sp == s->str_end || ((s->pat->flags & RE_FLAG_MULTILINE) && *sp == '\n')) { + pc++; continue; + } + return; + + case RE_BOT: + if (sp == s->str) { pc++; continue; } + return; + + case RE_EOT: + if (sp == s->str_end) { pc++; continue; } + return; + + case RE_EOTNL: + if (sp == s->str_end || (sp + 1 == s->str_end && *sp == '\n')) { pc++; continue; } + return; + + case RE_WBOUND: + { + mrb_bool before = (sp > s->str) && mrb_re_is_word_char((uint8_t)sp[-1]); + mrb_bool after = (sp < s->str_end) && mrb_re_is_word_char((uint8_t)*sp); + if (before != after) { pc++; continue; } + } + return; + + case RE_NWBOUND: + { + mrb_bool before = (sp > s->str) && mrb_re_is_word_char((uint8_t)sp[-1]); + mrb_bool after = (sp < s->str_end) && mrb_re_is_word_char((uint8_t)*sp); + if (before == after) { pc++; continue; } + } + return; + + case RE_MATCH: + s->matched = TRUE; + if (s->result_caps) { + memcpy(s->result_caps, CAP(s, cap_slot), sizeof(int) * s->ncap); + } + return; + + default: + break; + } + break; + } + + if (list->count < list->capa) { + re_thread *t = &list->threads[list->count++]; + t->pc = pc; + t->cap_slot = cap_slot; + } +} + +static int +pike_vm(mrb_state *mrb, const mrb_regexp_pattern *pat, + const char *str, mrb_int len, mrb_int start, + int *captures, int captures_size) +{ + const char *sp = str + start; + const char *str_end = str + len; + int ncap = pat->num_captures * 2; + if (ncap == 0) ncap = 2; + + int list_capa = (int)pat->code_len * 2 + 16; + + mrb_bool match_only = (captures == NULL || captures_size == 0); + + /* Use cached VM state if available (avoids malloc per call) */ + mrb_regexp_pattern *mpat = (mrb_regexp_pattern*)pat; /* for cache_in_use flag */ + mrb_bool use_cache = !mpat->cache_in_use && mpat->cached_visited != NULL; + if (use_cache) mpat->cache_in_use = TRUE; + + pike_state s; + s.mrb = mrb; + s.pat = pat; + s.ncap = ncap; + s.str = str; + s.str_end = str_end; + s.matched = FALSE; + s.match_only = match_only; + s.gen = 1; + if (match_only) { + s.pool_capa = 1; + s.pool_next = 0; + s.cap_pool = (int*)mrb_malloc(mrb, sizeof(int) * ncap); + s.result_caps = NULL; + } + else { + s.pool_capa = list_capa * 2; + s.pool_next = 0; + s.cap_pool = (int*)mrb_malloc(mrb, sizeof(int) * s.pool_capa * ncap); + s.result_caps = (int*)mrb_malloc(mrb, sizeof(int) * ncap); + memset(s.result_caps, -1, sizeof(int) * ncap); + } + + re_threadlist curr, next; + if (use_cache) { + s.visited = mpat->cached_visited; + memset(s.visited, 0, sizeof(uint32_t) * (pat->code_len + 1)); + curr.threads = (re_thread*)mpat->cached_threads[0]; + next.threads = (re_thread*)mpat->cached_threads[1]; + curr.capa = next.capa = mpat->cached_list_capa; + } + else { + s.visited = (uint32_t*)mrb_calloc(mrb, pat->code_len + 1, sizeof(uint32_t)); + curr.threads = (re_thread*)mrb_malloc(mrb, sizeof(re_thread) * list_capa); + next.threads = (re_thread*)mrb_malloc(mrb, sizeof(re_thread) * list_capa); + curr.capa = next.capa = list_capa; + } + curr.count = next.count = 0; + + for (; sp <= str_end; sp++) { + if (!s.matched) { + /* Skip ahead when no active threads */ + if (curr.count == 0) { + if (pat->prefix_len > 0) { + const char *skip = skip_to_prefix(pat, sp, str_end); + if (!skip) break; + sp = skip; + } + else if (pat->has_first_bytes) { + while (sp < str_end && !FIRST_BYTE_OK(pat, (uint8_t)*sp)) sp++; + if (sp > str_end) break; + } + } + int slot = match_only ? 0 : pool_alloc(&s); + if (!match_only) memset(CAP(&s, slot), -1, sizeof(int) * ncap); + s.gen++; + add_thread(&s, &curr, 0, slot, sp); + if (s.matched && curr.count == 0) break; + } + + if (sp >= str_end) break; + + if (!match_only) { + /* Compact: copy live thread captures to the front of the pool. */ + for (int i = 0; i < curr.count; i++) { + if (curr.threads[i].cap_slot != i) { + memcpy(CAP(&s, i), CAP(&s, curr.threads[i].cap_slot), + sizeof(int) * ncap); + curr.threads[i].cap_slot = i; + } + } + s.pool_next = curr.count; + } + + s.gen++; + next.count = 0; + + int ch = (uint8_t)*sp; + int advance = mrb_re_utf8_charlen(sp, str_end); + + for (int i = 0; i < curr.count; i++) { + re_thread *th = &curr.threads[i]; + if (th->pc >= pat->code_len) continue; + + re_inst inst = pat->code[th->pc]; + switch (inst.op) { + case RE_CHAR: + if (ch == inst.a) { + int cp = match_only ? 0 : pool_copy(&s, th->cap_slot); + add_thread(&s, &next, th->pc + 1, cp, sp + 1); + } + break; + + case RE_ANY: + if (ch != '\n') { + int cp = match_only ? 0 : pool_copy(&s, th->cap_slot); + add_thread(&s, &next, th->pc + 1, cp, sp + advance); + } + break; + + case RE_ANY_NL: + { + int cp = match_only ? 0 : pool_copy(&s, th->cap_slot); + add_thread(&s, &next, th->pc + 1, cp, sp + advance); + } + break; + + case RE_CLASS: + if (class_match(&pat->classes[inst.a], (uint8_t)ch)) { + int cp = match_only ? 0 : pool_copy(&s, th->cap_slot); + add_thread(&s, &next, th->pc + 1, cp, sp + advance); + } + break; + + case RE_NCLASS: + if (!class_match(&pat->classes[inst.a], (uint8_t)ch)) { + int cp = match_only ? 0 : pool_copy(&s, th->cap_slot); + add_thread(&s, &next, th->pc + 1, cp, sp + advance); + } + break; + + default: + break; + } + } + + /* swap curr and next */ + { + re_threadlist tmp = curr; + curr = next; + next = tmp; + } + + if (s.matched && curr.count == 0) break; + } + + int ret = 0; + if (s.matched) { + if (captures && s.result_caps) { + int copy = ncap < captures_size ? ncap : captures_size; + memcpy(captures, s.result_caps, sizeof(int) * copy); + } + ret = ncap > 0 ? ncap : 1; + } + + if (use_cache) { + mpat->cache_in_use = FALSE; + } + else { + mrb_free(mrb, curr.threads); + mrb_free(mrb, next.threads); + mrb_free(mrb, s.visited); + } + mrb_free(mrb, s.cap_pool); + if (s.result_caps) mrb_free(mrb, s.result_caps); + + return ret; +} + +/* + * Backtracking engine for patterns with backreferences. + * Step-limited to prevent ReDoS. + */ +static mrb_bool +bt_match(const mrb_regexp_pattern *pat, const char *str, const char *str_end, + const char *sp, uint32_t pc, int *captures, int ncap, int *steps, + int depth) +{ + if (depth > MRB_REGEXP_RECURSION_LIMIT) return FALSE; + while (pc < pat->code_len) { + if (++(*steps) > MRB_REGEXP_STEP_LIMIT) return FALSE; + + re_inst inst = pat->code[pc]; + switch (inst.op) { + case RE_CHAR: + if (sp >= str_end || (uint8_t)*sp != inst.a) return FALSE; + sp++; pc++; + break; + + case RE_ANY: + if (sp >= str_end || *sp == '\n') return FALSE; + sp += mrb_re_utf8_charlen(sp, str_end); pc++; + break; + + case RE_ANY_NL: + if (sp >= str_end) return FALSE; + sp += mrb_re_utf8_charlen(sp, str_end); pc++; + break; + + case RE_CLASS: + if (sp >= str_end || !class_match(&pat->classes[inst.a], (uint8_t)*sp)) return FALSE; + sp += mrb_re_utf8_charlen(sp, str_end); pc++; + break; + + case RE_NCLASS: + if (sp >= str_end || class_match(&pat->classes[inst.a], (uint8_t)*sp)) return FALSE; + sp += mrb_re_utf8_charlen(sp, str_end); pc++; + break; + + case RE_MATCH: + return TRUE; + + case RE_JMP: + pc = inst.offset; + break; + + case RE_SPLIT: + if (bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps, depth + 1)) return TRUE; + pc = inst.offset; + break; + + case RE_SPLITNG: + if (bt_match(pat, str, str_end, sp, inst.offset, captures, ncap, steps, depth + 1)) return TRUE; + pc++; + break; + + case RE_SAVE: + { + int slot = inst.offset; + if (slot < ncap) { + int old = captures[slot]; + captures[slot] = (int)(sp - str); + if (bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps, depth + 1)) return TRUE; + captures[slot] = old; + } + return FALSE; + } + + case RE_BOL: + if (sp != str && !(pat->flags & RE_FLAG_MULTILINE && sp > str && sp[-1] == '\n')) return FALSE; + pc++; + break; + + case RE_EOL: + if (sp != str_end && !(pat->flags & RE_FLAG_MULTILINE && *sp == '\n')) return FALSE; + pc++; + break; + + case RE_BOT: + if (sp != str) return FALSE; + pc++; + break; + + case RE_EOT: + if (sp != str_end) return FALSE; + pc++; + break; + + case RE_WBOUND: + { + mrb_bool before = (sp > str) && mrb_re_is_word_char((uint8_t)sp[-1]); + mrb_bool after = (sp < str_end) && mrb_re_is_word_char((uint8_t)*sp); + if (before == after) return FALSE; + } + pc++; + break; + + case RE_NWBOUND: + { + mrb_bool before = (sp > str) && mrb_re_is_word_char((uint8_t)sp[-1]); + mrb_bool after = (sp < str_end) && mrb_re_is_word_char((uint8_t)*sp); + if (before != after) return FALSE; + } + pc++; + break; + + case RE_BACKREF: + { + int group = inst.a; + if (group * 2 + 1 >= ncap) return FALSE; + int gs = captures[group * 2]; + int ge = captures[group * 2 + 1]; + if (gs < 0 || ge < 0) return FALSE; + int blen = ge - gs; + if (sp + blen > str_end) return FALSE; + if (memcmp(sp, str + gs, blen) != 0) return FALSE; + sp += blen; + pc++; + } + break; + + case RE_LOOKAHEAD: + if (!bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps, depth + 1)) + return FALSE; + pc = inst.offset; + break; + + case RE_NEG_LOOKAHEAD: + if (bt_match(pat, str, str_end, sp, pc + 1, captures, ncap, steps, depth + 1)) + return FALSE; + pc = inst.offset; + break; + + case RE_LOOKBEHIND: + { + int lb_len = inst.a; + if (sp - str < lb_len) return FALSE; /* not enough text before */ + if (!bt_match(pat, str, str_end, sp - lb_len, pc + 1, captures, ncap, steps, depth + 1)) + return FALSE; + pc = inst.offset; + } + break; + + case RE_NEG_LOOKBEHIND: + { + int lb_len = inst.a; + if (sp - str >= lb_len) { + if (bt_match(pat, str, str_end, sp - lb_len, pc + 1, captures, ncap, steps, depth + 1)) + return FALSE; + } + /* if not enough text before, negative lookbehind succeeds */ + pc = inst.offset; + } + break; + + default: + return FALSE; + } + } + return FALSE; +} + +static int +backtrack_exec(mrb_state *mrb, const mrb_regexp_pattern *pat, + const char *str, mrb_int len, mrb_int start, + int *captures, int captures_size) +{ + const char *str_end = str + len; + int ncap = pat->num_captures * 2; + if (ncap == 0) ncap = 2; + + int *caps = (int*)mrb_malloc(mrb, sizeof(int) * ncap); + + for (const char *sp = str + start; sp <= str_end; sp++) { + /* Skip ahead using literal prefix or first-byte bitmap */ + if (pat->prefix_len > 0) { + const char *skip = skip_to_prefix(pat, sp, str_end); + if (!skip) break; + sp = skip; + } + else if (pat->has_first_bytes) { + while (sp < str_end && !FIRST_BYTE_OK(pat, (uint8_t)*sp)) sp++; + if (sp > str_end) break; + } + memset(caps, -1, sizeof(int) * ncap); + int steps = 0; + + if (bt_match(pat, str, str_end, sp, 0, caps, ncap, &steps, 0)) { + if (captures) { + int copy = ncap < captures_size ? ncap : captures_size; + memcpy(captures, caps, sizeof(int) * copy); + } + mrb_free(mrb, caps); + return ncap > 0 ? ncap : 1; + } + } + mrb_free(mrb, caps); + return 0; +} + +/* Fast path for pure literal patterns: use memchr+memcmp, no NFA needed */ +static int +literal_exec(const mrb_regexp_pattern *pat, + const char *str, mrb_int len, mrb_int start, + int *captures, int captures_size) +{ + const char *sp = str + start; + const char *str_end = str + len; + int plen = pat->prefix_len; + + while (sp + plen <= str_end) { + const char *found = (const char*)memchr(sp, pat->prefix[0], str_end - sp); + if (!found || found + plen > str_end) return 0; + if (plen == 1 || memcmp(found + 1, pat->prefix + 1, plen - 1) == 0) { + /* match found */ + if (captures && captures_size >= 2) { + captures[0] = (int)(found - str); + captures[1] = (int)(found - str) + plen; + } + return 2; /* group 0 start/end */ + } + sp = found + 1; + } + return 0; +} + +/* Public entry point */ +int +mrb_re_exec(mrb_state *mrb, const mrb_regexp_pattern *pat, + const char *str, mrb_int len, mrb_int start, + int *captures, int captures_size) +{ + if (pat->is_literal) { + return literal_exec(pat, str, len, start, captures, captures_size); + } + if (pat->has_backref || pat->needs_backtrack) { + return backtrack_exec(mrb, pat, str, len, start, captures, captures_size); + } + return pike_vm(mrb, pat, str, len, start, captures, captures_size); +} diff --git a/mrbgems/mruby-regexp/src/re_utf8.c b/mrbgems/mruby-regexp/src/re_utf8.c new file mode 100644 index 0000000000..148d598586 --- /dev/null +++ b/mrbgems/mruby-regexp/src/re_utf8.c @@ -0,0 +1,76 @@ +/* +** re_utf8.c - UTF-8 utility functions for regexp engine +** +** See Copyright Notice in mruby.h +*/ + +#include "re_internal.h" + +/* Return byte length of UTF-8 character at s. + Returns 1 for invalid sequences (treat as single byte). */ +int +mrb_re_utf8_charlen(const char *s, const char *end) +{ + uint8_t c = (uint8_t)*s; + int len; + + if (c < 0x80) return 1; + else if (c < 0xc0) return 1; /* invalid continuation */ + else if (c < 0xe0) len = 2; + else if (c < 0xf0) len = 3; + else if (c < 0xf8) len = 4; + else return 1; /* invalid */ + + if (s + len > end) return 1; /* truncated */ + return len; +} + +/* Decode a UTF-8 character and return its codepoint. + *len is set to the byte length consumed. */ +uint32_t +mrb_re_utf8_decode(const char *s, int *len) +{ + uint8_t c = (uint8_t)s[0]; + uint32_t cp; + + if (c < 0x80) { + *len = 1; + return c; + } + else if (c < 0xc0) { + *len = 1; + return c; /* invalid, return as-is */ + } + else if (c < 0xe0) { + *len = 2; + cp = (c & 0x1f) << 6; + cp |= ((uint8_t)s[1] & 0x3f); + return cp; + } + else if (c < 0xf0) { + *len = 3; + cp = (c & 0x0f) << 12; + cp |= ((uint8_t)s[1] & 0x3f) << 6; + cp |= ((uint8_t)s[2] & 0x3f); + return cp; + } + else { + *len = 4; + cp = (c & 0x07) << 18; + cp |= ((uint8_t)s[1] & 0x3f) << 12; + cp |= ((uint8_t)s[2] & 0x3f) << 6; + cp |= ((uint8_t)s[3] & 0x3f); + return cp; + } +} + +/* Check if character is a "word" character (\w): [a-zA-Z0-9_] */ +mrb_bool +mrb_re_is_word_char(uint32_t c) +{ + if (c >= 'a' && c <= 'z') return TRUE; + if (c >= 'A' && c <= 'Z') return TRUE; + if (c >= '0' && c <= '9') return TRUE; + if (c == '_') return TRUE; + return FALSE; +} diff --git a/mrbgems/mruby-regexp/src/regexp.c b/mrbgems/mruby-regexp/src/regexp.c new file mode 100644 index 0000000000..fa06557f55 --- /dev/null +++ b/mrbgems/mruby-regexp/src/regexp.c @@ -0,0 +1,977 @@ +/* +** regexp.c - Regexp class and MatchData class +** +** See Copyright Notice in mruby.h +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "re_internal.h" + +#include + +/* Regexp data type */ +static void regexp_free(mrb_state *mrb, void *ptr) { + mrb_re_free(mrb, (mrb_regexp_pattern*)ptr); +} + +static const struct mrb_data_type regexp_type = { "Regexp", regexp_free }; + +/* MatchData */ +typedef struct { + mrb_value source; /* source string */ + mrb_value regexp; /* Regexp object (for named captures) */ + int *captures; /* capture positions [start0,end0,start1,end1,...] */ + int num_captures; /* number of capture groups (including 0) */ +} mrb_match_data; + +static void matchdata_free(mrb_state *mrb, void *ptr) { + mrb_match_data *md = (mrb_match_data*)ptr; + if (md) { + mrb_free(mrb, md->captures); + mrb_free(mrb, md); + } +} + +static const struct mrb_data_type matchdata_type = { "MatchData", matchdata_free }; + +/* Get internal flags from Regexp object */ +static uint32_t +get_iflags(mrb_state *mrb, mrb_value self) +{ + mrb_value v = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@flags")); + return mrb_nil_p(v) ? 0 : (uint32_t)mrb_integer(v); +} + +/* Parse flags from string or integer */ +static uint32_t +parse_flags(mrb_state *mrb, mrb_value flags_val) +{ + uint32_t flags = 0; + if (mrb_integer_p(flags_val)) { + mrb_int f = mrb_integer(flags_val); + if (f & 1) flags |= RE_FLAG_IGNORECASE; + if (f & 2) flags |= RE_FLAG_EXTENDED; + if (f & 4) flags |= RE_FLAG_MULTILINE | RE_FLAG_DOTALL; + return flags; + } + if (mrb_string_p(flags_val)) { + const char *s = RSTRING_PTR(flags_val); + mrb_int len = RSTRING_LEN(flags_val); + for (mrb_int i = 0; i < len; i++) { + switch (s[i]) { + case 'i': flags |= RE_FLAG_IGNORECASE; break; + case 'm': flags |= RE_FLAG_MULTILINE | RE_FLAG_DOTALL; break; + case 'x': flags |= RE_FLAG_EXTENDED; break; + } + } + return flags; + } + if (mrb_test(flags_val)) flags |= RE_FLAG_IGNORECASE; + return flags; +} + +/* + * Regexp.new(pattern, flags=nil) + * Regexp.new(regexp) + * Regexp.compile(pattern, flags=nil) + */ +static mrb_value +regexp_init(mrb_state *mrb, mrb_value self) +{ + mrb_value pattern; + mrb_value flags_val = mrb_nil_value(); + mrb_regexp_pattern *pat; + + mrb_get_args(mrb, "o|o", &pattern, &flags_val); + + uint32_t flags; + + /* If pattern is a Regexp, copy its source and flags */ + if (mrb_obj_is_kind_of(mrb, pattern, mrb_class_get(mrb, "Regexp"))) { + mrb_value iflags = mrb_iv_get(mrb, pattern, mrb_intern_lit(mrb, "@flags")); + flags = mrb_nil_p(iflags) ? 0 : (uint32_t)mrb_integer(iflags); + pattern = mrb_iv_get(mrb, pattern, mrb_intern_lit(mrb, "@source")); + } + else { + if (!mrb_string_p(pattern)) { + mrb_raise(mrb, E_TYPE_ERROR, "wrong argument type (expected String or Regexp)"); + } + flags = parse_flags(mrb, flags_val); + } + + /* Set @source and @flags before mrb_re_compile() so a Regexp that survives + a compile-time exception (e.g. picked up by ObjectSpace.each_object) + still has usable IVs for hash/eql?/inspect. */ + mrb_iv_set(mrb, self, mrb_intern_lit(mrb, "@source"), pattern); + mrb_iv_set(mrb, self, mrb_intern_lit(mrb, "@flags"), mrb_int_value(mrb, (mrb_int)flags)); + + pat = mrb_re_compile(mrb, RSTRING_PTR(pattern), RSTRING_LEN(pattern), flags); + + DATA_TYPE(self) = ®exp_type; + DATA_PTR(self) = pat; + + /* store named captures as hash */ + if (pat->num_named > 0) { + mrb_value nc = mrb_hash_new_capa(mrb, pat->num_named); + for (uint16_t i = 0; i < pat->num_named; i++) { + mrb_value name = mrb_str_new(mrb, pat->named_captures[i].name, pat->named_captures[i].name_len); + mrb_hash_set(mrb, nc, name, mrb_fixnum_value(pat->named_captures[i].group)); + } + mrb_iv_set(mrb, self, mrb_intern_lit(mrb, "@named_captures"), nc); + } + + return self; +} + +/* Pre-interned symbols for $1-$9 (cached on first use) */ +static mrb_sym nth_syms[9]; + +static void +ensure_nth_syms(mrb_state *mrb) +{ + if (nth_syms[0]) return; + nth_syms[0] = mrb_intern_lit(mrb, "$1"); + nth_syms[1] = mrb_intern_lit(mrb, "$2"); + nth_syms[2] = mrb_intern_lit(mrb, "$3"); + nth_syms[3] = mrb_intern_lit(mrb, "$4"); + nth_syms[4] = mrb_intern_lit(mrb, "$5"); + nth_syms[5] = mrb_intern_lit(mrb, "$6"); + nth_syms[6] = mrb_intern_lit(mrb, "$7"); + nth_syms[7] = mrb_intern_lit(mrb, "$8"); + nth_syms[8] = mrb_intern_lit(mrb, "$9"); +} + +static void +clear_match_globals(mrb_state *mrb) +{ + ensure_nth_syms(mrb); + mrb_gv_set(mrb, mrb_intern_lit(mrb, "$~"), mrb_nil_value()); + for (int i = 0; i < 9; i++) { + mrb_gv_set(mrb, nth_syms[i], mrb_nil_value()); + } +} + +/* Create MatchData from captures */ +static mrb_value +create_matchdata(mrb_state *mrb, mrb_value regexp, mrb_value str, int *captures, int ncap) +{ + ensure_nth_syms(mrb); + + struct RClass *md_class = mrb_class_get(mrb, "MatchData"); + mrb_match_data *md = (mrb_match_data*)mrb_malloc(mrb, sizeof(mrb_match_data)); + md->source = str; + md->regexp = regexp; + md->num_captures = ncap / 2; + md->captures = (int*)mrb_malloc(mrb, sizeof(int) * ncap); + memcpy(md->captures, captures, sizeof(int) * ncap); + + mrb_value obj = mrb_obj_value(mrb_data_object_alloc(mrb, md_class, md, &matchdata_type)); + /* Keep `source` and `regexp` GC-reachable via instance variables. + * The mrb_values are also held in mrb_match_data, but C-allocated + * structs are not scanned by the GC. */ + mrb_iv_set(mrb, obj, mrb_intern_lit(mrb, "source"), str); + mrb_iv_set(mrb, obj, mrb_intern_lit(mrb, "regexp"), regexp); + mrb_gv_set(mrb, mrb_intern_lit(mrb, "$~"), obj); + + /* set $1-$9 from captures */ + for (int i = 0; i < 9; i++) { + mrb_value val = mrb_nil_value(); + int g = i + 1; + if (g < md->num_captures && captures[g*2] >= 0) { + val = mrb_str_substr(mrb, str, captures[g*2], captures[g*2+1] - captures[g*2]); + } + mrb_gv_set(mrb, nth_syms[i], val); + } + + return obj; +} + +/* Internal: execute match and create MatchData. + Returns MatchData on match, nil on no match. + Sets $~ and $1-$9 globals. */ +static mrb_value +exec_match(mrb_state *mrb, mrb_value self, mrb_value str, mrb_int pos) +{ + mrb_regexp_pattern *pat = DATA_GET_PTR(mrb, self, ®exp_type, mrb_regexp_pattern); + if (!pat) mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized Regexp"); + + int cap_size = pat->num_captures * 2; + int *captures = (int*)mrb_malloc(mrb, sizeof(int) * cap_size); + memset(captures, -1, sizeof(int) * cap_size); + int ncap = mrb_re_exec(mrb, pat, RSTRING_PTR(str), RSTRING_LEN(str), pos, + captures, cap_size); + + if (ncap == 0) { + mrb_free(mrb, captures); + clear_match_globals(mrb); + return mrb_nil_value(); + } + mrb_value md = create_matchdata(mrb, self, str, captures, cap_size); + mrb_free(mrb, captures); + return md; +} + +/* + * Regexp#match(str, pos=0) + */ +static mrb_value +regexp_match(mrb_state *mrb, mrb_value self) +{ + mrb_value str; + mrb_int pos = 0; + mrb_get_args(mrb, "S|i", &str, &pos); + return exec_match(mrb, self, str, pos); +} + +/* + * Regexp#match?(str, pos=0) + */ +static mrb_value +regexp_match_p(mrb_state *mrb, mrb_value self) +{ + mrb_value str; + mrb_int pos = 0; + mrb_get_args(mrb, "S|i", &str, &pos); + + mrb_regexp_pattern *pat = DATA_GET_PTR(mrb, self, ®exp_type, mrb_regexp_pattern); + if (!pat) mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized Regexp"); + + int ncap = mrb_re_exec(mrb, pat, RSTRING_PTR(str), RSTRING_LEN(str), pos, NULL, 0); + return mrb_bool_value(ncap > 0); +} + +/* + * Regexp#=~(str) + */ +static mrb_value +regexp_match_op(mrb_state *mrb, mrb_value self) +{ + mrb_value str; + mrb_get_args(mrb, "o", &str); + if (mrb_nil_p(str)) return mrb_nil_value(); + mrb_ensure_string_type(mrb, str); + + mrb_value md = exec_match(mrb, self, str, 0); + if (mrb_nil_p(md)) return mrb_nil_value(); + + mrb_match_data *m = DATA_GET_PTR(mrb, md, &matchdata_type, mrb_match_data); + return mrb_int_value(mrb, m->captures[0]); +} + +/* + * Regexp#===(str) + */ +static mrb_value +regexp_case_match(mrb_state *mrb, mrb_value self) +{ + mrb_value str; + mrb_regexp_pattern *pat; + + mrb_get_args(mrb, "o", &str); + if (!mrb_string_p(str)) return mrb_false_value(); + + pat = DATA_GET_PTR(mrb, self, ®exp_type, mrb_regexp_pattern); + if (!pat) return mrb_false_value(); + + int ncap = mrb_re_exec(mrb, pat, RSTRING_PTR(str), RSTRING_LEN(str), 0, NULL, 0); + return mrb_bool_value(ncap > 0); +} + +/* + * Regexp#source + */ +static mrb_value +regexp_source(mrb_state *mrb, mrb_value self) +{ + return mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@source")); +} + +/* + * Regexp#options - convert internal flags to Ruby constants + * Internal: IGNORECASE=1, MULTILINE=2, DOTALL=4, EXTENDED=8 + * Ruby: IGNORECASE=1, EXTENDED=2, MULTILINE=4 + */ +static mrb_value +regexp_options(mrb_state *mrb, mrb_value self) +{ + uint32_t iflags = get_iflags(mrb, self); + mrb_int opts = 0; + if (iflags & RE_FLAG_IGNORECASE) opts |= 1; /* Regexp::IGNORECASE */ + if (iflags & RE_FLAG_EXTENDED) opts |= 2; /* Regexp::EXTENDED */ + if (iflags & RE_FLAG_MULTILINE) opts |= 4; /* Regexp::MULTILINE */ + return mrb_fixnum_value(opts); +} + +/* + * Regexp#casefold? + */ +static mrb_value +regexp_casefold_p(mrb_state *mrb, mrb_value self) +{ + return mrb_bool_value((get_iflags(mrb, self) & RE_FLAG_IGNORECASE) != 0); +} + +/* + * Regexp#to_s - CRuby-compatible (?flags:source) format + */ +static mrb_value +regexp_to_s(mrb_state *mrb, mrb_value self) +{ + mrb_value src = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@source")); + uint32_t flags = get_iflags(mrb, self); + + mrb_value result = mrb_str_new_lit(mrb, "(?"); + if (flags & RE_FLAG_IGNORECASE) mrb_str_cat_lit(mrb, result, "i"); + if (flags & RE_FLAG_MULTILINE) mrb_str_cat_lit(mrb, result, "m"); + if (flags & RE_FLAG_EXTENDED) mrb_str_cat_lit(mrb, result, "x"); + mrb_str_cat_lit(mrb, result, ":"); + mrb_str_cat_str(mrb, result, src); + mrb_str_cat_lit(mrb, result, ")"); + return result; +} + +static mrb_value +regexp_inspect(mrb_state *mrb, mrb_value self) +{ + mrb_value src = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@source")); + uint32_t flags = get_iflags(mrb, self); + + mrb_value result = mrb_str_new_lit(mrb, "/"); + mrb_str_cat_str(mrb, result, src); + mrb_str_cat_lit(mrb, result, "/"); + if (flags & RE_FLAG_IGNORECASE) mrb_str_cat_lit(mrb, result, "i"); + if (flags & RE_FLAG_MULTILINE) mrb_str_cat_lit(mrb, result, "m"); + if (flags & RE_FLAG_EXTENDED) mrb_str_cat_lit(mrb, result, "x"); + return result; +} + +/* + * Regexp#== (and eql?) + */ +static mrb_value +regexp_eql(mrb_state *mrb, mrb_value self) +{ + mrb_value other; + mrb_get_args(mrb, "o", &other); + if (!mrb_obj_is_kind_of(mrb, other, mrb_class_get(mrb, "Regexp"))) { + return mrb_false_value(); + } + mrb_value src1 = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@source")); + mrb_value src2 = mrb_iv_get(mrb, other, mrb_intern_lit(mrb, "@source")); + if (!mrb_string_p(src1) || !mrb_string_p(src2)) { + return mrb_bool_value(mrb_obj_eq(mrb, self, other)); + } + if (!mrb_str_equal(mrb, src1, src2)) return mrb_false_value(); + return mrb_bool_value(get_iflags(mrb, self) == get_iflags(mrb, other)); +} + +/* + * Regexp#hash + */ +static mrb_value +regexp_hash(mrb_state *mrb, mrb_value self) +{ + mrb_value src = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@source")); + uint32_t h = mrb_string_p(src) ? mrb_str_hash(mrb, src) : 0; + h ^= get_iflags(mrb, self) * 0x9e3779b9; /* mix flags into hash */ + return mrb_int_value(mrb, (mrb_int)h); +} + +/* + * Regexp.escape(str) + */ +static mrb_value +regexp_escape(mrb_state *mrb, mrb_value self) +{ + mrb_value str; + mrb_get_args(mrb, "S", &str); + + const char *s = RSTRING_PTR(str); + mrb_int len = RSTRING_LEN(str); + mrb_value result = mrb_str_new_capa(mrb, len + len / 4); + + for (mrb_int i = 0; i < len; i++) { + char c = s[i]; + switch (c) { + case '\\': case '.': case '*': case '+': case '?': case '|': + case '(': case ')': case '[': case ']': case '{': case '}': + case '^': case '$': + mrb_str_cat_lit(mrb, result, "\\"); + /* fall through */ + default: + mrb_str_cat(mrb, result, &c, 1); + break; + } + } + return result; +} + +/* --- MatchData methods --- */ + +/* + * MatchData#[](n) + */ +static mrb_value +matchdata_aref(mrb_state *mrb, mrb_value self) +{ + mrb_value arg; + mrb_get_args(mrb, "o", &arg); + + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md) return mrb_nil_value(); + + mrb_int idx; + if (mrb_string_p(arg) || mrb_symbol_p(arg)) { + /* named capture access */ + const char *name; + mrb_int name_len; + if (mrb_symbol_p(arg)) { + name = mrb_sym_name_len(mrb, mrb_symbol(arg), &name_len); + } + else { + name = RSTRING_PTR(arg); + name_len = RSTRING_LEN(arg); + } + /* look up name in regexp's named captures */ + mrb_regexp_pattern *pat = NULL; + if (!mrb_nil_p(md->regexp)) { + pat = DATA_GET_PTR(mrb, md->regexp, ®exp_type, mrb_regexp_pattern); + } + if (pat) { + for (uint16_t i = 0; i < pat->num_named; i++) { + if (pat->named_captures[i].name_len == (uint16_t)name_len && + memcmp(pat->named_captures[i].name, name, name_len) == 0) { + idx = pat->named_captures[i].group; + goto found; + } + } + } + return mrb_nil_value(); + } + else { + idx = mrb_as_int(mrb, arg); + } + +found: + if (idx < 0 || idx >= md->num_captures) return mrb_nil_value(); + int start = md->captures[idx * 2]; + int end = md->captures[idx * 2 + 1]; + if (start < 0) return mrb_nil_value(); + + return mrb_str_substr(mrb, md->source, start, end - start); +} + +/* Build array of capture strings from group `from` to num_captures-1 */ +static mrb_value +matchdata_to_ary(mrb_state *mrb, mrb_value self, int from) +{ + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md) return mrb_ary_new(mrb); + + mrb_value ary = mrb_ary_new_capa(mrb, md->num_captures - from); + for (int i = from; i < md->num_captures; i++) { + int s = md->captures[i * 2]; + int e = md->captures[i * 2 + 1]; + if (s < 0) { + mrb_ary_push(mrb, ary, mrb_nil_value()); + } + else { + mrb_ary_push(mrb, ary, mrb_str_substr(mrb, md->source, s, e - s)); + } + } + return ary; +} + +static mrb_value +matchdata_captures(mrb_state *mrb, mrb_value self) +{ + return matchdata_to_ary(mrb, self, 1); +} + +static mrb_value +matchdata_to_a(mrb_state *mrb, mrb_value self) +{ + return matchdata_to_ary(mrb, self, 0); +} + +/* + * MatchData#begin(n) / MatchData#end(n) + */ +static mrb_value +matchdata_begin(mrb_state *mrb, mrb_value self) +{ + mrb_int idx; + mrb_get_args(mrb, "i", &idx); + + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md || idx < 0 || idx >= md->num_captures) return mrb_nil_value(); + int pos = md->captures[idx * 2]; + if (pos < 0) return mrb_nil_value(); + return mrb_int_value(mrb, pos); +} + +static mrb_value +matchdata_end(mrb_state *mrb, mrb_value self) +{ + mrb_int idx; + mrb_get_args(mrb, "i", &idx); + + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md || idx < 0 || idx >= md->num_captures) return mrb_nil_value(); + int pos = md->captures[idx * 2 + 1]; + if (pos < 0) return mrb_nil_value(); + return mrb_int_value(mrb, pos); +} + +/* + * MatchData#pre_match / #post_match + */ +static mrb_value +matchdata_pre(mrb_state *mrb, mrb_value self) +{ + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md || md->captures[0] < 0) return mrb_nil_value(); + return mrb_str_substr(mrb, md->source, 0, md->captures[0]); +} + +static mrb_value +matchdata_post(mrb_state *mrb, mrb_value self) +{ + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md || md->captures[1] < 0) return mrb_nil_value(); + int pos = md->captures[1]; + return mrb_str_substr(mrb, md->source, pos, RSTRING_LEN(md->source) - pos); +} + +/* + * MatchData#length / #size + */ +static mrb_value +matchdata_length(mrb_state *mrb, mrb_value self) +{ + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md) return mrb_fixnum_value(0); + return mrb_fixnum_value(md->num_captures); +} + +/* + * MatchData#named_captures + */ +static mrb_value +matchdata_named_captures(mrb_state *mrb, mrb_value self) +{ + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md) return mrb_hash_new(mrb); + + mrb_regexp_pattern *pat = NULL; + if (!mrb_nil_p(md->regexp)) { + pat = DATA_GET_PTR(mrb, md->regexp, ®exp_type, mrb_regexp_pattern); + } + if (!pat || pat->num_named == 0) return mrb_hash_new(mrb); + + mrb_value result = mrb_hash_new_capa(mrb, pat->num_named); + for (uint16_t i = 0; i < pat->num_named; i++) { + mrb_value name = mrb_str_new(mrb, pat->named_captures[i].name, pat->named_captures[i].name_len); + int group = pat->named_captures[i].group; + mrb_value val = mrb_nil_value(); + if (group >= 0 && group < md->num_captures) { + int s = md->captures[group * 2]; + int e = md->captures[group * 2 + 1]; + if (s >= 0) val = mrb_str_substr(mrb, md->source, s, e - s); + } + mrb_hash_set(mrb, result, name, val); + } + return result; +} + +/* + * MatchData#string - the original string (frozen copy) + */ +static mrb_value +matchdata_string(mrb_state *mrb, mrb_value self) +{ + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md) return mrb_nil_value(); + return md->source; +} + +/* + * MatchData#regexp - the Regexp used + */ +static mrb_value +matchdata_regexp(mrb_state *mrb, mrb_value self) +{ + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md) return mrb_nil_value(); + return md->regexp; +} + +/* + * MatchData#to_s - full match string + */ +static mrb_value +matchdata_to_s(mrb_state *mrb, mrb_value self) +{ + mrb_match_data *md = DATA_GET_PTR(mrb, self, &matchdata_type, mrb_match_data); + if (!md || md->captures[0] < 0) return mrb_nil_value(); + int s = md->captures[0]; + int e = md->captures[1]; + return mrb_str_substr(mrb, md->source, s, e - s); +} + +/* --- C-level gsub/sub/scan core --- */ + +/* Process replacement string: expand \0-\9, \&, \`, \', \+, \\ */ +static void +apply_replacement(mrb_state *mrb, mrb_value result, + const char *rep, mrb_int rep_len, + const char *str, int *captures, int ncap) +{ + mrb_int i = 0; + while (i < rep_len) { + if (rep[i] == '\\' && i + 1 < rep_len) { + char c = rep[i + 1]; + if (c >= '0' && c <= '9') { + int g = c - '0'; + if (g < ncap && captures[g * 2] >= 0) { + int s = captures[g * 2], e = captures[g * 2 + 1]; + mrb_str_cat(mrb, result, str + s, e - s); + } + } + else if (c == '&') { + if (captures[0] >= 0) { + mrb_str_cat(mrb, result, str + captures[0], captures[1] - captures[0]); + } + } + else if (c == '`') { + if (captures[0] >= 0) { + mrb_str_cat(mrb, result, str, captures[0]); + } + } + else if (c == '\'') { + if (captures[1] >= 0) { + mrb_str_cat(mrb, result, str + captures[1], strlen(str) - captures[1]); + } + } + else if (c == '+') { + /* last successful capture */ + for (int g = ncap - 1; g >= 1; g--) { + if (captures[g * 2] >= 0) { + int s = captures[g * 2], e = captures[g * 2 + 1]; + mrb_str_cat(mrb, result, str + s, e - s); + break; + } + } + } + else if (c == '\\') { + mrb_str_cat_lit(mrb, result, "\\"); + } + else { + mrb_str_cat(mrb, result, rep + i, 2); /* \x as-is */ + } + i += 2; + } + else { + /* find next backslash or end for batch copy */ + mrb_int j = i + 1; + while (j < rep_len && rep[j] != '\\') j++; + mrb_str_cat(mrb, result, rep + i, j - i); + i = j; + } + } +} + +/* Check if replacement contains backslash */ +static mrb_bool +has_backslash(const char *s, mrb_int len) +{ + return memchr(s, '\\', len) != NULL; +} + +/* + * Regexp#__gsub_str(str, replacement) - gsub core without block + */ +static mrb_value +regexp_gsub_str(mrb_state *mrb, mrb_value self) +{ + mrb_value str, replacement; + mrb_get_args(mrb, "SS", &str, &replacement); + + mrb_regexp_pattern *pat = DATA_GET_PTR(mrb, self, ®exp_type, mrb_regexp_pattern); + if (!pat) mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized Regexp"); + + const char *s = RSTRING_PTR(str); + mrb_int slen = RSTRING_LEN(str); + const char *rep = RSTRING_PTR(replacement); + mrb_int rep_len = RSTRING_LEN(replacement); + mrb_bool need_expand = has_backslash(rep, rep_len); + + int ncap = pat->num_captures; + int cap_size = ncap * 2; + int *captures = (int*)mrb_malloc(mrb, sizeof(int) * cap_size); + mrb_value result = mrb_str_new_capa(mrb, slen); + int ai = mrb_gc_arena_save(mrb); + + mrb_int pos = 0; + int last_ncap = 0; + int last_captures[RE_MAX_CAPTURES * 2]; + + while (pos <= slen) { + memset(captures, -1, sizeof(int) * cap_size); + int n = mrb_re_exec(mrb, pat, s, slen, pos, captures, cap_size); + if (n == 0) break; + + /* save last match for $~ */ + last_ncap = cap_size; + memcpy(last_captures, captures, sizeof(int) * cap_size); + + /* append pre-match */ + if (captures[0] > pos) { + mrb_str_cat(mrb, result, s + pos, captures[0] - pos); + } + + /* append replacement */ + if (need_expand) { + apply_replacement(mrb, result, rep, rep_len, s, captures, ncap); + } + else { + mrb_str_cat(mrb, result, rep, rep_len); + } + + /* advance position */ + int match_end = captures[1]; + if (match_end == pos) { + /* zero-length match: copy one char and advance */ + if (pos < slen) { + mrb_str_cat(mrb, result, s + pos, 1); + } + pos++; + } + else { + pos = match_end; + } + mrb_gc_arena_restore(mrb, ai); + } + + /* append remainder */ + if (pos <= slen) { + mrb_str_cat(mrb, result, s + pos, slen - pos); + } + + mrb_free(mrb, captures); + + /* set $~ from last match */ + if (last_ncap > 0) { + create_matchdata(mrb, self, str, last_captures, last_ncap); + } + else { + clear_match_globals(mrb); + } + + return result; +} + +/* + * Regexp#__sub_str(str, replacement) - sub core without block + */ +static mrb_value +regexp_sub_str(mrb_state *mrb, mrb_value self) +{ + mrb_value str, replacement; + mrb_get_args(mrb, "SS", &str, &replacement); + + mrb_regexp_pattern *pat = DATA_GET_PTR(mrb, self, ®exp_type, mrb_regexp_pattern); + if (!pat) mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized Regexp"); + + const char *s = RSTRING_PTR(str); + mrb_int slen = RSTRING_LEN(str); + const char *rep = RSTRING_PTR(replacement); + mrb_int rep_len = RSTRING_LEN(replacement); + + int cap_size = pat->num_captures * 2; + int *captures = (int*)mrb_malloc(mrb, sizeof(int) * cap_size); + memset(captures, -1, sizeof(int) * cap_size); + + int n = mrb_re_exec(mrb, pat, s, slen, 0, captures, cap_size); + if (n == 0) { + mrb_free(mrb, captures); + clear_match_globals(mrb); + return mrb_str_dup(mrb, str); + } + + mrb_value result = mrb_str_new_capa(mrb, slen); + + /* pre-match */ + if (captures[0] > 0) { + mrb_str_cat(mrb, result, s, captures[0]); + } + + /* replacement */ + if (has_backslash(rep, rep_len)) { + apply_replacement(mrb, result, rep, rep_len, s, captures, pat->num_captures); + } + else { + mrb_str_cat(mrb, result, rep, rep_len); + } + + /* post-match */ + if (captures[1] < slen) { + mrb_str_cat(mrb, result, s + captures[1], slen - captures[1]); + } + + create_matchdata(mrb, self, str, captures, cap_size); + mrb_free(mrb, captures); + return result; +} + +/* + * Regexp#__scan(str) - scan core, returns array + */ +static mrb_value +regexp_scan(mrb_state *mrb, mrb_value self) +{ + mrb_value str; + mrb_get_args(mrb, "S", &str); + + mrb_regexp_pattern *pat = DATA_GET_PTR(mrb, self, ®exp_type, mrb_regexp_pattern); + if (!pat) mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized Regexp"); + + const char *s = RSTRING_PTR(str); + mrb_int slen = RSTRING_LEN(str); + int ncap = pat->num_captures; + int cap_size = ncap * 2; + int *captures = (int*)mrb_malloc(mrb, sizeof(int) * cap_size); + + mrb_value ary = mrb_ary_new(mrb); + int ai = mrb_gc_arena_save(mrb); + mrb_int pos = 0; + int last_ncap = 0; + int last_captures[RE_MAX_CAPTURES * 2]; + + while (pos <= slen) { + memset(captures, -1, sizeof(int) * cap_size); + int n = mrb_re_exec(mrb, pat, s, slen, pos, captures, cap_size); + if (n == 0) break; + + last_ncap = cap_size; + memcpy(last_captures, captures, sizeof(int) * cap_size); + + if (ncap <= 1) { + /* no captures or just group 0: push matched string */ + mrb_ary_push(mrb, ary, + mrb_str_substr(mrb, str, captures[0], captures[1] - captures[0])); + } + else if (ncap == 2) { + /* single capture group: push capture string */ + if (captures[2] >= 0) { + mrb_ary_push(mrb, ary, + mrb_str_substr(mrb, str, captures[2], captures[3] - captures[2])); + } + else { + mrb_ary_push(mrb, ary, mrb_nil_value()); + } + } + else { + /* multiple captures: push array of captures */ + mrb_value sub = mrb_ary_new_capa(mrb, ncap - 1); + for (int i = 1; i < ncap; i++) { + if (captures[i * 2] >= 0) { + mrb_ary_push(mrb, sub, + mrb_str_substr(mrb, str, captures[i*2], captures[i*2+1] - captures[i*2])); + } + else { + mrb_ary_push(mrb, sub, mrb_nil_value()); + } + } + mrb_ary_push(mrb, ary, sub); + } + + int match_end = captures[1]; + if (match_end == pos) { + pos++; + } + else { + pos = match_end; + } + mrb_gc_arena_restore(mrb, ai); + } + + mrb_free(mrb, captures); + + if (last_ncap > 0) { + create_matchdata(mrb, self, str, last_captures, last_ncap); + } + else { + clear_match_globals(mrb); + } + + return ary; +} + +/* --- Gem init --- */ + +void +mrb_mruby_regexp_gem_init(mrb_state *mrb) +{ + struct RClass *re = mrb_define_class(mrb, "Regexp", mrb->object_class); + MRB_SET_INSTANCE_TT(re, MRB_TT_CDATA); + + /* Constants */ + mrb_define_const(mrb, re, "IGNORECASE", mrb_fixnum_value(1)); + mrb_define_const(mrb, re, "EXTENDED", mrb_fixnum_value(2)); + mrb_define_const(mrb, re, "MULTILINE", mrb_fixnum_value(4)); + + /* Class methods */ + mrb_define_method(mrb, re, "initialize", regexp_init, MRB_ARGS_ARG(1, 2)); + /* compile is defined in Ruby (mrblib) as alias for new */ + mrb_define_class_method(mrb, re, "escape", regexp_escape, MRB_ARGS_REQ(1)); + mrb_define_class_method(mrb, re, "quote", regexp_escape, MRB_ARGS_REQ(1)); + + /* Instance methods */ + mrb_define_method(mrb, re, "match", regexp_match, MRB_ARGS_ARG(1, 1)); + mrb_define_method(mrb, re, "match?", regexp_match_p, MRB_ARGS_ARG(1, 1)); + mrb_define_method(mrb, re, "=~", regexp_match_op, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, re, "===", regexp_case_match, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, re, "source", regexp_source, MRB_ARGS_NONE()); + mrb_define_method(mrb, re, "inspect", regexp_inspect, MRB_ARGS_NONE()); + mrb_define_method(mrb, re, "to_s", regexp_to_s, MRB_ARGS_NONE()); + mrb_define_method(mrb, re, "==", regexp_eql, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, re, "eql?", regexp_eql, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, re, "hash", regexp_hash, MRB_ARGS_NONE()); + mrb_define_method(mrb, re, "options", regexp_options, MRB_ARGS_NONE()); + mrb_define_method(mrb, re, "casefold?", regexp_casefold_p, MRB_ARGS_NONE()); + mrb_define_method(mrb, re, "__gsub_str", regexp_gsub_str, MRB_ARGS_REQ(2)); + mrb_define_method(mrb, re, "__sub_str", regexp_sub_str, MRB_ARGS_REQ(2)); + mrb_define_method(mrb, re, "__scan", regexp_scan, MRB_ARGS_REQ(1)); + + /* MatchData class */ + struct RClass *md = mrb_define_class(mrb, "MatchData", mrb->object_class); + MRB_SET_INSTANCE_TT(md, MRB_TT_CDATA); + + mrb_define_method(mrb, md, "[]", matchdata_aref, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, md, "captures", matchdata_captures, MRB_ARGS_NONE()); + mrb_define_method(mrb, md, "to_a", matchdata_to_a, MRB_ARGS_NONE()); + mrb_define_method(mrb, md, "length", matchdata_length, MRB_ARGS_NONE()); + mrb_define_method(mrb, md, "size", matchdata_length, MRB_ARGS_NONE()); + mrb_define_method(mrb, md, "begin", matchdata_begin, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, md, "end", matchdata_end, MRB_ARGS_REQ(1)); + mrb_define_method(mrb, md, "pre_match", matchdata_pre, MRB_ARGS_NONE()); + mrb_define_method(mrb, md, "post_match", matchdata_post, MRB_ARGS_NONE()); + mrb_define_method(mrb, md, "named_captures", matchdata_named_captures, MRB_ARGS_NONE()); + mrb_define_method(mrb, md, "string", matchdata_string, MRB_ARGS_NONE()); + mrb_define_method(mrb, md, "regexp", matchdata_regexp, MRB_ARGS_NONE()); + mrb_define_method(mrb, md, "to_s", matchdata_to_s, MRB_ARGS_NONE()); +} + +void +mrb_mruby_regexp_gem_final(mrb_state *mrb) +{ +} diff --git a/mrbgems/mruby-regexp/test/regexp.rb b/mrbgems/mruby-regexp/test/regexp.rb new file mode 100644 index 0000000000..787c6898d7 --- /dev/null +++ b/mrbgems/mruby-regexp/test/regexp.rb @@ -0,0 +1,494 @@ +assert("Regexp.new with string") do + re = Regexp.new("abc") + assert_kind_of Regexp, re +end + +assert("Regexp.new with regexp") do + r1 = Regexp.new("abc", Regexp::IGNORECASE) + r2 = Regexp.new(r1) + assert_equal r1.source, r2.source + assert_equal r1.options, r2.options + assert_true r2.match?("ABC") +end + +assert("Regexp#match - simple") do + re = Regexp.new("abc") + md = re.match("xabcy") + assert_kind_of MatchData, md + assert_equal "abc", md[0] +end + +assert("Regexp#match - no match") do + re = Regexp.new("xyz") + assert_nil re.match("abc") +end + +assert("Regexp#match?") do + re = Regexp.new("abc") + assert_true re.match?("xabcy") + assert_false re.match?("xyz") +end + +assert("Regexp#=~") do + re = Regexp.new("bc") + assert_equal 1, re =~ "abcd" + assert_nil re =~ "xyz" +end + +assert("Regexp#===") do + re = Regexp.new("abc") + assert_true re === "abc" + assert_false re === "xyz" +end + +assert("Regexp - character class") do + re = Regexp.new("[a-z]+") + md = re.match("123abc456") + assert_equal "abc", md[0] +end + +assert("Regexp - \\b inside character class is backspace") do + # Outside [...], \b is the word boundary assertion; inside [...] + # it must mean U+0008 (backspace), matching MRI/Onigmo. + assert_equal "Ruby", "Ruby".gsub(/[\b]/, "X") + assert_equal "aXc", "a\bc".gsub(/[\b]/, "X") + assert_equal ["\b", "\t", "\n"], "ABC\b\t\n".scan(/[\b-\n]/) +end + +assert("Regexp - dot") do + re = Regexp.new("a.c") + assert_true re.match?("abc") + assert_true re.match?("axc") + assert_false re.match?("ac") +end + +assert("Regexp - alternation") do + re = Regexp.new("cat|dog") + assert_equal "cat", re.match("I have a cat")[0] + assert_equal "dog", re.match("I have a dog")[0] +end + +assert("Regexp - quantifiers") do + assert_equal "aaa", Regexp.new("a+").match("aaa")[0] + assert_equal "", Regexp.new("a*").match("bbb")[0] + assert_equal "ab", Regexp.new("ab?").match("ab")[0] + assert_equal "a", Regexp.new("ab?").match("ac")[0] +end + +assert("Regexp - captures") do + re = Regexp.new("(\\w+)@(\\w+)") + md = re.match("user@host") + assert_equal "user@host", md[0] + assert_equal "user", md[1] + assert_equal "host", md[2] +end + +assert("Regexp - \\d \\w \\s") do + assert_true Regexp.new("\\d+").match?("123") + assert_true Regexp.new("\\w+").match?("abc_123") + assert_true Regexp.new("\\s+").match?(" ") + assert_false Regexp.new("\\d+").match?("abc") +end + +assert("Regexp - anchors") do + assert_true Regexp.new("^abc").match?("abc") + assert_false Regexp.new("^abc").match?("xabc") + assert_true Regexp.new("abc$").match?("abc") + assert_false Regexp.new("abc$").match?("abcx") +end + +assert("Regexp - case insensitive") do + re = Regexp.new("abc", Regexp::IGNORECASE) + assert_true re.match?("ABC") + assert_true re.match?("Abc") +end + +assert("Regexp - repetition {n,m}") do + assert_equal "aaa", Regexp.new("a{3}").match("aaaa")[0] + assert_equal "aa", Regexp.new("a{2,3}").match("aa")[0] + assert_equal "aaa", Regexp.new("a{2,3}").match("aaaa")[0] +end + +assert("MatchData#captures") do + re = Regexp.new("(a)(b)(c)") + md = re.match("abc") + assert_equal ["a", "b", "c"], md.captures +end + +assert("MatchData#pre_match / #post_match") do + re = Regexp.new("bc") + md = re.match("abcde") + assert_equal "a", md.pre_match + assert_equal "de", md.post_match +end + +assert("MatchData#string") do + md = Regexp.new("bc").match("abcde") + assert_equal "abcde", md.string +end + +assert("MatchData#regexp") do + re = Regexp.new("bc") + md = re.match("abcde") + assert_equal re, md.regexp +end + +assert("MatchData#to_s") do + md = Regexp.new("bc").match("abcde") + assert_equal "bc", md.to_s +end + +assert("MatchData#begin / #end") do + re = Regexp.new("bc") + md = re.match("abcde") + assert_equal 1, md.begin(0) + assert_equal 3, md.end(0) +end + +assert("Regexp.escape") do + assert_equal "a\\.b\\*c", Regexp.escape("a.b*c") +end + +assert("Regexp#inspect") do + re = Regexp.new("abc", Regexp::IGNORECASE) + assert_equal "/abc/i", re.inspect +end + +assert("Regexp#to_s") do + assert_equal "(?:abc)", Regexp.new("abc").to_s + assert_equal "(?i:abc)", Regexp.new("abc", Regexp::IGNORECASE).to_s + assert_equal "(?m:abc)", Regexp.new("abc", Regexp::MULTILINE).to_s + assert_equal "(?im:abc)", Regexp.new("abc", Regexp::IGNORECASE | Regexp::MULTILINE).to_s +end + +assert("Regexp#== and Regexp#eql?") do + r1 = Regexp.new("abc", Regexp::IGNORECASE) + r2 = Regexp.new("abc", Regexp::IGNORECASE) + r3 = Regexp.new("abc") + r4 = Regexp.new("def", Regexp::IGNORECASE) + assert_true r1 == r2 + assert_true r1.eql?(r2) + assert_false r1 == r3 # different flags + assert_false r1 == r4 # different source + assert_false r1 == "abc" # not a Regexp +end + +assert("Regexp#hash") do + r1 = Regexp.new("abc", Regexp::IGNORECASE) + r2 = Regexp.new("abc", Regexp::IGNORECASE) + r3 = Regexp.new("abc") + assert_equal r1.hash, r2.hash + assert_not_equal r1.hash, r3.hash +end + +assert("Regexp#hash/== on uninitialized regexp") do + # Regexp.allocate yields an object with no @source IV; hash/== must + # not crash (regression: ObjectSpace.each_object could expose a + # half-initialized Regexp after Regexp.new raised a compile error). + r = Regexp.allocate + assert_kind_of Integer, r.hash + assert_true r == r + assert_false r == Regexp.allocate + assert_false r == Regexp.new("abc") +end + +assert("Regexp#options") do + assert_equal 0, Regexp.new("abc").options + assert_equal Regexp::IGNORECASE, Regexp.new("abc", Regexp::IGNORECASE).options + assert_equal Regexp::MULTILINE, Regexp.new("abc", Regexp::MULTILINE).options + assert_equal Regexp::EXTENDED, Regexp.new("abc", Regexp::EXTENDED).options + assert_equal Regexp::IGNORECASE | Regexp::MULTILINE, + Regexp.new("abc", Regexp::IGNORECASE | Regexp::MULTILINE).options + assert_equal Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE, + Regexp.new("abc", Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE).options +end + +assert("Regexp#casefold?") do + assert_true Regexp.new("abc", Regexp::IGNORECASE).casefold? + assert_false Regexp.new("abc").casefold? +end + +assert("Regexp extended mode (x flag)") do + # whitespace is ignored + re = Regexp.new('a b c', Regexp::EXTENDED) + assert_true re.match?("abc") + assert_false re.match?("a b c") + + # comments are ignored + re = Regexp.new("a # match a\nb # match b\nc", Regexp::EXTENDED) + assert_true re.match?("abc") + + # whitespace inside character class is literal + re = Regexp.new('[ ]', Regexp::EXTENDED) + assert_true re.match?(" ") + + # escaped whitespace is preserved + re = Regexp.new('a\\ b', Regexp::EXTENDED) + assert_true re.match?("a b") + + # inspect shows x flag + assert_equal "/abc/x", Regexp.new("abc", Regexp::EXTENDED).inspect + + # to_s shows x flag + assert_equal "(?x:abc)", Regexp.new("abc", Regexp::EXTENDED).to_s +end + +assert("String#match") do + md = "hello world".match(Regexp.new("(\\w+)\\s(\\w+)")) + assert_equal "hello", md[1] + assert_equal "world", md[2] +end + +assert("String#sub") do + assert_equal "hXllo", "hello".sub(Regexp.new("e"), "X") +end + +assert("String#gsub") do + assert_equal "h-ll-", "hello".gsub(Regexp.new("[eo]"), "-") +end + +assert("String#sub with \\& \\` \\' specials") do + # \& = full match + assert_equal "a[bc]d", "abcd".sub(/bc/, '[\\&]') + # \` = pre_match + assert_equal "a[a]d", "abcd".sub(/bc/, '[\\`]') + # \' = post_match + assert_equal "a[d]d", "abcd".sub(/bc/, "[\\']") + # \+ = last capture + assert_equal "a[c]d", "abcd".sub(/(b)(c)/, '[\\+]') + # \\ = literal backslash + assert_equal "a\\d", "abcd".sub(/bc/, "\\\\") + # \1 still works + assert_equal "abbd", "abcd".sub(/(b)c/, '\\1\\1') +end + +assert("String#gsub with \\& special") do + assert_equal "[a][b][c]", "abc".gsub(/./, '[\\&]') +end + +assert("String#scan") do + assert_equal ["1", "2", "3"], "a1b2c3".scan(Regexp.new("\\d")) +end + +assert("Regexp literal /regex/") do + assert_true /abc/.match?("abc") + assert_equal "123", /\d+/.match("abc123")[0] + assert_true /hello/i.match?("HELLO") +end + +assert("$~ global variable") do + /(\w+)@(\w+)/ =~ "user@host" + assert_kind_of MatchData, $~ + assert_equal "user", $~[1] + assert_equal "host", $~[2] +end + +assert("$~ is nil on no match") do + /xyz/ =~ "abc" + assert_nil $~ +end + +assert("Regexp.last_match") do + /(\d+)/ =~ "abc123" + assert_equal "123", Regexp.last_match(1) + assert_equal "123", Regexp.last_match(0) +end + +assert("Regexp - empty pattern") do + assert_true //.match?("") + assert_true //.match?("abc") +end + +assert("Regexp - nested captures") do + md = /((a)(b))c/.match("abc") + assert_equal "abc", md[0] + assert_equal "ab", md[1] + assert_equal "a", md[2] + assert_equal "b", md[3] +end + +assert("Regexp - non-greedy quantifiers") do + + assert_equal "a", /a+?/.match("aaa")[0] + assert_equal "", /a*?/.match("aaa")[0] +end + +assert("Regexp - word boundary") do + assert_equal "cat", /\bcat\b/.match("the cat sat")[0] + assert_nil /\bcat\b/.match("concatenate") +end + +assert("Regexp - non-capturing group") do + md = /(?:a)(b)/.match("ab") + assert_equal "ab", md[0] + assert_equal "b", md[1] + assert_nil md[2] +end + +assert("String#sub with block") do + assert_equal "HELLO world", "hello world".sub(/\w+/) { |m| m.upcase } +end + +assert("String#gsub with block") do + assert_equal "HELLO WORLD", "hello world".gsub(/\w+/) { |m| m.upcase } +end + +assert("String#gsub date reformat") do + result = "2026-03-21".gsub(/(\d+)-(\d+)-(\d+)/) { "#{$~[3]}/#{$~[2]}/#{$~[1]}" } + assert_equal "21/03/2026", result +end + +assert("String#scan with captures") do + assert_equal [["1","a"],["2","b"]], "1a2b".scan(/(\d)(\w)/) +end + +assert("String#split with regexp") do + assert_equal ["a", "b", "c"], "a, b, c".split(/,\s*/) +end + +assert("Regexp - case in when") do + result = case "hello123" + when /\d+/ then "has digits" + else "no digits" + end + assert_equal "has digits", result +end + +assert("Regexp - backreference \\1") do + # match repeated word + md = /(\w+) \1/.match("hello hello world") + assert_equal "hello hello", md[0] + assert_equal "hello", md[1] +end + +assert("Regexp - backreference no match") do + assert_nil /(\w+) \1/.match("hello world") +end + +assert("Regexp - named captures") do + md = /(?\d+)-(?\d+)-(?\d+)/.match("2026-03-21") + assert_equal "2026", md[:year] + assert_equal "03", md[:month] + assert_equal "21", md[:day] + assert_equal "2026", md["year"] +end + +assert("MatchData#named_captures") do + md = /(?\w+)@(?\w+)/.match("user@host") + nc = md.named_captures + assert_equal "user", nc["a"] + assert_equal "host", nc["b"] +end + +assert("Regexp - named captures survive /x preprocessing") do + # Regression: with /x, mrb_re_compile freed the stripped buffer that + # named_captures[i].name pointed into. + re = /(?\d+) # comment + \s* (?\w+) /x + m = re.match("42 px") + assert_equal "42", m[:n] + assert_equal "px", m[:u] +end + +assert("Regexp - named captures survive source string mutation") do + # Regression: name pointer used to alias RSTRING_PTR of the source. + s = String.new("(?\\d+)") + re = Regexp.new(s) + s.replace("X" * 10000) # force buffer reallocation + m = re.match("abc 123 def") + assert_equal "123", m[:key] +end + +assert("Regexp - positive lookahead (?=...)") do + md = /\w+(?=@)/.match("user@host") + assert_equal "user", md[0] +end + +assert("Regexp - negative lookahead (?!...)") do + md = /\d+(?!%)/.match("100%") + assert_equal "10", md[0] +end + +assert("Regexp - lookahead does not consume") do + md = /foo(?=bar)/.match("foobar") + assert_equal "foo", md[0] + assert_nil /foo(?=baz)/.match("foobar") +end + +assert("Regexp - positive lookbehind (?<=...)") do + md = Regexp.new("(?<=@)\\w+").match("user@host") + assert_equal "host", md[0] + assert_nil Regexp.new("(?<=@)\\w+").match("user_host") +end + +assert("Regexp - negative lookbehind (? 'mruby-error') # spec.add_dependency('mruby-mtest') - # Check if HAL gem is loaded - # HAL gems must be explicitly specified in build config (recommended) or via auto-selection below - spec.build.gems.one? { |g| g.name =~ /^hal-.*-socket$/ } or begin - # No HAL found - determine appropriate error message or auto-load - suggested_hal = if spec.for_windows? - 'hal-win-socket' - elsif RUBY_PLATFORM =~ /linux|darwin|bsd/ - 'hal-posix-socket' - else - nil - end - - if suggested_hal - # Auto-load HAL gem for convenience (for development) - # This works because HAL gems declare dependency on mruby-socket - warn "mruby-socket: No HAL specified, loading #{suggested_hal} (explicit selection recommended)" - spec.build.gem core: suggested_hal - else - # Unknown platform - fail with helpful message - fail "mruby-socket: No HAL available for platform '#{RUBY_PLATFORM}'.\n" \ - "Please specify HAL gem explicitly in your build config:\n" \ - " conf.gem core: 'hal-posix-socket' # For Linux/macOS/BSD\n" \ - " conf.gem core: 'hal-win-socket' # For Windows" - end + if spec.for_windows? + spec.linker.libraries << "wsock32" + spec.linker.libraries << "ws2_32" + spec.linker.libraries << "iphlpapi" # for GetAdaptersAddresses (Socket.ip_address_list) end end diff --git a/mrbgems/mruby-socket/mrblib/socket.rb b/mrbgems/mruby-socket/mrblib/socket.rb index f350d6cd57..ce9b2a31b4 100644 --- a/mrbgems/mruby-socket/mrblib/socket.rb +++ b/mrbgems/mruby-socket/mrblib/socket.rb @@ -778,7 +778,6 @@ def self.getaddrinfo(nodename, servname, family=nil, socktype=nil, protocol=nil, end #def self.getnameinfo - #def self.ip_address_list # # call-seq: diff --git a/mrbgems/hal-posix-socket/src/socket_hal.c b/mrbgems/mruby-socket/ports/posix/socket_hal.c similarity index 75% rename from mrbgems/hal-posix-socket/src/socket_hal.c rename to mrbgems/mruby-socket/ports/posix/socket_hal.c index 1cc08c5af6..a2f6ad6695 100644 --- a/mrbgems/hal-posix-socket/src/socket_hal.c +++ b/mrbgems/mruby-socket/ports/posix/socket_hal.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include @@ -41,6 +43,16 @@ mrb_hal_socket_final(mrb_state *mrb) /* No cleanup needed for POSIX sockets */ } +/* + * Error Handling + */ + +void +mrb_hal_socket_set_errno_from_last_error(void) +{ + /* POSIX socket calls already set errno on failure; nothing to do. */ +} + /* * Socket Control Operations */ @@ -138,20 +150,26 @@ mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen) return mrb_str_new_cstr(mrb, ((const struct sockaddr_un*)sockaddr)->sun_path); } -/* - * Gem initialization - */ - -void -mrb_hal_posix_socket_gem_init(mrb_state *mrb) -{ - (void)mrb; - /* HAL interface functions are called by mruby-socket gem */ -} - -void -mrb_hal_posix_socket_gem_final(mrb_state *mrb) +mrb_value +mrb_hal_socket_ip_address_list(mrb_state *mrb) { - (void)mrb; - /* Cleanup handled by mrb_hal_socket_final called from mruby-socket */ + struct ifaddrs *ifap = NULL; + if (getifaddrs(&ifap) != 0) { + mrb_sys_fail(mrb, "getifaddrs"); + } + mrb_value ary = mrb_ary_new(mrb); + int arena_idx = mrb_gc_arena_save(mrb); + for (struct ifaddrs *ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) continue; + socklen_t salen; + switch (ifa->ifa_addr->sa_family) { + case AF_INET: salen = sizeof(struct sockaddr_in); break; + case AF_INET6: salen = sizeof(struct sockaddr_in6); break; + default: continue; + } + mrb_ary_push(mrb, ary, mrb_str_new(mrb, (const char*)ifa->ifa_addr, salen)); + mrb_gc_arena_restore(mrb, arena_idx); + } + freeifaddrs(ifap); + return ary; } diff --git a/mrbgems/mruby-socket/ports/win/socket_hal.c b/mrbgems/mruby-socket/ports/win/socket_hal.c new file mode 100644 index 0000000000..de01226668 --- /dev/null +++ b/mrbgems/mruby-socket/ports/win/socket_hal.c @@ -0,0 +1,320 @@ +/* +** socket_hal.c - Windows HAL implementation for mruby-socket +** +** See Copyright Notice in mruby.h +** +** Windows implementation for socket operations using Winsock APIs. +** Supported platforms: Windows, MinGW +*/ + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 // need Windows XP or later +#endif + +#include +#include +#include +#include +#include +#include "socket_hal.h" +#include +#include +#include +#include +#include +#include + +/* + * Socket HAL Initialization/Finalization + */ + +void +mrb_hal_socket_init(mrb_state *mrb) +{ + WSADATA wsaData; + int result = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (result != NO_ERROR) { + mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(RuntimeError)), "WSAStartup failed"); + } +} + +void +mrb_hal_socket_final(mrb_state *mrb) +{ + (void)mrb; + WSACleanup(); +} + +/* + * Error Handling + */ + +/* Map a Winsock error code to a POSIX errno value. Each case is guarded + * with #ifdef so older MSVC CRTs that lack a particular Exxx still build; + * unknown codes fall back to EIO so mrb_sys_fail still produces a non-zero + * SystemCallError rather than reporting "success." */ +static int +wsa_to_errno(int wsa_err) +{ + switch (wsa_err) { + case 0: return 0; +#ifdef EINTR + case WSAEINTR: return EINTR; +#endif +#ifdef EBADF + case WSAEBADF: return EBADF; +#endif +#ifdef EACCES + case WSAEACCES: return EACCES; +#endif +#ifdef EFAULT + case WSAEFAULT: return EFAULT; +#endif +#ifdef EINVAL + case WSAEINVAL: return EINVAL; +#endif +#ifdef EMFILE + case WSAEMFILE: return EMFILE; +#endif +#ifdef EWOULDBLOCK + case WSAEWOULDBLOCK: return EWOULDBLOCK; +#endif +#ifdef EINPROGRESS + case WSAEINPROGRESS: return EINPROGRESS; +#endif +#ifdef EALREADY + case WSAEALREADY: return EALREADY; +#endif +#ifdef ENOTSOCK + case WSAENOTSOCK: return ENOTSOCK; +#endif +#ifdef EDESTADDRREQ + case WSAEDESTADDRREQ: return EDESTADDRREQ; +#endif +#ifdef EMSGSIZE + case WSAEMSGSIZE: return EMSGSIZE; +#endif +#ifdef EPROTOTYPE + case WSAEPROTOTYPE: return EPROTOTYPE; +#endif +#ifdef ENOPROTOOPT + case WSAENOPROTOOPT: return ENOPROTOOPT; +#endif +#ifdef EPROTONOSUPPORT + case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; +#endif +#ifdef EOPNOTSUPP + case WSAEOPNOTSUPP: return EOPNOTSUPP; +#endif +#ifdef EAFNOSUPPORT + case WSAEAFNOSUPPORT: return EAFNOSUPPORT; + case WSAEPFNOSUPPORT: return EAFNOSUPPORT; + case WSAESOCKTNOSUPPORT: return EAFNOSUPPORT; +#endif +#ifdef EADDRINUSE + case WSAEADDRINUSE: return EADDRINUSE; +#endif +#ifdef EADDRNOTAVAIL + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; +#endif +#ifdef ENETDOWN + case WSAENETDOWN: return ENETDOWN; +#endif +#ifdef ENETUNREACH + case WSAENETUNREACH: return ENETUNREACH; +#endif +#ifdef ENETRESET + case WSAENETRESET: return ENETRESET; +#endif +#ifdef ECONNABORTED + case WSAECONNABORTED: return ECONNABORTED; +#endif +#ifdef ECONNRESET + case WSAECONNRESET: return ECONNRESET; +#endif +#ifdef ENOBUFS + case WSAENOBUFS: return ENOBUFS; +#endif +#ifdef EISCONN + case WSAEISCONN: return EISCONN; +#endif +#ifdef ENOTCONN + case WSAENOTCONN: return ENOTCONN; +#endif +#ifdef ETIMEDOUT + case WSAETIMEDOUT: return ETIMEDOUT; +#endif +#ifdef ECONNREFUSED + case WSAECONNREFUSED: return ECONNREFUSED; +#endif +#ifdef EHOSTUNREACH + case WSAEHOSTUNREACH: return EHOSTUNREACH; +#endif +#ifdef ENAMETOOLONG + case WSAENAMETOOLONG: return ENAMETOOLONG; +#endif + default: return EIO; + } +} + +void +mrb_hal_socket_set_errno_from_last_error(void) +{ + errno = wsa_to_errno(WSAGetLastError()); +} + +/* + * Socket Control Operations + */ + +int +mrb_hal_socket_set_nonblock(mrb_state *mrb, int fd, int nonblock) +{ + (void)mrb; + u_long mode = nonblock ? 1 : 0; + int result = ioctlsocket(fd, FIONBIO, &mode); + if (result != NO_ERROR) { + mrb_hal_socket_set_errno_from_last_error(); + return -1; + } + return 0; +} + +/* + * Address Conversion Functions + */ + +const char* +mrb_hal_socket_inet_ntop(int af, const void *src, char *dst, size_t size) +{ + if (af == AF_INET) { + struct sockaddr_in in = {0}; + in.sin_family = AF_INET; + memcpy(&in.sin_addr, src, sizeof(struct in_addr)); + if (getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in), + dst, (DWORD)size, NULL, 0, NI_NUMERICHOST) == 0) { + return dst; + } + return NULL; + } + else if (af == AF_INET6) { + struct sockaddr_in6 in = {0}; + in.sin6_family = AF_INET6; + memcpy(&in.sin6_addr, src, sizeof(struct in6_addr)); + if (getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6), + dst, (DWORD)size, NULL, 0, NI_NUMERICHOST) == 0) { + return dst; + } + return NULL; + } + return NULL; +} + +int +mrb_hal_socket_inet_pton(int af, const char *src, void *dst) +{ + struct addrinfo hints = {0}; + hints.ai_family = af; + hints.ai_flags = AI_NUMERICHOST; + + struct addrinfo *res; + if (getaddrinfo(src, NULL, &hints, &res) != 0) { + return 0; /* Invalid address */ + } + + if (res == NULL) { + return 0; + } + + if (af == AF_INET && res->ai_family == AF_INET) { + memcpy(dst, &((struct sockaddr_in*)res->ai_addr)->sin_addr, sizeof(struct in_addr)); + freeaddrinfo(res); + return 1; + } + else if (af == AF_INET6 && res->ai_family == AF_INET6) { + memcpy(dst, &((struct sockaddr_in6*)res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); + freeaddrinfo(res); + return 1; + } + + freeaddrinfo(res); + return 0; +} + +/* + * Platform-Specific Socket Features + */ + +mrb_value +mrb_hal_socket_sockaddr_un(mrb_state *mrb, const char *path, size_t pathlen) +{ + (void)path; + (void)pathlen; + mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(NotImplementedError)), + "sockaddr_un unsupported on Windows"); + return mrb_nil_value(); +} + +int +mrb_hal_socket_socketpair(mrb_state *mrb, int domain, int type, int protocol, int sv[2]) +{ + (void)mrb; + (void)domain; + (void)type; + (void)protocol; + (void)sv; + /* socketpair is not supported on Windows */ + errno = ENOSYS; + return -1; +} + +mrb_value +mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen) +{ + (void)sockaddr; + (void)socklen; + mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(NotImplementedError)), + "unix_path unsupported on Windows"); + return mrb_nil_value(); +} + +mrb_value +mrb_hal_socket_ip_address_list(mrb_state *mrb) +{ + /* MSDN recommends 15 KiB as the initial buffer size to handle most + adapter configurations in a single call. */ + ULONG buflen = 15000; + IP_ADAPTER_ADDRESSES *adapters = (IP_ADAPTER_ADDRESSES*)mrb_malloc(mrb, buflen); + ULONG ret = ERROR_BUFFER_OVERFLOW; + for (int retries = 0; retries < 3 && ret == ERROR_BUFFER_OVERFLOW; retries++) { + ret = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, + NULL, adapters, &buflen); + if (ret == ERROR_BUFFER_OVERFLOW) { + adapters = (IP_ADAPTER_ADDRESSES*)mrb_realloc(mrb, adapters, buflen); + } + } + if (ret != ERROR_SUCCESS) { + mrb_free(mrb, adapters); + mrb_raisef(mrb, mrb_class_get_id(mrb, MRB_SYM(SocketError)), + "GetAdaptersAddresses failed (Win32 error %u)", (unsigned int)ret); + } + + mrb_value ary = mrb_ary_new(mrb); + int arena_idx = mrb_gc_arena_save(mrb); + for (IP_ADAPTER_ADDRESSES *ad = adapters; ad != NULL; ad = ad->Next) { + for (IP_ADAPTER_UNICAST_ADDRESS *ua = ad->FirstUnicastAddress; ua != NULL; ua = ua->Next) { + SOCKADDR *sa = ua->Address.lpSockaddr; + int salen; + switch (sa->sa_family) { + case AF_INET: salen = sizeof(SOCKADDR_IN); break; + case AF_INET6: salen = sizeof(SOCKADDR_IN6); break; + default: continue; + } + mrb_ary_push(mrb, ary, mrb_str_new(mrb, (const char*)sa, salen)); + mrb_gc_arena_restore(mrb, arena_idx); + } + } + mrb_free(mrb, adapters); + return ary; +} diff --git a/mrbgems/mruby-socket/src/socket.c b/mrbgems/mruby-socket/src/socket.c index 815bc2402e..ffb4c63141 100644 --- a/mrbgems/mruby-socket/src/socket.c +++ b/mrbgems/mruby-socket/src/socket.c @@ -134,6 +134,16 @@ static inline const char *get_pf_name(int family) { #define E_SOCKET_ERROR mrb_class_get_id(mrb, MRB_SYM(SocketError)) +/* Raise a SystemCallError for the most recent socket-API failure. + * On Windows the HAL translates WSAGetLastError() into errno first; + * on POSIX errno is already set, so this is just mrb_sys_fail. */ +static mrb_noreturn void +sock_sys_fail(mrb_state *mrb, const char *mesg) +{ + mrb_hal_socket_set_errno_from_last_error(); + mrb_sys_fail(mrb, mesg); +} + struct gen_addrinfo_args { struct RClass *klass; struct addrinfo *addrinfo; @@ -295,7 +305,7 @@ sa2addrlist(mrb_state *mrb, const struct sockaddr *sa, socklen_t salen) port = ntohs(port); mrb_value host = mrb_str_new_capa(mrb, NI_MAXHOST); if (getnameinfo(sa, salen, RSTRING_PTR(host), NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == -1) - mrb_sys_fail(mrb, "getnameinfo"); + sock_sys_fail(mrb, "getnameinfo"); mrb_str_resize(mrb, host, strlen(RSTRING_PTR(host))); mrb_value ary = mrb_ary_new_capa(mrb, 4); @@ -344,7 +354,7 @@ mrb_basicsocket_getpeereid(mrb_state *mrb, mrb_value self) uid_t euid; int s = socket_fd(mrb, self); if (getpeereid(s, &euid, &egid) != 0) - mrb_sys_fail(mrb, "getpeereid"); + sock_sys_fail(mrb, "getpeereid"); mrb_value ary = mrb_ary_new_capa(mrb, 2); mrb_ary_push(mrb, ary, mrb_fixnum_value((mrb_int)euid)); @@ -371,7 +381,7 @@ mrb_basicsocket_getpeername(mrb_state *mrb, mrb_value self) socklen_t salen = sizeof(ss); if (getpeername(socket_fd(mrb, self), (struct sockaddr*)&ss, &salen) != 0) - mrb_sys_fail(mrb, "getpeername"); + sock_sys_fail(mrb, "getpeername"); return mrb_str_new(mrb, (char*)&ss, salen); } @@ -391,7 +401,7 @@ mrb_basicsocket_getsockname(mrb_state *mrb, mrb_value self) socklen_t salen = sizeof(ss); if (getsockname(socket_fd(mrb, self), (struct sockaddr*)&ss, &salen) != 0) - mrb_sys_fail(mrb, "getsockname"); + sock_sys_fail(mrb, "getsockname"); return mrb_str_new(mrb, (char*)&ss, salen); } @@ -636,7 +646,7 @@ mrb_basicsocket_getsockopt(mrb_state *mrb, mrb_value self) socklen_t optlen = sizeof(opt); if (getsockopt(s, (int)level, (int)optname, opt, &optlen) == -1) - mrb_sys_fail(mrb, "getsockopt"); + sock_sys_fail(mrb, "getsockopt"); mrb_int family = socket_family(s); mrb_value data = mrb_str_new(mrb, opt, optlen); mrb_value args[4] = {mrb_fixnum_value(family), mrb_fixnum_value(level), mrb_fixnum_value(optname), data}; @@ -662,7 +672,7 @@ mrb_basicsocket_recv(mrb_state *mrb, mrb_value self) mrb_value buf = mrb_str_new_capa(mrb, maxlen); ssize_t n = recv(socket_fd(mrb, self), RSTRING_PTR(buf), (fsize_t)maxlen, (int)flags); if (n == -1) - mrb_sys_fail(mrb, "recv"); + sock_sys_fail(mrb, "recv"); mrb_str_resize(mrb, buf, (mrb_int)n); return buf; } @@ -686,7 +696,7 @@ mrb_basicsocket_recvfrom(mrb_state *mrb, mrb_value self) mrb_value sa = mrb_str_new_capa(mrb, socklen); ssize_t n = recvfrom(socket_fd(mrb, self), RSTRING_PTR(buf), (fsize_t)maxlen, (int)flags, (struct sockaddr*)RSTRING_PTR(sa), &socklen); if (n == -1) - mrb_sys_fail(mrb, "recvfrom"); + sock_sys_fail(mrb, "recvfrom"); mrb_str_resize(mrb, buf, (mrb_int)n); mrb_str_resize(mrb, sa, (mrb_int)socklen); @@ -721,7 +731,7 @@ mrb_basicsocket_send(mrb_state *mrb, mrb_value self) n = sendto(socket_fd(mrb, self), RSTRING_PTR(mesg), (fsize_t)RSTRING_LEN(mesg), (int)flags, (const struct sockaddr*)RSTRING_PTR(dest), (fsize_t)RSTRING_LEN(dest)); } if (n == -1) - mrb_sys_fail(mrb, "send"); + sock_sys_fail(mrb, "send"); return mrb_fixnum_value((mrb_int)n); } @@ -743,7 +753,7 @@ mrb_basicsocket_setnonblock(mrb_state *mrb, mrb_value self) int fd = socket_fd(mrb, self); if (mrb_hal_socket_set_nonblock(mrb, fd, nonblocking) == -1) - mrb_sys_fail(mrb, "set_nonblock"); + sock_sys_fail(mrb, "set_nonblock"); return mrb_nil_value(); } @@ -801,7 +811,7 @@ mrb_basicsocket_setsockopt(mrb_state *mrb, mrb_value self) int s = socket_fd(mrb, self); if (setsockopt(s, (int)level, (int)optname, RSTRING_PTR(optval), (socklen_t)RSTRING_LEN(optval)) == -1) - mrb_sys_fail(mrb, "setsockopt"); + sock_sys_fail(mrb, "setsockopt"); return mrb_fixnum_value(0); } @@ -822,7 +832,7 @@ mrb_basicsocket_shutdown(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "|i", &how); if (shutdown(socket_fd(mrb, self), (int)how) != 0) - mrb_sys_fail(mrb, "shutdown"); + sock_sys_fail(mrb, "shutdown"); return mrb_fixnum_value(0); } @@ -935,7 +945,7 @@ mrb_ipsocket_recvfrom(mrb_state *mrb, mrb_value self) ssize_t n = recvfrom(fd, RSTRING_PTR(buf), (fsize_t)maxlen, (int)flags, (struct sockaddr*)&ss, &socklen); if (n == -1) { - mrb_sys_fail(mrb, "recvfrom"); + sock_sys_fail(mrb, "recvfrom"); } mrb_str_resize(mrb, buf, (mrb_int)n); @@ -965,11 +975,41 @@ mrb_socket_gethostname(mrb_state *mrb, mrb_value cls) mrb_value buf = mrb_str_new_capa(mrb, (mrb_int)bufsize); if (gethostname(RSTRING_PTR(buf), (fsize_t)bufsize) != 0) - mrb_sys_fail(mrb, "gethostname"); + sock_sys_fail(mrb, "gethostname"); mrb_str_resize(mrb, buf, (mrb_int)strlen(RSTRING_PTR(buf))); return buf; } +/* + * call-seq: + * Socket.ip_address_list -> array + * + * Returns an array of `Addrinfo` objects representing all local IP addresses + * on every network interface (both IPv4 and IPv6). Loopback and link-local + * addresses are included; the caller is responsible for filtering. + * + * Socket.ip_address_list + * #=> [#, #, #, ...] + * + * Backed by `getifaddrs(3)` on POSIX and `GetAdaptersAddresses` on Windows. + */ +static mrb_value +mrb_socket_ip_address_list(mrb_state *mrb, mrb_value klass) +{ + (void)klass; + mrb_value sas = mrb_hal_socket_ip_address_list(mrb); + struct RClass *ainfo = mrb_class_get_id(mrb, MRB_SYM(Addrinfo)); + mrb_value result = mrb_ary_new_capa(mrb, RARRAY_LEN(sas)); + int arena_idx = mrb_gc_arena_save(mrb); + for (mrb_int i = 0; i < RARRAY_LEN(sas); i++) { + mrb_value sa = RARRAY_PTR(sas)[i]; + mrb_value addr = mrb_obj_new(mrb, ainfo, 1, &sa); + mrb_ary_push(mrb, result, addr); + mrb_gc_arena_restore(mrb, arena_idx); + } + return result; +} + /* * call-seq: * Socket._accept(fd) -> [new_fd, sockaddr] @@ -985,7 +1025,7 @@ mrb_socket_accept(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "i", &s0); int s1 = (int)accept(s0, NULL, NULL); if (s1 == -1) { - mrb_sys_fail(mrb, "accept"); + sock_sys_fail(mrb, "accept"); } return mrb_fixnum_value(s1); } @@ -1003,7 +1043,7 @@ mrb_socket_accept2(mrb_state *mrb, mrb_value klass) int s1 = (int)accept(s0, (struct sockaddr*)RSTRING_PTR(sastr), &socklen); if (s1 == -1) { - mrb_sys_fail(mrb, "accept"); + sock_sys_fail(mrb, "accept"); } mrb_str_resize(mrb, sastr, socklen); @@ -1026,7 +1066,7 @@ mrb_socket_bind(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "iS", &s, &sastr); if (bind((int)s, (struct sockaddr*)RSTRING_PTR(sastr), (socklen_t)RSTRING_LEN(sastr)) == -1) { - mrb_sys_fail(mrb, "bind"); + sock_sys_fail(mrb, "bind"); } return mrb_nil_value(); } @@ -1045,7 +1085,7 @@ mrb_socket_connect(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "iS", &s, &sastr); if (connect((int)s, (struct sockaddr*)RSTRING_PTR(sastr), (socklen_t)RSTRING_LEN(sastr)) == -1) { - mrb_sys_fail(mrb, "connect"); + sock_sys_fail(mrb, "connect"); } return mrb_nil_value(); } @@ -1063,7 +1103,7 @@ mrb_socket_listen(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "ii", &s, &backlog); if (listen((int)s, (int)backlog) == -1) { - mrb_sys_fail(mrb, "listen"); + sock_sys_fail(mrb, "listen"); } return mrb_nil_value(); } @@ -1119,7 +1159,7 @@ mrb_socket_socketpair(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "iii", &domain, &type, &protocol); if (mrb_hal_socket_socketpair(mrb, (int)domain, (int)type, (int)protocol, sv) == -1) { - mrb_sys_fail(mrb, "socketpair"); + sock_sys_fail(mrb, "socketpair"); } mrb_value ary = mrb_ary_new_capa(mrb, 2); @@ -1145,7 +1185,7 @@ mrb_socket_socket(mrb_state *mrb, mrb_value klass) int s = (int)socket((int)domain, (int)type, (int)protocol); if (s == -1) - mrb_sys_fail(mrb, "socket"); + sock_sys_fail(mrb, "socket"); return mrb_fixnum_value(s); } @@ -1220,7 +1260,7 @@ mrb_win32_basicsocket_sysread(mrb_state *mrb, mrb_value self) } break; case SOCKET_ERROR: /* Error */ - mrb_sys_fail(mrb, "recv"); + sock_sys_fail(mrb, "recv"); break; default: if (RSTRING_LEN(buf) != ret) { @@ -1263,7 +1303,7 @@ mrb_win32_basicsocket_syswrite(mrb_state *mrb, mrb_value self) int n = send(sd, RSTRING_PTR(str), (int)RSTRING_LEN(str), 0); if (n == SOCKET_ERROR) - mrb_sys_fail(mrb, "send"); + sock_sys_fail(mrb, "send"); return mrb_int_value(mrb, n); } @@ -1342,6 +1382,7 @@ mrb_mruby_socket_gem_init(mrb_state* mrb) mrb_define_class_method_id(mrb, sock, MRB_SYM(_sockaddr_family), mrb_socket_sockaddr_family, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, sock, MRB_SYM(_socket), mrb_socket_socket, MRB_ARGS_REQ(3)); mrb_define_class_method_id(mrb, sock, MRB_SYM(gethostname), mrb_socket_gethostname, MRB_ARGS_NONE()); + mrb_define_class_method_id(mrb, sock, MRB_SYM(ip_address_list), mrb_socket_ip_address_list, MRB_ARGS_NONE()); mrb_define_class_method_id(mrb, sock, MRB_SYM(sockaddr_un), mrb_socket_sockaddr_un, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, sock, MRB_SYM(socketpair), mrb_socket_socketpair, MRB_ARGS_REQ(3)); diff --git a/mrbgems/mruby-socket/test/addrinfo.rb b/mrbgems/mruby-socket/test/addrinfo.rb index 4916561793..eaab2a39ee 100644 --- a/mrbgems/mruby-socket/test/addrinfo.rb +++ b/mrbgems/mruby-socket/test/addrinfo.rb @@ -7,6 +7,7 @@ end assert('Addrinfo.getaddrinfo') do + skip "localhost resolution unreliable in Windows getaddrinfo" if SocketTest.win? ary = Addrinfo.getaddrinfo("localhost", 53, Socket::AF_INET, Socket::SOCK_STREAM) assert_true(ary.size >= 1) ai = ary[0] @@ -18,6 +19,7 @@ end assert('Addrinfo.foreach') do + skip "localhost resolution unreliable in Windows getaddrinfo" if SocketTest.win? # assume Addrinfo.getaddrinfo works well a = Addrinfo.getaddrinfo("localhost", 80) b = [] diff --git a/mrbgems/mruby-socket/test/socket.rb b/mrbgems/mruby-socket/test/socket.rb index b64a67919e..3a57710b06 100644 --- a/mrbgems/mruby-socket/test/socket.rb +++ b/mrbgems/mruby-socket/test/socket.rb @@ -36,3 +36,17 @@ end end # win? + +# Socket.ip_address_list works on both POSIX (getifaddrs) and Windows +# (GetAdaptersAddresses), so this test runs everywhere. +assert('Socket.ip_address_list') do + list = Socket.ip_address_list + assert_kind_of Array, list + # Every host should have at least one address (loopback at minimum). + assert_true list.length >= 1 + list.each do |ai| + assert_kind_of Addrinfo, ai + # Only AF_INET and AF_INET6 are returned. + assert_true [Socket::AF_INET, Socket::AF_INET6].include?(ai.afamily) + end +end diff --git a/mrbgems/mruby-sprintf/src/sprintf.c b/mrbgems/mruby-sprintf/src/sprintf.c index c7fbf4397e..5f4404cf9e 100644 --- a/mrbgems/mruby-sprintf/src/sprintf.c +++ b/mrbgems/mruby-sprintf/src/sprintf.c @@ -50,7 +50,7 @@ mrb_uint_to_cstr(char *buf, size_t len, mrb_int num, int base) char *b = buf + len - 1; const int mask = base-1; int shift; - mrb_uint val = (uint64_t)num; + mrb_uint val = (mrb_uint)num; if (num == 0) { buf[0] = '0'; buf[1] = '\0'; @@ -386,7 +386,7 @@ mrb_str_format(mrb_state *mrb, mrb_int argc, const mrb_value *argv, mrb_value fm buffer, so this is O(1); String#replace on the original goes through str_replace which decrements the shared refcount, leaving our copy's buffer intact. */ - fmt = mrb_str_dup(mrb, fmt); + fmt = mrb_str_dup_frozen(mrb, fmt); p = RSTRING_PTR(fmt); end = p + RSTRING_LEN(fmt); blen = 0; diff --git a/mrbgems/mruby-sprintf/test/sprintf.rb b/mrbgems/mruby-sprintf/test/sprintf.rb index 80220137a8..c513188a14 100644 --- a/mrbgems/mruby-sprintf/test/sprintf.rb +++ b/mrbgems/mruby-sprintf/test/sprintf.rb @@ -91,6 +91,16 @@ end end +assert("sprintf %g with high precision") do + # Regression test: precision values larger than double's significand + # used to cause out-of-bounds reads in fp_uscale's fixed_width(). + assert_equal "7", "%.*g" % [51, 7] + assert_equal "7.5", "%.*g" % [51, 7.5] + assert_equal "7", "%.51g" % 7.0 + assert_equal "7." + "0" * 50, "%#.51g" % 7.0 + assert_equal "7", "%.*g" % [1000, 7.0] +end + assert("sprintf with to_s mutating format string") do # The to_s callback must not be able to invalidate sprintf's internal # iteration pointers by mutating the format string. diff --git a/mrbgems/mruby-string-ext/mrblib/string.rb b/mrbgems/mruby-string-ext/mrblib/string.rb index 6c3c791f48..15d930a934 100644 --- a/mrbgems/mruby-string-ext/mrblib/string.rb +++ b/mrbgems/mruby-string-ext/mrblib/string.rb @@ -163,4 +163,34 @@ def __upto_endless(&block) end self end + + ## + # call-seq: + # str.scrub -> new_str + # str.scrub(repl) -> new_str + # str.scrub {|bytes| block } -> new_str + # + # Returns a copy of +self+ with each maximal run of invalid UTF-8 bytes + # replaced by +repl+ (U+FFFD if +repl+ is omitted), or by the value + # returned from the block when one is given. The block receives the + # invalid bytes as a String. + # + # "abc\x80def".scrub #=> "abc\u{FFFD}def" + # "abc\x80def".scrub("?") #=> "abc?def" + # "\xE3\x81".scrub #=> "\u{FFFD}" + # "\x80\x81".scrub { |b| b.bytes.map { |c| "<%02X>" % c }.join } + # #=> "<80><81>" + def scrub(repl = nil, &block) + return __scrub(repl) unless block + chunks = __scrub_chunks + return chunks[0] if chunks.length == 1 + result = chunks[0].dup + i = 1 + while i < chunks.length + result << yield(chunks[i]).to_s + result << chunks[i + 1] if i + 1 < chunks.length + i += 2 + end + result + end end diff --git a/mrbgems/mruby-string-ext/src/string.c b/mrbgems/mruby-string-ext/src/string.c index 6d3b408c88..4878db56ea 100644 --- a/mrbgems/mruby-string-ext/src/string.c +++ b/mrbgems/mruby-string-ext/src/string.c @@ -33,7 +33,8 @@ int_chr_utf8(mrb_state *mrb, mrb_value num) mrb_int len; mrb_value str; - if (cp < 0 || 0x10FFFF < cp) { + /* Reject negative, above U+10FFFF, and UTF-16 surrogates (RFC 3629). */ + if (cp < 0 || 0x10FFFF < cp || (0xD800 <= cp && cp <= 0xDFFF)) { mrb_raisef(mrb, E_RANGE_ERROR, "%v out of char range", num); } len = mrb_utf8_to_buf(utf8, (uint32_t)cp); @@ -973,20 +974,27 @@ utf8code(mrb_state* mrb, const unsigned char* p, const unsigned char *e) if (p[0] < 0x80) return p[0]; mrb_int len = mrb_utf8len_table[p[0]>>3]; + mrb_int cp = -1; if (p+len <= e && len > 1 && (p[1] & 0xc0) == 0x80) { if (len == 2) - return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f); - if ((p[2] & 0xc0) == 0x80) { + cp = ((p[0] & 0x1f) << 6) + (p[1] & 0x3f); + else if ((p[2] & 0xc0) == 0x80) { if (len == 3) - return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) - + (p[2] & 0x3f); - if ((p[3] & 0xc0) == 0x80) { - if (len == 4) - return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) - + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f); + cp = ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + (p[2] & 0x3f); + else if (len == 4 && (p[3] & 0xc0) == 0x80) { + cp = ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) + + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f); } } } + /* Reject overlong sequences, UTF-16 surrogates, and code points above + U+10FFFF (RFC 3629, Unicode D93b). */ + if (cp >= 0 && + ((len == 2 && cp >= 0x80) || + (len == 3 && cp >= 0x800 && (cp < 0xD800 || 0xDFFF < cp)) || + (len == 4 && cp >= 0x10000 && cp <= 0x10FFFF))) { + return cp; + } mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid UTF-8 byte sequence"); /* not reached */ return -1; @@ -1012,6 +1020,143 @@ str_ord(mrb_state* mrb, mrb_value str) return mrb_fixnum_value(c); } +/* Returns the byte length of a valid UTF-8 char starting at p, or -1 for + any invalid sequence (illegal lead byte, truncated tail, invalid + continuation byte, overlong encoding, UTF-16 surrogate, or codepoint + above U+10FFFF). Like utf8code() but reports rather than raises. */ +static mrb_int +str_scrub_char_len(const unsigned char *p, const unsigned char *e) +{ + if (p[0] < 0x80) return 1; + mrb_int len = mrb_utf8len_table[p[0]>>3]; + if (len < 2 || len > e - p) return -1; + for (mrb_int i = 1; i < len; i++) { + if ((p[i] & 0xc0) != 0x80) return -1; + } + mrb_int cp; + if (len == 2) { + cp = ((p[0] & 0x1f) << 6) | (p[1] & 0x3f); + if (cp < 0x80) return -1; + } + else if (len == 3) { + cp = ((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f); + if (cp < 0x800) return -1; + if (cp >= 0xD800 && cp <= 0xDFFF) return -1; + } + else { /* len == 4 */ + cp = ((p[0] & 0x07) << 18) | ((p[1] & 0x3f) << 12) + | ((p[2] & 0x3f) << 6) | (p[3] & 0x3f); + if (cp < 0x10000 || cp > 0x10FFFF) return -1; + } + return len; +} + +static void +str_scrub_validate_replacement(mrb_state *mrb, mrb_value repl) +{ + const unsigned char *p = (const unsigned char*)RSTRING_PTR(repl); + const unsigned char *e = p + RSTRING_LEN(repl); + while (p < e) { + mrb_int len = str_scrub_char_len(p, e); + if (len < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "replacement must be valid UTF-8"); + } + p += len; + } +} + +/* Core of String#scrub for the no-block case. Returns a new string with + each maximal run of invalid UTF-8 bytes replaced by `repl` (or U+FFFD + if `repl` is nil). Already-valid strings are returned via mrb_str_dup. */ +static mrb_value +str_scrub_core(mrb_state *mrb, mrb_value self) +{ + mrb_value repl = mrb_nil_value(); + mrb_get_args(mrb, "|S!", &repl); + + const char *replace; + mrb_int replace_len; + if (mrb_nil_p(repl)) { + replace = "\xEF\xBF\xBD"; /* U+FFFD REPLACEMENT CHARACTER */ + replace_len = 3; + } + else { + str_scrub_validate_replacement(mrb, repl); + replace = RSTRING_PTR(repl); + replace_len = RSTRING_LEN(repl); + } + + struct RString *s = mrb_str_ptr(self); + if (RSTR_SINGLE_BYTE_P(s) || RSTR_BINARY_P(s)) { + return mrb_str_dup(mrb, self); + } + + const unsigned char *p = (const unsigned char*)RSTR_PTR(s); + const unsigned char *e = p + RSTR_LEN(s); + const unsigned char *valid_start = p; + const unsigned char *q = p; + mrb_value result = mrb_nil_value(); /* lazily allocated on first invalid byte */ + + while (q < e) { + mrb_int len = str_scrub_char_len(q, e); + if (len < 0) { + if (mrb_nil_p(result)) { + result = mrb_str_new(mrb, NULL, 0); + } + mrb_str_cat(mrb, result, (const char*)valid_start, q - valid_start); + mrb_str_cat(mrb, result, replace, replace_len); + q++; + while (q < e && str_scrub_char_len(q, e) < 0) q++; + valid_start = q; + } + else { + q += len; + } + } + + if (mrb_nil_p(result)) { + return mrb_str_dup(mrb, self); /* already valid */ + } + mrb_str_cat(mrb, result, (const char*)valid_start, q - valid_start); + return result; +} + +/* Splits self into alternating valid/invalid byte runs and returns them + as an Array of strings ([valid, invalid, valid, ...], odd length). + Used by the block form of String#scrub in mrblib; the block can then + map each invalid run to a replacement of its choosing without the C + side having to call back into the VM. */ +static mrb_value +str_scrub_chunks(mrb_state *mrb, mrb_value self) +{ + mrb_value ary = mrb_ary_new(mrb); + struct RString *s = mrb_str_ptr(self); + if (RSTR_SINGLE_BYTE_P(s) || RSTR_BINARY_P(s)) { + mrb_ary_push(mrb, ary, mrb_str_dup(mrb, self)); + return ary; + } + const unsigned char *p = (const unsigned char*)RSTR_PTR(s); + const unsigned char *e = p + RSTR_LEN(s); + const unsigned char *valid_start = p; + const unsigned char *q = p; + while (q < e) { + mrb_int len = str_scrub_char_len(q, e); + if (len < 0) { + mrb_ary_push(mrb, ary, mrb_str_new(mrb, (const char*)valid_start, q - valid_start)); + const unsigned char *invalid_start = q; + q++; + while (q < e && str_scrub_char_len(q, e) < 0) q++; + mrb_ary_push(mrb, ary, mrb_str_new(mrb, (const char*)invalid_start, q - invalid_start)); + valid_start = q; + } + else { + q += len; + } + } + mrb_ary_push(mrb, ary, mrb_str_new(mrb, (const char*)valid_start, q - valid_start)); + return ary; +} + /* Internal helper for String#codepoints - returns array of character codepoints */ static mrb_value str_codepoints(mrb_state *mrb, mrb_value str) @@ -1060,6 +1205,24 @@ str_codepoints(mrb_state *mrb, mrb_value self) } return result; } + +/* Non-UTF-8 builds: scrub is a no-op. The replacement arg is accepted + (and validated only as a String) for API parity with the UTF-8 build. */ +static mrb_value +str_scrub_core(mrb_state *mrb, mrb_value self) +{ + mrb_value repl = mrb_nil_value(); + mrb_get_args(mrb, "|S!", &repl); + return mrb_str_dup(mrb, self); +} + +static mrb_value +str_scrub_chunks(mrb_state *mrb, mrb_value self) +{ + mrb_value ary = mrb_ary_new(mrb); + mrb_ary_push(mrb, ary, mrb_str_dup(mrb, self)); + return ary; +} #endif static mrb_bool @@ -2274,6 +2437,8 @@ static const mrb_mt_entry string_ext_rom_entries[] = { MRB_MT_ENTRY(str_b, MRB_SYM(b), MRB_ARGS_NONE()), MRB_MT_ENTRY(str_lines, MRB_SYM(__lines), MRB_ARGS_NONE()), MRB_MT_ENTRY(str_codepoints, MRB_SYM(__codepoints), MRB_ARGS_NONE()), + MRB_MT_ENTRY(str_scrub_core, MRB_SYM(__scrub), MRB_ARGS_OPT(1)), + MRB_MT_ENTRY(str_scrub_chunks, MRB_SYM(__scrub_chunks), MRB_ARGS_NONE()), MRB_MT_ENTRY(str_lstrip, MRB_SYM(lstrip), MRB_ARGS_NONE()), MRB_MT_ENTRY(str_rstrip, MRB_SYM(rstrip), MRB_ARGS_NONE()), MRB_MT_ENTRY(str_strip, MRB_SYM(strip), MRB_ARGS_NONE()), diff --git a/mrbgems/mruby-string-ext/test/numeric.rb b/mrbgems/mruby-string-ext/test/numeric.rb index f79235de09..1d876dedef 100644 --- a/mrbgems/mruby-string-ext/test/numeric.rb +++ b/mrbgems/mruby-string-ext/test/numeric.rb @@ -22,6 +22,11 @@ assert_equal("«", 171.chr("utf-8")) assert_equal("あ", 12354.chr("Utf-8")) assert_raise(RangeError) { -1.chr("utf-8") } - assert_raise(RangeError) { 0x110000.chr.chr("UTF-8") } + assert_raise(RangeError) { 0x110000.chr("UTF-8") } + # UTF-16 surrogates are not valid Unicode scalar values (RFC 3629, #2708) + assert_raise(RangeError) { 0xD800.chr("UTF-8") } + assert_raise(RangeError) { 0xDFFF.chr("UTF-8") } + assert_equal "\u{D7FF}", 0xD7FF.chr("UTF-8") + assert_equal "\u{E000}", 0xE000.chr("UTF-8") end end diff --git a/mrbgems/mruby-string-ext/test/string.rb b/mrbgems/mruby-string-ext/test/string.rb index 44281d390f..c0fd381db3 100644 --- a/mrbgems/mruby-string-ext/test/string.rb +++ b/mrbgems/mruby-string-ext/test/string.rb @@ -667,6 +667,19 @@ def assert_upto(exp, receiver, *args) assert_equal expect, got end if UTF8STRING +assert('String#ord(UTF-8) rejects ill-formed sequences', '#2708') do + # overlong encodings (RFC 3629) + assert_raise(ArgumentError) { "\xC0\x80".ord } # 2-byte overlong NUL + assert_raise(ArgumentError) { "\xE0\x80\x80".ord } # 3-byte overlong NUL + assert_raise(ArgumentError) { "\xF0\x80\x80\x80".ord } # 4-byte overlong NUL + assert_raise(ArgumentError) { "\xE0\x9F\xBF".ord } # overlong U+07FF as 3 bytes + # UTF-16 surrogates encoded as UTF-8 + assert_raise(ArgumentError) { "\xED\xA0\x80".ord } # U+D800 + assert_raise(ArgumentError) { "\xED\xBF\xBF".ord } # U+DFFF + # above U+10FFFF + assert_raise(ArgumentError) { "\xF4\x90\x80\x80".ord } # U+110000 +end if UTF8STRING + assert('String#chr') do assert_equal "a", "abcde".chr assert_equal "h", "hello!".chr @@ -778,3 +791,60 @@ def assert_upto(exp, receiver, *args) a = -(a.freeze) assert_true(a.frozen?) end + +assert('String#scrub default replacement (U+FFFD)') do + # scrub has UTF-8 semantics; on builds without MRB_UTF8_STRING it + # degrades to a no-op (verified separately below). + skip unless "あ".length == 1 + assert_equal "\u{FFFD}", "\xE3\x81".scrub + assert_equal "abc\u{FFFD}def", "abc\x80def".scrub + assert_equal "\u{FFFD}", "\x80\x81\x82".scrub # run collapsed + assert_equal "", "".scrub + assert_equal "hello", "hello".scrub # already valid + assert_equal "あい", "あい".scrub # already valid multibyte +end + +assert('String#scrub rejects malformed sequences') do + skip unless "あ".length == 1 + # overlong, UTF-16 surrogate, codepoint above U+10FFFF + assert_equal "\u{FFFD}", "\xC0\xAF".scrub # overlong "/" + assert_equal "\u{FFFD}", "\xED\xA0\x80".scrub # surrogate U+D800 + assert_equal "\u{FFFD}", "\xF4\x90\x80\x80".scrub # > U+10FFFF +end + +assert('String#scrub with replacement string') do + skip unless "あ".length == 1 + assert_equal "abc?def", "abc\x80def".scrub("?") + assert_equal "abcdef", "abc\x80def".scrub("") + assert_equal "abcdef", "abc\x80def".scrub("") +end + +assert('String#scrub raises on invalid replacement') do + skip unless "あ".length == 1 + assert_raise(ArgumentError) { "abc\x80".scrub("\xFF") } +end + +assert('String#scrub with block') do + skip unless "あ".length == 1 + assert_equal "abc<80>def", + "abc\x80def".scrub { |b| "<" + b.bytes.first.to_s(16) + ">" } + # Block not called when string is already valid + called = false + "hello".scrub { |_| called = true; "X" } + assert_false called + # Multiple invalid runs each get their own block invocation + result = "a\x80b\x81c".scrub { |b| "[#{b.bytes.first}]" } + assert_equal "a[128]b[129]c", result + # Non-String block return values are coerced via to_s (mruby leniency; + # CRuby raises TypeError instead). Locking this in so the choice is + # explicit and doesn't drift accidentally. + assert_equal "abc42def", "abc\x80def".scrub { 42 } +end + +assert('String#scrub is a no-op without MRB_UTF8_STRING') do + skip if "あ".length == 1 + # Method is still defined and returns a (string-equal) copy. + assert_equal "abc\x80def", "abc\x80def".scrub + assert_equal "abc\x80def", "abc\x80def".scrub("?") + assert_equal "abc\x80def", "abc\x80def".scrub { |_| "?" } +end diff --git a/mrbgems/mruby-symbol-ext/mrblib/symbol.rb b/mrbgems/mruby-symbol-ext/mrblib/symbol.rb index c2166a9b93..821cb1d2ab 100644 --- a/mrbgems/mruby-symbol-ext/mrblib/symbol.rb +++ b/mrbgems/mruby-symbol-ext/mrblib/symbol.rb @@ -69,4 +69,10 @@ def empty? self.length == 0 end + def slice *args + to_s.slice(*args) + end + + alias [] slice + end diff --git a/mrbgems/mruby-symbol-ext/test/symbol.rb b/mrbgems/mruby-symbol-ext/test/symbol.rb index 239f8c2705..2dd26467de 100644 --- a/mrbgems/mruby-symbol-ext/test/symbol.rb +++ b/mrbgems/mruby-symbol-ext/test/symbol.rb @@ -53,3 +53,15 @@ assert('Symbol#intern') do assert_equal :test, :test.intern end + +assert('Symbol#slice') do + assert_equal 'a', :abc.slice(0) + assert_equal 'ab', :abc.slice(0, 2) + assert_nil :abc.slice(4, 4) +end + +assert('Symbol#[]') do + assert_equal 'a', :abc[0] + assert_equal 'ab', :abc[0, 2] + assert_nil :abc[4, 4] +end diff --git a/mrbgems/mruby-task/README.md b/mrbgems/mruby-task/README.md index c261795743..04a9881f75 100644 --- a/mrbgems/mruby-task/README.md +++ b/mrbgems/mruby-task/README.md @@ -13,8 +13,9 @@ The primary purpose of `mruby-task` is to enable mruby applications to: - Schedule tasks based on priority (0-255, where 0 is highest priority). - Provide cooperative yielding with `Task.pass`. - Support preemptive scheduling via timer-based interrupts. -- Synchronize tasks using `sleep` and `join` operations. +- Synchronize tasks using `sleep`, `join` and `Task::Queue`. - Suspend and resume tasks programmatically. +- Coordinate producers and consumers via `Task::Queue` without polling. ## Architecture @@ -186,6 +187,116 @@ Task.run puts "Worker finished" ``` +### Task::Error + +`Task::Error` is the base error class for queue-related errors. It inherits +from `StandardError`. + +```ruby +begin + q = Task::Queue.new + q.close + q.push(1) # raises Task::Error: queue closed +rescue Task::Error => e + puts e.message # => "queue closed" +end +``` + +Errors raised by `Task::Queue`: + +| Situation | Error class | Message | +| ---------------------------------- | ------------- | ---------------- | +| `push` on a closed queue | `Task::Error` | `"queue closed"` | +| `pop(true)` on an empty open queue | `Task::Error` | `"queue empty"` | + +Internal consistency errors (programming errors, not queue logic) still use +`RuntimeError`: + +| Situation | Error class | +| ------------------------------------------------------- | -------------- | +| Blocking `pop` called from root context | `RuntimeError` | +| Blocking `pop` called from inside a C function boundary | `RuntimeError` | + +### Task::Queue + +`Task::Queue` is a thread-safe FIFO queue for inter-task communication, analogous to CRuby's `Queue`. A task that calls `pop` on an empty queue is automatically moved to the WAITING state and rescheduled when another task pushes an item. +No polling or explicit sleep is required. + +#### Creating a Queue + +```ruby +q = Task::Queue.new +``` + +#### Pushing Items + +```ruby +q.push(item) # add to the back of the queue; raises Task::Error if closed +q << item # alias for push +q.enq(item) # alias for push +``` + +#### Popping Items + +```ruby +item = q.pop # block until an item is available +item = q.pop(true) # non-blocking: raises Task::Error if empty +item = q.deq # alias for pop +item = q.shift # alias for pop +``` + +Behavior when the queue is **closed**: + +- `pop` on a non-empty closed queue returns the remaining items normally. +- `pop` on an empty closed queue returns `nil` immediately (no blocking). + +#### Inspecting the Queue + +```ruby +q.size # => Integer: number of items currently in the queue +q.length # alias for size +q.empty? # => true if the queue has no items +q.num_waiting # => Integer: number of tasks currently blocked in pop +``` + +#### Clearing and Closing + +```ruby +q.clear # remove all items; returns self +q.close # close the queue; returns self +q.closed? # => true if the queue has been closed +``` + +After `close`: + +- `push` raises `Task::Error`. +- Tasks blocked in `pop` are woken and receive `nil` for an empty queue. +- `pop` on remaining items still returns them normally; returns `nil` when empty. + +#### Producer/Consumer Example + +```ruby +q = Task::Queue.new + +Task.new(name: "producer") do + 10.times do |i| + q.push(i) + sleep 0.1 + end + q.close +end + +Task.new(name: "consumer") do + loop do + item = q.pop # blocks until an item arrives or the queue closes + break if item.nil? + puts "got #{item}" + end +end + +Task.run +``` + ### Kernel Methods (Sleep) The task scheduler provides task-aware sleep methods that cooperatively yield @@ -260,180 +371,107 @@ These grow automatically as needed, similar to Fiber. ### HAL (Hardware Abstraction Layer) -The task scheduler uses a Hardware Abstraction Layer (HAL) to support different platforms. Platform-specific timer and interrupt handling is provided by separate HAL gems. +The task scheduler uses a Hardware Abstraction Layer (HAL) to support +different platforms. Platform-specific timer and interrupt handling +lives in `mruby-task/ports//task_hal.c`, and the active +port is selected at build configuration time. -#### Built-in HAL Gems +#### Built-in Ports -**hal-posix-task** - For POSIX systems (Linux, macOS, BSD, Unix) +**`ports/posix/`** - For POSIX systems (Linux, macOS, BSD, Unix) -- Uses `SIGALRM` and `setitimer()` for timer +- Uses `SIGALRM` and `setitimer()` for the timer - Uses `sigprocmask()` for interrupt protection - Uses `SA_RESTART` to prevent `EINTR` on system calls - Supports multiple VMs per process -- **WASM/Emscripten support**: When compiled with Emscripten (`__EMSCRIPTEN__` defined), the SIGALRM timer is automatically disabled. JavaScript handles tick calls via `setInterval`, preventing double-increment of the tick counter +- **WASM/Emscripten support**: When compiled with Emscripten + (`__EMSCRIPTEN__` defined), the SIGALRM timer is automatically + disabled. JavaScript handles tick calls via `setInterval`, + preventing double-increment of the tick counter -**hal-win-task** - For Windows +**`ports/win/`** - For Windows -- Uses multimedia timer API (`timeSetEvent`/`timeKillEvent`) +- Uses the multimedia timer API (`timeSetEvent`/`timeKillEvent`) - Uses `CRITICAL_SECTION` for interrupt protection - Supports multiple VMs per process -#### HAL Selection +#### Port Selection -The task scheduler will automatically select an appropriate HAL gem based on your platform. For explicit control, you can specify the HAL gem in your build configuration: +The build system auto-detects `:posix` on Linux/macOS/BSD and `:win` +on Windows. For explicit control (cross-compilation, Cosmopolitan, +etc.) set `conf.ports` in your build configuration: ```ruby MRuby::Build.new do |conf| - # Option 1: Explicit HAL selection (recommended) - conf.gem core: 'hal-posix-task' # For Linux/macOS/BSD - # or - conf.gem core: 'hal-win-task' # For Windows - - # mruby-task automatically loads if HAL is loaded - # But you can also specify it explicitly: + conf.ports :posix # or :win, or your own port name conf.gem core: 'mruby-task' end ``` -**Auto-detection behavior:** - -- If you include `mruby-task` but no HAL gem, it will automatically load the appropriate HAL -- On Linux/macOS/BSD: loads `hal-posix-task` -- On Windows: loads `hal-win-task` -- On unknown platforms: fails with helpful error message +When `conf.ports` is set, the corresponding `ports//` +directory in every gem is compiled in; directories for other port +names are skipped. This is how `build_config/cosmopolitan.rb` reuses +the POSIX HAL on Cosmopolitan. **Multi-VM support:** -- Both HAL implementations support multiple `mrb_state` instances +- Both built-in ports support multiple `mrb_state` instances - A single system timer ticks all registered VMs - Maximum VMs: configurable via `MRB_TASK_MAX_VMS` (default: 8) -#### Custom HAL Implementation +#### Adding a New HAL -For embedded systems or unsupported platforms, you can create a custom HAL gem. The HAL must provide five functions defined in `mruby-task/include/task_hal.h`: +To support a new platform (an RTOS, a UI runloop like GLib or Cocoa, +a bare-metal target), add a new directory +`mruby-task/ports//task_hal.c` and contribute it upstream. The +HAL must implement the six functions declared in +`mruby-task/include/task_hal.h`: ```c /** - * Initialize timer and register VM - * Called during gem initialization - * Must set up periodic timer to call mrb_tick(mrb) every MRB_TICK_UNIT ms + * Initialize timer and register VM. + * Called during gem initialization. Must set up a periodic timer + * that calls mrb_tick(mrb) every MRB_TICK_UNIT milliseconds. */ -void mrb_task_hal_init(mrb_state *mrb); +void mrb_hal_task_init(mrb_state *mrb); /** - * Cleanup timer and unregister VM - * Called during gem finalization + * Cleanup timer and unregister VM. + * Called during gem finalization. */ -void mrb_task_hal_final(mrb_state *mrb); +void mrb_hal_task_final(mrb_state *mrb); /** - * Enable timer interrupts (exit critical section) - * Must be reentrant for nested calls + * Enable timer interrupts (exit critical section). + * Must be reentrant for nested calls. */ void mrb_task_enable_irq(void); /** - * Disable timer interrupts (enter critical section) - * Must be reentrant for nested calls + * Disable timer interrupts (enter critical section). + * Must be reentrant for nested calls. */ void mrb_task_disable_irq(void); /** - * Put CPU in low-power/idle mode - * Called when no tasks are ready but some are waiting - * Should sleep ~MRB_TICK_UNIT milliseconds + * Put CPU in low-power/idle mode. + * Called when no tasks are ready but some are waiting; should sleep + * roughly MRB_TICK_UNIT milliseconds and allow the timer to fire. */ -void mrb_task_hal_idle_cpu(mrb_state *mrb); -``` - -**Example custom HAL gem structure:** +void mrb_hal_task_idle_cpu(mrb_state *mrb); -``` -mrbgems/hal-myplatform-task/ -├── mrbgem.rake # Gem specification -├── include/ -│ └── task_hal.h # Symlink to mruby-task/include/task_hal.h -└── src/ - └── task_hal.c # Platform implementation -``` - -**mrbgem.rake:** - -```ruby -MRuby::Gem::Specification.new('hal-myplatform-task') do |spec| - spec.license = 'MIT' - spec.authors = 'Your Name' - spec.summary = 'My Platform HAL for mruby-task' - - # HAL gem depends on feature gem (important for build order) - spec.add_dependency 'mruby-task', core: 'mruby-task' - - # Add any platform-specific libraries or flags - # spec.linker.libraries << 'myplatform_timer' -end -``` - -**task_hal.c example for embedded system:** - -```c -#include -#include "task_hal.h" -#include "myplatform_hardware.h" - -static mrb_state *registered_vm = NULL; - -void mrb_task_hal_init(mrb_state *mrb) -{ - registered_vm = mrb; - - // Setup hardware timer to fire every MRB_TICK_UNIT milliseconds - hardware_timer_init(MRB_TICK_UNIT, timer_isr); - hardware_timer_start(); -} - -void mrb_task_hal_final(mrb_state *mrb) -{ - hardware_timer_stop(); - registered_vm = NULL; -} - -void mrb_task_enable_irq(void) -{ - hardware_enable_interrupts(); -} - -void mrb_task_disable_irq(void) -{ - hardware_disable_interrupts(); -} - -void mrb_task_hal_idle_cpu(mrb_state *mrb) -{ - (void)mrb; - hardware_sleep_mode(); // Enter low-power mode until interrupt -} - -// Timer ISR - must call mrb_tick() for scheduler -void timer_isr(void) -{ - if (registered_vm) { - mrb_tick(registered_vm); - } -} - -// Gem initialization (required but can be empty) -void mrb_hal_myplatform_task_gem_init(mrb_state *mrb) -{ - (void)mrb; -} - -void mrb_hal_myplatform_task_gem_final(mrb_state *mrb) -{ - (void)mrb; -} +/** + * Sleep for the given number of microseconds of wall-clock time. + * Must allow timer interrupts/callbacks during the sleep and should + * complete the full duration even if interrupted. + */ +void mrb_hal_task_sleep_us(mrb_state *mrb, mrb_int usec); ``` -See `hal-posix-task` and `hal-win-task` source code for complete reference implementations. +Users selecting your port set `conf.ports :`; the built-in +POSIX and Windows ports are then skipped, so there is no symbol +clash. See `mruby-task/ports/posix/task_hal.c` and +`mruby-task/ports/win/task_hal.c` for reference implementations. ## C API @@ -657,6 +695,49 @@ sleep 1 task.terminate # Stop permanently ``` +### Producer/Consumer with Task::Queue + +Using `Task::Queue` eliminates polling. The consumer blocks on `pop` and wakes +automatically when the producer pushes an item. + +```ruby +q = Task::Queue.new +results = [] + +Task.new(name: "producer") do + ["a", "b", "c"].each do |v| + q.push(v) + sleep 0.1 + end + q.close +end + +Task.new(name: "consumer") do + loop do + item = q.pop # blocks here until an item is available or the queue closes + break if item.nil? + results << item + end +end + +Task.run +puts results.inspect # => ["a", "b", "c"] +``` + +Multiple producers and consumers work naturally: + +```ruby +q = Task::Queue.new + +3.times { |i| Task.new { q.push(i) } } + +received = [] +3.times { Task.new { received << q.pop } } + +Task.run +puts received.sort.inspect # => [0, 1, 2] +``` + ## Limitations and Compatibility ### Relationship with Fiber @@ -701,6 +782,10 @@ The gem includes tests that verify: - Join synchronization - Suspend and resume - Task.pass cooperative yielding +- Task::Queue push/pop FIFO order +- Task::Queue blocking pop and wakeup on push +- Task::Queue close semantics +- Task::Queue num_waiting count Run tests with: @@ -717,7 +802,7 @@ Each task can be in one of five states: - `DORMANT (0x00)`: Not started or finished - `READY (0x02)`: Ready to run - `RUNNING (0x03)`: Currently executing -- `WAITING (0x04)`: Waiting (sleep, join, mutex) +- `WAITING (0x04)`: Waiting (sleep, join, queue) - `SUSPENDED (0x08)`: Manually suspended ### Wait Reasons @@ -728,6 +813,7 @@ When a task is in WAITING state, the reason indicates why: - `SLEEP (0x01)`: Sleeping for time - `MUTEX (0x02)`: Waiting for mutex (reserved, not yet implemented) - `JOIN (0x04)`: Waiting for another task +- `QUEUE (0x08)`: Waiting for an item to be pushed to a `Task::Queue` ### Scheduler Algorithm @@ -756,6 +842,7 @@ The scheduler includes a lock counter (`mrb->task.scheduler_lock`) that prevents Planned features not yet implemented: - **Mutex support**: Thread-safe synchronization primitives +- **Task::SizedQueue**: Bounded queue with backpressure (push blocks when full) - **Task.raise**: Throw exceptions to other tasks - **Task#value**: Retrieve task return value (like Thread#value) - **Per-task timeslice configuration** diff --git a/mrbgems/mruby-task/examples/queue.rb b/mrbgems/mruby-task/examples/queue.rb new file mode 100644 index 0000000000..0308de8bff --- /dev/null +++ b/mrbgems/mruby-task/examples/queue.rb @@ -0,0 +1,54 @@ +# Task::Queue Example +# Demonstrates producer/consumer coordination using Task::Queue. +# No polling required - consumers block until items are available. + +puts "=== Task::Queue Producer/Consumer Demo ===" +puts + +TOTAL_ITEMS = 10 +q = Task::Queue.new +produced = 0 +consumed = 0 + +producer = Task.new(name: "producer") do + TOTAL_ITEMS.times do |i| + item = "item-#{i}" + q.push(item) + produced += 1 + puts "Producer: pushed #{item}" + sleep 0.1 + end + q.close + puts "Producer: closed queue after #{produced} items" +end + +consumer1 = Task.new(name: "consumer-1") do + loop do + item = q.pop # blocks until an item is available or queue closes + break if item.nil? + consumed += 1 + puts "Consumer-1: got #{item}" + sleep 0.15 + end + puts "Consumer-1: done" +end + +consumer2 = Task.new(name: "consumer-2") do + loop do + item = q.pop + break if item.nil? + consumed += 1 + puts "Consumer-2: got #{item}" + sleep 0.2 + end + puts "Consumer-2: done" +end + +Task.run + +puts +puts "=== Summary ===" +puts "Produced: #{produced}" +puts "Consumed: #{consumed}" +puts "Queue size: #{q.size}" +puts "Queue closed: #{q.closed?}" diff --git a/mrbgems/mruby-task/include/task.h b/mrbgems/mruby-task/include/task.h index 25154ba1be..6a464eb9c1 100644 --- a/mrbgems/mruby-task/include/task.h +++ b/mrbgems/mruby-task/include/task.h @@ -28,8 +28,11 @@ enum { MRB_TASK_REASON_SLEEP = 0x01, /* Sleeping for time */ MRB_TASK_REASON_MUTEX = 0x02, /* Waiting for mutex (reserved) */ MRB_TASK_REASON_JOIN = 0x04, /* Waiting for another task */ + MRB_TASK_REASON_QUEUE = 0x08, /* Waiting for queue item */ }; +struct mrb_task_queue; + /* * Task structure - represents a single task in the scheduler * @@ -38,7 +41,6 @@ enum { * - Removed started flag (inferred from c.status): 1 byte * - Unified wakeup_tick/join/mutex into single union: 4 bytes * - Removed redundant proc field (stored in c.ci->proc): 8 bytes - * - Unified timeslice/result into state union: ~4 bytes * Total savings: ~18 bytes per task (14% reduction) */ typedef struct mrb_task { @@ -46,6 +48,7 @@ typedef struct mrb_task { uint8_t priority; /* Priority (0-255, 0=highest) */ uint8_t status; /* Current status (TASKSTATUS enum) */ uint8_t reason; /* Wait reason (TASKREASON enum) */ + volatile uint8_t timeslice; /* Remaining ticks while RUNNING */ mrb_value name; /* Optional task name */ /* Wait-specific data - mutually exclusive based on reason field */ @@ -53,15 +56,12 @@ typedef struct mrb_task { uint32_t wakeup_tick; /* Tick count to wake up (REASON_SLEEP) */ const struct mrb_task *join; /* Task being waited on (REASON_JOIN) */ void *mutex; /* Mutex pointer (REASON_MUTEX, reserved) */ + struct mrb_task_queue *queue; /* Queue being waited on (REASON_QUEUE) */ } wait; mrb_value self; /* Ruby Task object reference */ - /* State-specific data - mutually exclusive based on status */ - union { - volatile uint8_t timeslice; /* Remaining ticks (RUNNING only) */ - mrb_value result; /* Task return value (DORMANT only) */ - } state; + mrb_value result; /* Task return value */ struct mrb_context c; /* Execution context (stack, callinfo, etc) */ } mrb_task; @@ -91,14 +91,8 @@ typedef struct mrb_task { #define TASK_CI_INIT_SIZE 4 /* Initial task callinfo size */ /* - * HAL (Hardware Abstraction Layer) functions - * Platform-specific implementations must provide these + * HAL (Hardware Abstraction Layer) functions are declared in task_hal.h. */ -void mrb_task_hal_init(mrb_state *mrb); -void mrb_task_hal_final(mrb_state *mrb); -void mrb_task_enable_irq(void); -void mrb_task_disable_irq(void); -void mrb_task_hal_idle_cpu(mrb_state *mrb); /* * GC integration @@ -140,4 +134,38 @@ MRB_API void mrb_task_init_context(mrb_state *mrb, mrb_value task, struct RProc MRB_API void mrb_task_reset_context(mrb_state *mrb, mrb_value task); MRB_API void mrb_task_proc_set(mrb_state *mrb, mrb_value task, struct RProc *proc); +/* + * Internal helpers - used by task.c and task_queue.c + */ +#include +#include "task_hal.h" + +/* Scheduler state accessors (require a local mrb variable in scope) */ +#define q_dormant_ (mrb->task.queues[MRB_TASK_QUEUE_DORMANT]) +#define q_ready_ (mrb->task.queues[MRB_TASK_QUEUE_READY]) +#define q_waiting_ (mrb->task.queues[MRB_TASK_QUEUE_WAITING]) +#define q_suspended_ (mrb->task.queues[MRB_TASK_QUEUE_SUSPENDED]) +#define tick_ (mrb->task.tick) +#define wakeup_tick_ (mrb->task.wakeup_tick) +#define switching_ (mrb->task.switching) + +/* Recover the mrb_task that owns the current mruby context */ +#define MRB2TASK(mrb) ((mrb_task *)((uint8_t *)(mrb)->c - offsetof(mrb_task, c))) + +/* Raise if the scheduler is locked (synchronous execution in progress) */ +static inline void +task_check_scheduler_lock(mrb_state *mrb) +{ + if (mrb->task.scheduler_lock > 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot use asynchronous Task API during synchronous execution"); + } +} + +/* Priority-queue insert/delete - defined in task.c */ +void mrb_task_q_insert(mrb_state *mrb, mrb_task *t); +void mrb_task_q_delete(mrb_state *mrb, mrb_task *t); + +/* Task::Queue class registration - defined in task_queue.c */ +void mrb_init_task_queue(mrb_state *mrb, struct RClass *task_class); + #endif /* MRUBY_TASK_H */ diff --git a/mrbgems/mruby-task/include/task_hal.h b/mrbgems/mruby-task/include/task_hal.h index 907320596f..b79827ed29 100644 --- a/mrbgems/mruby-task/include/task_hal.h +++ b/mrbgems/mruby-task/include/task_hal.h @@ -35,8 +35,8 @@ /* * HAL Interface Functions * - * Platform-specific implementations (hal-posix-task, hal-win-task, etc.) - * must provide these functions. + * The port directory under mruby-task/ports// provides + * these functions. See README.md for adding a new port. */ /** diff --git a/mrbgems/mruby-task/mrbgem.rake b/mrbgems/mruby-task/mrbgem.rake index 7edc620815..484ac60c69 100644 --- a/mrbgems/mruby-task/mrbgem.rake +++ b/mrbgems/mruby-task/mrbgem.rake @@ -6,31 +6,44 @@ MRuby::Gem::Specification.new('mruby-task') do |spec| # Enable task scheduler globally (required for vm.c integration) spec.build.defines << 'MRB_USE_TASK_SCHEDULER' - # Check if HAL gem is loaded - # HAL gems must be explicitly specified in build config (recommended) or via auto-selection below - spec.build.gems.one? { |g| g.name =~ /^hal-.*-task$/ } or begin - # No HAL found - determine appropriate error message or auto-load - suggested_hal = if spec.for_windows? - # Windows (including MinGW) - use Windows HAL - 'hal-win-task' - elsif RUBY_PLATFORM =~ /linux|darwin|bsd/ - 'hal-posix-task' - else - nil + if spec.for_windows? + spec.linker.libraries << "winmm" + end + + ports = spec.build.effective_ports + + # ports/glib/ needs glib-2.0 (GSource, GMainContext, GRecMutex) and + # gthread-2.0 (GThread). On modern distros gthread-2.0 is a transparent + # alias for glib-2.0; on older ones it's a separate .pc that pulls in + # -lpthread, so query it separately. + if ports.include?('glib') + unless spec.search_package('glib-2.0') && spec.search_package('gthread-2.0') + abort <<~MSG + [mruby-task] conf.ports :glib selected but pkg-config could not find + glib-2.0 / gthread-2.0. Install the GLib development headers + (Debian/Ubuntu: libglib2.0-dev; Fedora: glib2-devel; Arch: glib2; + macOS Homebrew: glib). For non-default install locations, set + PKG_CONFIG_PATH before invoking rake. + MSG end + end - if suggested_hal - # Auto-load HAL gem for convenience (for development) - # This works because HAL gems declare dependency on mruby-task - warn "mruby-task: No HAL specified, loading #{suggested_hal} (explicit selection recommended)" - spec.build.gem core: suggested_hal - else - # Unknown platform - fail with helpful message - fail "mruby-task: No HAL available for platform '#{RUBY_PLATFORM}'.\n" \ - "Please specify HAL gem explicitly in your build config:\n" \ - " conf.gem core: 'hal-posix-task' # For Linux/macOS/BSD\n" \ - " conf.gem core: 'hal-win-task' # For Windows\n" \ - "Or create custom HAL - see mrbgems/mruby-task/README.md" + # Optional demo tool that exercises the GLib HAL end-to-end (basic + # scheduling, priority ordering, timeslice preemption, auto-execution + # under a foreign GLib main loop). Default off; opt in from your + # build_config with: + # + # conf.cc.defines << 'MRB_TASK_BUILD_DEMO' + # conf.ports :glib + # conf.gem core: 'mruby-task' + # + # When enabled, `rake` produces bin/mruby-task-demo from + # tools/mruby-task-demo/. The define is only inspected here -- the + # demo's C source does not condition on it. + if spec.build.cc.defines.include?('MRB_TASK_BUILD_DEMO') + unless ports.include?('glib') + abort '[mruby-task] MRB_TASK_BUILD_DEMO requires conf.ports :glib' end + spec.bins = %w(mruby_task_demo) end end diff --git a/mrbgems/mruby-task/mrblib/queue.rb b/mrbgems/mruby-task/mrblib/queue.rb new file mode 100644 index 0000000000..792dab885e --- /dev/null +++ b/mrbgems/mruby-task/mrblib/queue.rb @@ -0,0 +1,30 @@ +class Task + class Queue + # WAIT_RETRY is defined in C (task_queue.c gem init) + + def push(obj) + __push(obj) + self + end + alias enq push + alias << push + + # Blocks until an item is available (default), or raises if non_block is true. + # Returns nil if the queue is closed and empty. + # + # The loop is not a busy-wait. When __pop_try finds the queue empty it moves + # the current task to WAITING and sets switching_=TRUE before returning + # WAIT_RETRY. The VM detects switching_ at the next opcode boundary and + # exits mrb_vm_exec, handing control back to the scheduler. This task does + # not run again until a push (or close) moves it back to READY. The loop + # body therefore executes at most once per wakeup event. + def pop(non_block = false) + loop do + v = __pop_try(non_block) + return v unless v.equal?(WAIT_RETRY) + end + end + alias deq pop + alias shift pop + end +end diff --git a/mrbgems/mruby-task/ports/glib/task_hal.c b/mrbgems/mruby-task/ports/glib/task_hal.c new file mode 100644 index 0000000000..a8a1dbe6a4 --- /dev/null +++ b/mrbgems/mruby-task/ports/glib/task_hal.c @@ -0,0 +1,536 @@ +/* +** task_hal.c - GLib HAL for mruby-task +** +** See Copyright Notice in mruby.h +** +** Drives the mruby-task scheduler from an embedding application's GLib +** main loop. No Task.run is required from Ruby; the HAL fires +** mrb_task_run_once and mrb_tick automatically as the host loop iterates, +** which is the integration pattern a GTK or webview app uses in practice. +** +** Two GSources collaborate: +** +** 1. VM-run source on the thread-default GMainContext. Its callback +** runs mrb_task_run_once on every registered VM. +** +** 2. Tick source on a dedicated GMainContext, iterated by a private +** GThread. Its callback runs mrb_tick on every registered VM under +** a recursive mutex. The separate thread is what gives us +** preemption: it can fire mrb_tick while the VM thread is blocked +** inside mrb_vm_exec. +** +** Both are manual GSources -- NULL prepare/check, dispatch driven purely +** by ready_time updates via g_source_set_ready_time. g_timeout_source's +** auto-reschedule would race with park-when-idle. +** +** State machine, evaluated after every dispatch under the IRQ lock: +** +** has_ready (any q_ready_) : vm_run = 0, tick = +1 interval +** has_sleep (q_waiting_) : vm_run = -1 (parked), tick = soonest sleeper +** neither : both = -1 (parked) +** +** In has_sleep state the ticker is the sole waker: it fires at the +** sleeper deadline, catches the scheduler clock up via mrb_tick, and +** sets vm_run = 0 once a task is promoted to ready. In neither state, +** mrb_task_enable_irq is the wake: any Ruby-side scheduler activity +** (Task.new from a bind callback, etc.) sets vm_run = 0 from outside. +** +** Tickless catch-up: in has_sleep state the ticker can be parked for +** arbitrarily long. On fire we compute (now - last_fire_us) + remainder, +** divide by MRB_TICK_INTERVAL_US, and call mrb_tick that many times in +** one go. The leftover < interval is carried in remainder_us to the +** next fire, keeping the scheduler clock aligned with monotonic time. +** Net effect: a loop of long sleeps costs one wakeup per sleep period. +** +** Threading: +** - Per-thread state lives in heap-allocated mrb_task_thread_state, +** reachable via thread-local `ts`. The ticker thread receives a +** pointer at spawn; it never touches another thread's TLS. +** - On the main thread no GMainContext push is needed; the default +** context is used implicitly. +** - On any other thread that opens an mrb_state, the caller MUST first +** call g_main_context_push_thread_default(). This is the standard +** GLib convention used by libsoup, GIO async, GTask, etc. +** +** Locking: +** - irq_lock (GRecMutex) covers every mutation of mrb->task state. +** mrb_task_disable_irq / mrb_task_enable_irq are lock / unlock, +** with the enable side additionally setting vm_run = 0. +** - The ticker holds the lock across its mrb_tick batch. +** - arm_locked runs with the lock held so a concurrent ticker can't +** promote a sleeper between our inspection and our arm decision. +** - mrb_vm_exec runs WITHOUT the lock; the ticker can preempt it +** mid-execution by setting switching_. +** +** Supported platforms: any system with GLib 2.x and GThread (Linux, +** BSD, macOS, Windows with MinGW or MSVC, ...). +*/ + +#include +#include +#include "task.h" +#include "task_hal.h" +#include +#include +#include + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +# define MRB_TASK_TLS _Thread_local +#elif defined(__GNUC__) || defined(__clang__) || defined(__SUNPRO_C) || defined(__xlC__) || defined(__IBMC__) +# define MRB_TASK_TLS __thread +#elif defined(_MSC_VER) || defined(__BORLANDC__) +# define MRB_TASK_TLS __declspec(thread) +#else +# error "mruby-task GLib HAL: no thread-local storage qualifier known for this compiler" +#endif + +#define MRB_TICK_INTERVAL_US ((gint64)MRB_TICK_UNIT * 1000) + +typedef struct mrb_task_thread_state { + mrb_state *vm_list[MRB_TASK_MAX_VMS]; + int vm_count; + GRecMutex irq_lock; + + GMainContext *vm_ctx; + GSource *vm_run_src; + + GMainContext *tick_ctx; + GMainLoop *tick_loop; + GSource *tick_src; + GThread *ticker; + + /* Tickless catch-up: last_fire_us is the monotonic anchor; remainder_us + * is the sub-interval carry from the previous fire. */ + gint64 last_fire_us; + uint32_t remainder_us; +} mrb_task_thread_state; + +static MRB_TASK_TLS mrb_task_thread_state *ts; + +static gboolean +deadline_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) +{ + (void)source; + if (!callback) { + return G_SOURCE_REMOVE; + } + return callback(user_data); +} + +static GSourceFuncs deadline_source_funcs = { + NULL, NULL, deadline_source_dispatch, NULL, NULL, NULL, +}; + +static void +free_thread_state(mrb_task_thread_state *s) +{ + if (!s) { + return; + } + if (s->ticker) { + if (s->tick_loop) { + g_main_loop_quit(s->tick_loop); + } + (void)g_thread_join(s->ticker); + s->ticker = NULL; + } + if (s->tick_src) { + g_source_destroy(s->tick_src); + g_source_unref(s->tick_src); + s->tick_src = NULL; + } + if (s->tick_loop) { + g_main_loop_unref(s->tick_loop); + s->tick_loop = NULL; + } + if (s->tick_ctx) { + g_main_context_unref(s->tick_ctx); + s->tick_ctx = NULL; + } + if (s->vm_run_src) { + g_source_destroy(s->vm_run_src); + g_source_unref(s->vm_run_src); + s->vm_run_src = NULL; + } + if (s->vm_ctx) { + g_main_context_unref(s->vm_ctx); + s->vm_ctx = NULL; + } + g_rec_mutex_clear(&s->irq_lock); + g_free(s); +} + +/* + * Walk every VM's queues, decide the arm state, apply it. Called with + * the IRQ lock held. q_waiting_ is walked directly to find the soonest + * SLEEP-reason wakeup; this is authoritative regardless of what + * mrb->task.wakeup_tick currently reads. + * + * Idempotent. Deadlines are computed relative to s->last_fire_us (the + * last actual ticker fire) rather than g_get_monotonic_time(), so + * repeated calls between fires yield the same ready_time and don't + * drift the cadence. set_ready_time is skipped when the value + * wouldn't change, to avoid the eventfd-write side effect. + */ +static void +arm_locked(mrb_task_thread_state *s) +{ + gint64 monotonic_now = g_get_monotonic_time(); + int32_t min_wake_offset = 0; + gboolean has_ready = FALSE; + gboolean has_sleep = FALSE; + gint64 new_vm_ready; + gint64 new_tick_ready; + gint64 cur_vm_ready; + gint64 cur_tick_ready; + int i; + + for (i = 0; i < s->vm_count; i++) { + mrb_state *vm = s->vm_list[i]; + if (!vm) continue; + + if (vm->task.queues[MRB_TASK_QUEUE_READY] != NULL) { + has_ready = TRUE; + continue; + } + + mrb_task *w = vm->task.queues[MRB_TASK_QUEUE_WAITING]; + while (w) { + if (w->reason == MRB_TASK_REASON_SLEEP) { + uint32_t wake = w->wait.wakeup_tick; + uint32_t tick = vm->task.tick; + int32_t offset = (int32_t)(wake - tick); + if (!has_sleep || offset < min_wake_offset) { + min_wake_offset = offset; + has_sleep = TRUE; + } + } + w = w->next; + } + } + + /* Anchor the catch-up clock when the ticker transitions from parked + * to active, so elapsed time counts from "now" rather than from + * before the park. */ + if ((has_ready || has_sleep) && + g_source_get_ready_time(s->tick_src) == -1) { + s->last_fire_us = monotonic_now; + s->remainder_us = 0; + } + + if (has_ready) { + new_vm_ready = 0; + new_tick_ready = s->last_fire_us + MRB_TICK_INTERVAL_US; + } + else if (has_sleep) { + new_vm_ready = -1; + if (min_wake_offset <= 0) { + new_tick_ready = 0; /* overdue, fire immediately */ + } + else { + new_tick_ready = s->last_fire_us + + (gint64)min_wake_offset * MRB_TICK_INTERVAL_US; + } + } + else { + new_vm_ready = -1; + new_tick_ready = -1; + } + + cur_vm_ready = g_source_get_ready_time(s->vm_run_src); + cur_tick_ready = g_source_get_ready_time(s->tick_src); + + if (new_vm_ready != cur_vm_ready) { + g_source_set_ready_time(s->vm_run_src, new_vm_ready); + } + if (new_tick_ready != cur_tick_ready) { + g_source_set_ready_time(s->tick_src, new_tick_ready); + } +} + +static gpointer +ticker_thread(gpointer data) +{ + mrb_task_thread_state *s = (mrb_task_thread_state *)data; + g_main_context_push_thread_default(s->tick_ctx); + g_main_loop_run(s->tick_loop); + g_main_context_pop_thread_default(s->tick_ctx); + return NULL; +} + +/* + * Tick GSource callback on the ticker thread. Computes catch-up ticks + * from elapsed monotonic time plus carried remainder, calls mrb_tick + * that many times under irq_lock, then lets arm_locked decide the + * next state (steady-state cadence vs tickless deadline vs full park). + */ +static gboolean +tick_source_cb(gpointer user_data) +{ + mrb_task_thread_state *s = (mrb_task_thread_state *)user_data; + int i; + gint64 now = g_get_monotonic_time(); + gint64 total_us = (now - s->last_fire_us) + (gint64)s->remainder_us; + gint64 raw_ticks = total_us / (gint64)MRB_TICK_INTERVAL_US; + uint32_t catch_up_ticks = (raw_ticks > (gint64)UINT32_MAX) + ? UINT32_MAX + : (uint32_t)raw_ticks; + + s->remainder_us = (uint32_t)(total_us - + (gint64)catch_up_ticks * (gint64)MRB_TICK_INTERVAL_US); + s->last_fire_us = now; + + g_rec_mutex_lock(&s->irq_lock); + for (i = 0; i < s->vm_count; i++) { + mrb_state *vm = s->vm_list[i]; + if (!vm) continue; + for (uint32_t k = 0; k < catch_up_ticks; k++) { + mrb_tick(vm); + } + } + arm_locked(s); + g_rec_mutex_unlock(&s->irq_lock); + + return G_SOURCE_CONTINUE; +} + +/* + * VM-run GSource callback on the VM thread. Snapshots vm_list under + * the IRQ lock, runs mrb_task_run_once on each entry outside the + * lock, then re-acquires to decide the next arm state. The snapshot + * protects against list-shape changes while we're iterating (e.g., a + * task body that registers or removes another mrb_state on this + * thread). + */ +static gboolean +vm_run_source_cb(gpointer user_data) +{ + mrb_state *snapshot[MRB_TASK_MAX_VMS]; + int snapshot_count; + int i; + (void)user_data; + + if (!ts) { + return G_SOURCE_CONTINUE; + } + + g_rec_mutex_lock(&ts->irq_lock); + snapshot_count = ts->vm_count; + memcpy(snapshot, ts->vm_list, (size_t)snapshot_count * sizeof(mrb_state *)); + g_rec_mutex_unlock(&ts->irq_lock); + + for (i = 0; i < snapshot_count; i++) { + if (snapshot[i]) { + (void)mrb_task_run_once(snapshot[i]); + } + } + + g_rec_mutex_lock(&ts->irq_lock); + arm_locked(ts); + g_rec_mutex_unlock(&ts->irq_lock); + + return G_SOURCE_CONTINUE; +} + +void +mrb_hal_task_init(mrb_state *mrb) +{ + int i; + int idx = -1; + gboolean first_on_thread = FALSE; + guint attach_id; + GError *err = NULL; + gchar *err_msg; + + for (i = 0; i < MRB_NUM_TASK_QUEUE; i++) { + mrb->task.queues[i] = NULL; + } + mrb->task.tick = 0; + mrb->task.wakeup_tick = UINT32_MAX; + mrb->task.switching = FALSE; + + if (ts == NULL) { + ts = g_new0(mrb_task_thread_state, 1); + g_rec_mutex_init(&ts->irq_lock); + ts->last_fire_us = g_get_monotonic_time(); + first_on_thread = TRUE; + } + + g_rec_mutex_lock(&ts->irq_lock); + + for (i = 0; i < ts->vm_count; i++) { + if (ts->vm_list[i] == mrb) { + idx = i; + break; + } + } + + if (idx < 0) { + if (ts->vm_count >= MRB_TASK_MAX_VMS) { + g_rec_mutex_unlock(&ts->irq_lock); + if (first_on_thread) { + free_thread_state(ts); + ts = NULL; + } + mrb_raisef(mrb, E_RUNTIME_ERROR, + "too many mrb_states with task scheduler on this thread " + "(max: %d)", + MRB_TASK_MAX_VMS); + } + ts->vm_list[ts->vm_count++] = mrb; + } + + g_rec_mutex_unlock(&ts->irq_lock); + + if (first_on_thread) { + ts->vm_ctx = g_main_context_ref_thread_default(); + g_assert_nonnull(ts->vm_ctx); + + ts->vm_run_src = g_source_new(&deadline_source_funcs, sizeof(GSource)); + g_assert_nonnull(ts->vm_run_src); + g_source_set_callback(ts->vm_run_src, vm_run_source_cb, NULL, NULL); + g_source_set_ready_time(ts->vm_run_src, -1); + + attach_id = g_source_attach(ts->vm_run_src, ts->vm_ctx); + if (attach_id == 0) { + free_thread_state(ts); + ts = NULL; + mrb_raise(mrb, E_RUNTIME_ERROR, + "mruby-task GLib HAL: g_source_attach failed for VM-run source"); + } + + ts->tick_ctx = g_main_context_new(); + g_assert_nonnull(ts->tick_ctx); + + ts->tick_loop = g_main_loop_new(ts->tick_ctx, FALSE); + g_assert_nonnull(ts->tick_loop); + + ts->tick_src = g_source_new(&deadline_source_funcs, sizeof(GSource)); + g_assert_nonnull(ts->tick_src); + g_source_set_callback(ts->tick_src, tick_source_cb, ts, NULL); + g_source_set_ready_time(ts->tick_src, -1); + + attach_id = g_source_attach(ts->tick_src, ts->tick_ctx); + if (attach_id == 0) { + free_thread_state(ts); + ts = NULL; + mrb_raise(mrb, E_RUNTIME_ERROR, + "mruby-task GLib HAL: g_source_attach failed for tick source"); + } + + ts->ticker = g_thread_try_new("mruby-task-tick", ticker_thread, ts, &err); + if (ts->ticker == NULL) { + /* Copy GLib's error message into a stack buffer before any mruby + * allocation, so mrb_raise's longjmp can't strand the GLib heap. */ + char buf[256]; + err_msg = g_strdup_printf( + "mruby-task GLib HAL: failed to spawn ticker thread: %s", + err ? err->message : "unknown error"); + g_strlcpy(buf, err_msg, sizeof(buf)); + g_free(err_msg); + if (err) { + g_error_free(err); + } + free_thread_state(ts); + ts = NULL; + mrb_raise(mrb, E_RUNTIME_ERROR, buf); + } + } +} + +void +mrb_hal_task_final(mrb_state *mrb) +{ + int i, j; + gboolean last_on_thread = FALSE; + + if (ts == NULL) { + return; + } + + g_rec_mutex_lock(&ts->irq_lock); + + for (i = 0; i < ts->vm_count; i++) { + if (ts->vm_list[i] == mrb) { + for (j = i; j < ts->vm_count - 1; j++) { + ts->vm_list[j] = ts->vm_list[j + 1]; + } + ts->vm_list[ts->vm_count - 1] = NULL; + ts->vm_count--; + break; + } + } + + if (ts->vm_count == 0) { + last_on_thread = TRUE; + } + + g_rec_mutex_unlock(&ts->irq_lock); + + if (last_on_thread) { + free_thread_state(ts); + ts = NULL; + } +} + +void +mrb_task_disable_irq(void) +{ + if (ts) { + g_rec_mutex_lock(&ts->irq_lock); + } +} + +/* Hooked into the scheduler's IRQ-release path. After any state + * change, re-evaluate the arm state of both sources via arm_locked. + * This is what wires up preemption: arm_locked sets tick_src's + * ready_time so the ticker thread fires mrb_tick on cadence. + * + * Without this, only vm_run_source_cb (the foreign-loop dispatch + * callback) ever calls arm_locked, so a Task.run-driven scheduler + * never arms the ticker -- preemption never happens, sleepers in + * q_waiting_ are never woken, and CPU-bound tasks spin forever. + * Calling arm_locked here covers both Task.run and foreign-loop + * drivers symmetrically. + * + * arm_locked also sets vm_run_src ready_time to 0 only when there's + * a ready task, which prevents the spurious wake during mrb_task_run's + * idle queue check (the disable_irq/check/enable_irq pattern over a + * read-only check produces no state change, so vm_run_src stays + * parked at -1). */ +void +mrb_task_enable_irq(void) +{ + if (!ts) { + return; + } + arm_locked(ts); + g_rec_mutex_unlock(&ts->irq_lock); +} + +/* Called only from mrb_task_run's idle loop. Iterates the VM context + * to dispatch any pending vm_run_src (e.g., the ticker just woke us + * because a sleeper became ready), then returns. Block-wait is OK now + * because mrb_task_enable_irq's q_ready_ guard prevents the spurious- + * wake loop that would otherwise spin this iteration. */ +void +mrb_hal_task_idle_cpu(mrb_state *mrb) +{ + (void)mrb; + if (ts && ts->vm_ctx) { + (void)g_main_context_iteration(ts->vm_ctx, TRUE); + } + else { + g_usleep(MRB_TICK_UNIT * 1000); + } +} + +void +mrb_hal_task_sleep_us(mrb_state *mrb, mrb_int usec) +{ + (void)mrb; + if (usec <= 0) { + return; + } + g_usleep((gulong)usec); +} diff --git a/mrbgems/hal-posix-task/src/task_hal.c b/mrbgems/mruby-task/ports/posix/task_hal.c similarity index 94% rename from mrbgems/hal-posix-task/src/task_hal.c rename to mrbgems/mruby-task/ports/posix/task_hal.c index 6e293b704a..e653da3140 100644 --- a/mrbgems/hal-posix-task/src/task_hal.c +++ b/mrbgems/mruby-task/ports/posix/task_hal.c @@ -232,21 +232,3 @@ mrb_hal_task_final(mrb_state *mrb) (void)mrb; #endif } - -/* - * Gem initialization (empty - HAL functions called by mruby-task) - */ - -void -mrb_hal_posix_task_gem_init(mrb_state *mrb) -{ - (void)mrb; - /* HAL interface functions are called by mruby-task gem */ -} - -void -mrb_hal_posix_task_gem_final(mrb_state *mrb) -{ - (void)mrb; - /* Cleanup handled by mrb_hal_task_final called from mruby-task */ -} diff --git a/mrbgems/hal-win-task/src/task_hal.c b/mrbgems/mruby-task/ports/win/task_hal.c similarity index 91% rename from mrbgems/hal-win-task/src/task_hal.c rename to mrbgems/mruby-task/ports/win/task_hal.c index f6e306f3c8..83e6474412 100644 --- a/mrbgems/hal-win-task/src/task_hal.c +++ b/mrbgems/mruby-task/ports/win/task_hal.c @@ -167,21 +167,3 @@ mrb_hal_task_final(mrb_state *mrb) LeaveCriticalSection(&irq_lock); } } - -/* - * Gem initialization (empty - HAL functions called by mruby-task) - */ - -void -mrb_hal_win_task_gem_init(mrb_state *mrb) -{ - (void)mrb; - /* HAL interface functions are called by mruby-task gem */ -} - -void -mrb_hal_win_task_gem_final(mrb_state *mrb) -{ - (void)mrb; - /* Cleanup handled by mrb_hal_task_final called from mruby-task */ -} diff --git a/mrbgems/mruby-task/src/task.c b/mrbgems/mruby-task/src/task.c index a8a6274090..4e66a4053a 100644 --- a/mrbgems/mruby-task/src/task.c +++ b/mrbgems/mruby-task/src/task.c @@ -19,21 +19,6 @@ #include #include #include "task.h" -#include "task_hal.h" - -/* - * Queue helper macros - */ -#define q_dormant_ (mrb->task.queues[MRB_TASK_QUEUE_DORMANT]) -#define q_ready_ (mrb->task.queues[MRB_TASK_QUEUE_READY]) -#define q_waiting_ (mrb->task.queues[MRB_TASK_QUEUE_WAITING]) -#define q_suspended_ (mrb->task.queues[MRB_TASK_QUEUE_SUSPENDED]) -#define tick_ (mrb->task.tick) -#define wakeup_tick_ (mrb->task.wakeup_tick) -#define switching_ (mrb->task.switching) - -/* Get task from current context using pointer arithmetic */ -#define MRB2TASK(mrb) ((mrb_task *)((uint8_t *)mrb->c - offsetof(mrb_task, c))) /* Get task pointer from self with validation */ #define TASK_GET_PTR_OR_RAISE(var, self) \ @@ -50,15 +35,6 @@ /* Maximum value for scheduler_lock (uint8_t max) */ #define MRB_TASK_SCHEDULER_LOCK_MAX 255 -/* Check scheduler lock and raise error if locked */ -static inline void -task_check_scheduler_lock(mrb_state *mrb) -{ - if (mrb->task.scheduler_lock > 0) { - mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot use asynchronous Task API during synchronous execution"); - } -} - /* * Task data type for GC */ @@ -118,6 +94,16 @@ mrb_task_mark_all(mrb_state *mrb) for (i = 0; i < e; i++) { mrb_gc_mark_value(mrb, c->stbase[i]); } + /* Clear the dead slots above the live range, matching + mark_context_stack() in gc.c. A preempted task whose live range + later shrinks (a frame returned) would otherwise leave stale + object pointers in those slots; the objects get swept while the + pointers survive, and a subsequent mark of the resumed task trips + the MRB_TT_FREE assertion in mrb_gc_mark (issue #6870). */ + size_t stend = c->stend - c->stbase; + for (; i < stend; i++) { + SET_NIL_VALUE(c->stbase[i]); + } } /* Mark call stack */ @@ -137,9 +123,7 @@ mrb_task_mark_all(mrb_state *mrb) /* Mark task-specific values */ mrb_gc_mark_value(mrb, t->self); - if (t->status == MRB_TASK_STATUS_DORMANT) { - mrb_gc_mark_value(mrb, t->state.result); - } + mrb_gc_mark_value(mrb, t->result); mrb_gc_mark_value(mrb, t->name); t = t->next; @@ -171,8 +155,8 @@ q_get_queue(mrb_state *mrb, mrb_task *t) } /* Insert task into queue based on priority (higher priority = lower number = earlier in queue) */ -static void -q_insert_task(mrb_state *mrb, mrb_task *t) +void +mrb_task_q_insert(mrb_state *mrb, mrb_task *t) { mrb_task **q = q_get_queue(mrb, t); mrb_task *curr = *q; @@ -195,8 +179,8 @@ q_insert_task(mrb_state *mrb, mrb_task *t) } /* Delete task from its current queue */ -static void -q_delete_task(mrb_state *mrb, mrb_task *t) +void +mrb_task_q_delete(mrb_state *mrb, mrb_task *t) { mrb_task **q = q_get_queue(mrb, t); mrb_task *curr = *q; @@ -226,10 +210,10 @@ task_cleanup_if_stopped(mrb_state *mrb, mrb_task *t) if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_TASK_STOPPED) { /* Task is terminated but still in queue - remove it */ mrb_task_disable_irq(); - q_delete_task(mrb, t); + mrb_task_q_delete(mrb, t); if (t->status != MRB_TASK_STATUS_DORMANT) { t->status = MRB_TASK_STATUS_DORMANT; - q_insert_task(mrb, t); + mrb_task_q_insert(mrb, t); } mrb_task_enable_irq(); return TRUE; @@ -305,20 +289,28 @@ task_init_context(mrb_state *mrb, mrb_task *t, const struct RProc *proc) static void wake_up_join_waiters(mrb_state *mrb, mrb_task *completed_task) { + mrb_task_disable_irq(); mrb_task *curr = q_waiting_; while (curr != NULL) { mrb_task *next = curr->next; if (curr->reason == MRB_TASK_REASON_JOIN && curr->wait.join == completed_task) { - mrb_task_disable_irq(); - q_delete_task(mrb, curr); + mrb_task_q_delete(mrb, curr); curr->status = MRB_TASK_STATUS_READY; curr->reason = MRB_TASK_REASON_NONE; curr->wait.join = NULL; - q_insert_task(mrb, curr); - mrb_task_enable_irq(); + mrb_task_q_insert(mrb, curr); + /* If a higher-priority waiter is resumed from task context, + * request a context switch after leaving the critical section. */ + if (mrb->c != mrb->root_c && !switching_) { + mrb_task *running = MRB2TASK(mrb); + if (curr->priority < running->priority) { + switching_ = TRUE; + } + } } curr = next; } + mrb_task_enable_irq(); } /* Change task state with IRQ protection and queue management */ @@ -326,12 +318,33 @@ static void task_change_state(mrb_state *mrb, mrb_task *t, uint8_t new_status) { mrb_task_disable_irq(); - q_delete_task(mrb, t); + mrb_task_q_delete(mrb, t); t->status = new_status; - q_insert_task(mrb, t); + mrb_task_q_insert(mrb, t); mrb_task_enable_irq(); } +typedef struct execute_task_vm_args { + mrb_task *t; + const struct RProc *proc; + const mrb_code *pc; +} execute_task_vm_args; + +static mrb_value +execute_task_vm(mrb_state *mrb, void *ud) +{ + execute_task_vm_args *args = (execute_task_vm_args*)ud; + + mrb->task.exception_as_result = TRUE; + args->t->result = mrb_vm_exec(mrb, args->proc, args->pc); + if (mrb->exc) { + args->t->result = mrb_obj_value(mrb->exc); + mrb->exc = NULL; + } + mrb->task.exception_as_result = FALSE; + return args->t->result; +} + /* Execute a single task - core task execution logic */ static void execute_task(mrb_state *mrb, mrb_task *t) @@ -341,8 +354,8 @@ execute_task(mrb_state *mrb, mrb_task *t) uint8_t prev_cci; /* Set task as running */ + t->timeslice = MRB_TIMESLICE_TICK_COUNT; t->status = MRB_TASK_STATUS_RUNNING; - t->state.timeslice = MRB_TIMESLICE_TICK_COUNT; /* Switch to task context */ prev_c = mrb->c; @@ -366,8 +379,13 @@ execute_task(mrb_state *mrb, mrb_task *t) /* Set vmexec flag to prevent fiber_terminate from being called */ t->c.vmexec = TRUE; - /* Execute task - PC is saved in ci->pc from previous run */ - t->state.result = mrb_vm_exec(mrb, proc, pc); + /* Execute task - PC is saved in ci->pc from previous run. + Unhandled task exceptions are converted to the task result by + mrb_vm_exec() in task mode, so the scheduler protect frame stays intact. */ + execute_task_vm_args args = { t, proc, pc }; + mrb_bool error = FALSE; + t->result = mrb_protect_error(mrb, execute_task_vm, &args, &error); + mrb->task.exception_as_result = FALSE; /* Clear vmexec flag */ t->c.vmexec = FALSE; @@ -381,13 +399,25 @@ execute_task(mrb_state *mrb, mrb_task *t) prev_c->ci = prev_ci; prev_ci->cci = prev_cci; + /* If an abnormal path inside mrb_vm_exec() bypassed + exception_as_result and unwound via MRB_THROW (e.g. a + CINFO_SKIP frame), mrb_protect_error caught it and stored the + exception object in t->result. Force the task to terminate + cleanly so the scheduler keeps running instead of aborting - + re-raising into the scheduler would abort in pattern 1, where + no outer jmpbuf exists. The exception remains observable via + mrb_task_value() / Task#value. */ + if (error) { + t->c.status = MRB_TASK_STOPPED; + } + /* Handle task termination */ if (t->c.status == MRB_TASK_STOPPED) { switching_ = FALSE; mrb_task_disable_irq(); - q_delete_task(mrb, t); + mrb_task_q_delete(mrb, t); t->status = MRB_TASK_STATUS_DORMANT; - q_insert_task(mrb, t); + mrb_task_q_insert(mrb, t); mrb_task_enable_irq(); /* Wake up tasks waiting on join */ @@ -410,15 +440,23 @@ mrb_tick(mrb_state *mrb) /* Decrease timeslice for running task */ t = q_ready_; - if (t && t->status == MRB_TASK_STATUS_RUNNING && t->state.timeslice > 0) { - t->state.timeslice--; - if (t->state.timeslice == 0) { + if (t && t->status == MRB_TASK_STATUS_RUNNING && t->timeslice > 0) { + t->timeslice--; + if (t->timeslice == 0) { switching_ = TRUE; /* Trigger context switch */ } } - /* Wake up sleeping tasks whose wakeup time has passed */ - if ((int32_t)(wakeup_tick_ - tick_) <= 0) { + /* Wake up sleeping tasks whose wakeup time has passed. + * + * UINT32_MAX is the "no sleepers" sentinel. Without the explicit + * check, (int32_t)(UINT32_MAX - tick_) evaluates negative for the + * first half of the 32-bit range, so the queue walk fires every + * tick with nothing to do. Short-circuiting saves the walk in the + * common no-sleepers case -- benefits every HAL, not just + * tickless ones. */ + if (wakeup_tick_ != UINT32_MAX && + (int32_t)(wakeup_tick_ - tick_) <= 0) { mrb_task *curr = q_waiting_; mrb_task *next; uint32_t next_wakeup = UINT32_MAX; @@ -429,10 +467,10 @@ mrb_tick(mrb_state *mrb) if (curr->reason == MRB_TASK_REASON_SLEEP) { if ((int32_t)(curr->wait.wakeup_tick - tick_) <= 0) { /* Time to wake up */ - q_delete_task(mrb, curr); + mrb_task_q_delete(mrb, curr); curr->status = MRB_TASK_STATUS_READY; curr->reason = MRB_TASK_REASON_NONE; - q_insert_task(mrb, curr); + mrb_task_q_insert(mrb, curr); switching_ = TRUE; } else if (curr->wait.wakeup_tick < next_wakeup) { @@ -447,11 +485,14 @@ mrb_tick(mrb_state *mrb) } } -/* Main scheduler loop */ -MRB_API mrb_value -mrb_task_run(mrb_state *mrb) +/* Body of the main scheduler loop. Wrapped by mrb_task_run() under + mrb_protect_error so an exception raised from a task body unwinds + cleanly without leaving `loop_running` set. */ +static mrb_value +task_run_body(mrb_state *mrb, void *ud) { mrb_task *t; + (void)ud; while (1) { t = q_ready_; @@ -488,10 +529,27 @@ mrb_task_run(mrb_state *mrb) mrb_incremental_gc(mrb); } } - return mrb_nil_value(); } +/* Main scheduler loop */ +MRB_API mrb_value +mrb_task_run(mrb_state *mrb) +{ + if (mrb->task.loop_running) { + return mrb_nil_value(); + } + mrb->task.loop_running = TRUE; + + mrb_bool error = FALSE; + mrb_value result = mrb_protect_error(mrb, task_run_body, NULL, &error); + mrb->task.loop_running = FALSE; + if (error) { + mrb_exc_raise(mrb, result); + } + return result; +} + /* Single-step task execution for WASM event loop integration */ MRB_API mrb_value mrb_task_run_once(mrb_state *mrb) @@ -559,7 +617,7 @@ sleep_us_impl(mrb_state *mrb, uint32_t usec) mrb_task_disable_irq(); /* Remove from ready queue */ - q_delete_task(mrb, t); + mrb_task_q_delete(mrb, t); /* Move to waiting queue */ t->status = MRB_TASK_STATUS_WAITING; @@ -567,12 +625,20 @@ sleep_us_impl(mrb_state *mrb, uint32_t usec) /* Convert microseconds to ticks (tick unit is in milliseconds) */ t->wait.wakeup_tick = tick_ + USEC_TO_TICKS(usec); - /* Update next wakeup time if this task wakes earlier */ - if ((int32_t)(t->wait.wakeup_tick - wakeup_tick_) < 0) { + /* Update next wakeup time if this task wakes earlier. + * + * When wakeup_tick_ is UINT32_MAX (no prior sleepers), the + * unsigned subtraction wraps to a small positive value and the + * int32_t cast stays non-negative, so the update is skipped and + * the global stays stale until the next mrb_tick self-heals it. + * Handle the sentinel explicitly so tickless HALs that read this + * field get a consistent value as soon as the sleeper is + * installed. */ + if (wakeup_tick_ == UINT32_MAX || + (int32_t)(t->wait.wakeup_tick - wakeup_tick_) < 0) { wakeup_tick_ = t->wait.wakeup_tick; } - - q_insert_task(mrb, t); + mrb_task_q_insert(mrb, t); mrb_task_enable_irq(); @@ -597,9 +663,9 @@ mrb_f_sleep(mrb_state *mrb, mrb_value self) mrb_task *t = q_ready_; if (t) { mrb_task_disable_irq(); - q_delete_task(mrb, t); + mrb_task_q_delete(mrb, t); t->status = MRB_TASK_STATUS_SUSPENDED; - q_insert_task(mrb, t); + mrb_task_q_insert(mrb, t); mrb_task_enable_irq(); switching_ = TRUE; } @@ -648,6 +714,36 @@ mrb_f_usleep(mrb_state *mrb, mrb_value self) return mrb_fixnum_value(usec); } +/* Common task creation logic shared by Task.new and mrb_create_task */ +static mrb_task* +task_create_common(mrb_state *mrb, const struct RProc *proc, + mrb_value name, uint8_t priority) +{ + mrb_task *t = task_alloc(mrb); + t->priority = priority; + t->status = MRB_TASK_STATUS_READY; + t->reason = MRB_TASK_REASON_NONE; + t->name = name; + + mrb_value task_obj = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_get(mrb, "Task"), + t, &mrb_task_type)); + t->self = task_obj; + mrb_gc_register(mrb, task_obj); + task_init_context(mrb, t, proc); + + mrb_task_disable_irq(); + mrb_task_q_insert(mrb, t); + mrb_task_enable_irq(); + + if (q_ready_ && q_ready_->status == MRB_TASK_STATUS_RUNNING) { + if (t->priority < q_ready_->priority) { + switching_ = TRUE; + } + } + + return t; +} + /* * Task class methods */ @@ -675,14 +771,12 @@ mrb_task_s_new(mrb_state *mrb, mrb_value self) /* Parse keyword arguments */ if (!mrb_undef_p(kw_values[0])) { - /* Validate name type - must be String */ if (!mrb_string_p(kw_values[0])) { mrb_raise(mrb, E_TYPE_ERROR, "name must be a String"); } name_val = kw_values[0]; } if (!mrb_undef_p(kw_values[1])) { - /* Validate priority type - must be Integer */ if (!mrb_integer_p(kw_values[1])) { mrb_raise(mrb, E_TYPE_ERROR, "priority must be an Integer"); } @@ -692,38 +786,8 @@ mrb_task_s_new(mrb_state *mrb, mrb_value self) } } - /* Allocate and initialize task */ - mrb_task *t = task_alloc(mrb); - t->priority = (uint8_t)priority; - t->status = MRB_TASK_STATUS_READY; - t->reason = MRB_TASK_REASON_NONE; - t->name = name_val; - /* Note: proc is stored in t->c.ci->proc and marked via callinfo GC */ - - /* Create Ruby object to hold task */ - mrb_value task_obj = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_get_id(mrb, MRB_SYM(Task)), - t, &mrb_task_type)); - t->self = task_obj; - - /* Register with GC to protect task object from collection */ - mrb_gc_register(mrb, task_obj); - - /* Initialize task context */ - task_init_context(mrb, t, proc); - - /* Insert into ready queue */ - mrb_task_disable_irq(); - q_insert_task(mrb, t); - mrb_task_enable_irq(); - - /* Trigger context switch if this task has higher priority than current */ - if (q_ready_ && q_ready_->status == MRB_TASK_STATUS_RUNNING) { - if (t->priority < q_ready_->priority) { - switching_ = TRUE; - } - } - - return task_obj; + mrb_task *t = task_create_common(mrb, proc, name_val, (uint8_t)priority); + return t->self; } static mrb_value @@ -1026,8 +1090,8 @@ mrb_task_set_priority(mrb_state *mrb, mrb_value self) /* Re-sort in queue if task is ready */ if (t->status == MRB_TASK_STATUS_READY || t->status == MRB_TASK_STATUS_RUNNING) { - q_delete_task(mrb, t); - q_insert_task(mrb, t); + mrb_task_q_delete(mrb, t); + mrb_task_q_insert(mrb, t); } mrb_task_enable_irq(); @@ -1097,22 +1161,22 @@ mrb_task_join(mrb_state *mrb, mrb_value self) /* If task is already dormant, return immediately */ if (t->status == MRB_TASK_STATUS_DORMANT) { - return t->state.result; + return t->result; } /* Wait for task to complete */ mrb_task_disable_irq(); - q_delete_task(mrb, current); + mrb_task_q_delete(mrb, current); current->status = MRB_TASK_STATUS_WAITING; current->reason = MRB_TASK_REASON_JOIN; current->wait.join = t; - q_insert_task(mrb, current); + mrb_task_q_insert(mrb, current); mrb_task_enable_irq(); /* Trigger context switch */ switching_ = TRUE; - return t->state.result; + return t->result; } /* @@ -1164,7 +1228,7 @@ mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc_val, mrb_int argc, /* 3. Move task from DORMANT to READY */ mrb_task_disable_irq(); t->status = MRB_TASK_STATUS_READY; - q_insert_task(mrb, t); + mrb_task_q_insert(mrb, t); mrb_task_enable_irq(); /* 4. Execute the task in a dedicated loop (no context switching) */ @@ -1172,23 +1236,23 @@ mrb_execute_proc_synchronously(mrb_state *mrb, mrb_value proc_val, mrb_int argc, mrb->c = &t->c; while (t->c.status != MRB_TASK_STOPPED) { - t->state.result = mrb_vm_exec(mrb, mrb->c->ci->proc, mrb->c->ci->pc); + t->result = mrb_vm_exec(mrb, mrb->c->ci->proc, mrb->c->ci->pc); } /* If there's an unhandled exception after VM stops, save it as result */ if (mrb->exc) { - t->state.result = mrb_obj_value(mrb->exc); + t->result = mrb_obj_value(mrb->exc); } /* 5. Get result and clean up */ - mrb_value result = t->state.result; + mrb_value result = t->result; if (mrb_obj_ptr(result) == mrb->exc) { mrb->exc = NULL; /* Clear exception */ } /* 6. Free the temporary task's resources */ mrb_task_disable_irq(); - q_delete_task(mrb, t); + mrb_task_q_delete(mrb, t); mrb_task_enable_irq(); /* Prevent double-free: clear Data object's type before freeing task */ @@ -1248,42 +1312,14 @@ mrb_create_task(mrb_state *mrb, struct RProc *proc, mrb_value name, mrb_value pr /* Validate/default name */ mrb_value name_val = mrb_nil_p(name) ? mrb_str_new_lit(mrb, "(noname)") : name; - /* Allocate and initialize task */ - mrb_task *t = task_alloc(mrb); - t->priority = (uint8_t)prio; - t->status = MRB_TASK_STATUS_READY; - t->reason = MRB_TASK_REASON_NONE; - t->name = name_val; - - /* Create Ruby object to hold task */ - mrb_value task_obj = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_get(mrb, "Task"), - t, &mrb_task_type)); - t->self = task_obj; - - /* Register with GC to protect task object from collection */ - mrb_gc_register(mrb, task_obj); - - /* Initialize task context */ - task_init_context(mrb, t, proc); + mrb_task *t = task_create_common(mrb, proc, name_val, (uint8_t)prio); /* Set top_self if provided */ if (!mrb_nil_p(top_self)) { t->c.ci->stack[0] = top_self; } - /* Insert into ready queue */ - mrb_task_disable_irq(); - q_insert_task(mrb, t); - mrb_task_enable_irq(); - - /* Trigger context switch if this task has higher priority than current */ - if (q_ready_ && q_ready_->status == MRB_TASK_STATUS_RUNNING) { - if (t->priority < q_ready_->priority) { - switching_ = TRUE; - } - } - - return task_obj; + return t->self; } /* @@ -1350,11 +1386,22 @@ resume_task_internal(mrb_state *mrb, mrb_task *t) } } - /* Update wakeup_tick if task has sleep reason */ + /* Update wakeup_tick if task has sleep reason. + * + * Two fixes here vs the original: + * - The UINT32_MAX sentinel case (see comments in + * sleep_us_impl). + * - The read-modify-write on wakeup_tick_ races with + * mrb_tick, which also rewrites this field. sleep_us_impl + * already wraps its update in the IRQ pair; we need the + * same here to match the locking discipline. */ if (t->reason == MRB_TASK_REASON_SLEEP) { - if ((int32_t)(t->wait.wakeup_tick - wakeup_tick_) < 0) { + mrb_task_disable_irq(); + if (wakeup_tick_ == UINT32_MAX || + (int32_t)(t->wait.wakeup_tick - wakeup_tick_) < 0) { wakeup_tick_ = t->wait.wakeup_tick; } + mrb_task_enable_irq(); } } @@ -1381,10 +1428,10 @@ terminate_task_internal(mrb_state *mrb, mrb_task *t) if (t->status == MRB_TASK_STATUS_DORMANT) return; mrb_task_disable_irq(); - q_delete_task(mrb, t); + mrb_task_q_delete(mrb, t); t->status = MRB_TASK_STATUS_DORMANT; t->c.status = MRB_TASK_STOPPED; - q_insert_task(mrb, t); + mrb_task_q_insert(mrb, t); mrb_task_enable_irq(); wake_up_join_waiters(mrb, t); @@ -1436,7 +1483,7 @@ mrb_task_value(mrb_state *mrb, mrb_value task) mrb_task *t = (mrb_task*)mrb_data_check_get_ptr(mrb, task, &mrb_task_type); if (!t) return mrb_nil_value(); - return t->state.result; + return t->result; } /* @@ -1524,10 +1571,18 @@ mrb_mruby_task_gem_init(mrb_state *mrb) /* Initialize main task to NULL and scheduler_lock to 0 */ mrb->task.main_task = NULL; mrb->task.scheduler_lock = 0; + mrb->task.loop_running = FALSE; + mrb->task.exception_as_result = FALSE; task_class = mrb_define_class_id(mrb, MRB_SYM(Task), mrb->object_class); MRB_SET_INSTANCE_TT(task_class, MRB_TT_DATA); + /* Task::Error - base error class for task synchronization errors */ + mrb_define_class_under_id(mrb, task_class, MRB_SYM(Error), mrb->eStandardError_class); + + /* Task::Queue */ + mrb_init_task_queue(mrb, task_class); + /* Class methods */ mrb_define_class_method_id(mrb, task_class, MRB_SYM(new), mrb_task_s_new, MRB_ARGS_KEY(2,0)|MRB_ARGS_BLOCK()); mrb_define_class_method_id(mrb, task_class, MRB_SYM(current), mrb_task_s_current, MRB_ARGS_NONE()); @@ -1549,6 +1604,7 @@ mrb_mruby_task_gem_init(mrb_state *mrb) mrb_define_method_id(mrb, task_class, MRB_SYM(resume), mrb_task_resume, MRB_ARGS_NONE()); mrb_define_method_id(mrb, task_class, MRB_SYM(terminate), mrb_task_terminate, MRB_ARGS_NONE()); mrb_define_method_id(mrb, task_class, MRB_SYM(join), mrb_task_join, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, task_class, MRB_SYM(value), mrb_task_value, MRB_ARGS_NONE()); /* Kernel methods (module functions like CRuby) * Note: sleep and usleep override mruby-sleep's implementation to be task-aware diff --git a/mrbgems/mruby-task/src/task_queue.c b/mrbgems/mruby-task/src/task_queue.c new file mode 100644 index 0000000000..11175ff6fc --- /dev/null +++ b/mrbgems/mruby-task/src/task_queue.c @@ -0,0 +1,253 @@ +/* +** task_queue.c - Task::Queue implementation +*/ + +#include +#include +#include +#include +#include +#include +#include "task.h" + +typedef struct mrb_task_queue { + uint8_t closed; +} mrb_task_queue; + +static void +mrb_task_queue_free(mrb_state *mrb, void *ptr) +{ + mrb_free(mrb, ptr); +} + +static const struct mrb_data_type mrb_task_queue_type = { + "Task::Queue", mrb_task_queue_free, +}; + +static mrb_value wait_retry_; +static struct RClass *task_error_class_; + +/* Wake the highest-priority task waiting on this queue */ +static void +queue_wake_one_waiter(mrb_state *mrb, mrb_task_queue *q) +{ + mrb_task_disable_irq(); + mrb_task *curr = q_waiting_; + while (curr) { + mrb_task *next = curr->next; + if (curr->reason == MRB_TASK_REASON_QUEUE && curr->wait.queue == q) { + mrb_task_q_delete(mrb, curr); + curr->status = MRB_TASK_STATUS_READY; + curr->reason = MRB_TASK_REASON_NONE; + curr->wait.queue = NULL; + mrb_task_q_insert(mrb, curr); + switching_ = TRUE; + break; + } + curr = next; + } + mrb_task_enable_irq(); +} + +/* Wake all tasks waiting on this queue (used by close) */ +static void +queue_wake_all_waiters(mrb_state *mrb, mrb_task_queue *q) +{ + mrb_bool woke_any = FALSE; + mrb_task_disable_irq(); + mrb_task *curr = q_waiting_; + while (curr) { + mrb_task *next = curr->next; + if (curr->reason == MRB_TASK_REASON_QUEUE && curr->wait.queue == q) { + mrb_task_q_delete(mrb, curr); + curr->status = MRB_TASK_STATUS_READY; + curr->reason = MRB_TASK_REASON_NONE; + curr->wait.queue = NULL; + mrb_task_q_insert(mrb, curr); + woke_any = TRUE; + } + curr = next; + } + if (woke_any) { + switching_ = TRUE; + } + mrb_task_enable_irq(); +} + +static mrb_value +queue_initialize(mrb_state *mrb, mrb_value self) +{ + mrb_task_queue *q = (mrb_task_queue*)mrb_malloc(mrb, sizeof(mrb_task_queue)); + q->closed = 0; + mrb_data_init(self, q, &mrb_task_queue_type); + mrb_iv_set(mrb, self, mrb_intern_lit(mrb, "@items"), mrb_ary_new(mrb)); + return self; +} + +static mrb_value +queue_push(mrb_state *mrb, mrb_value self) +{ + mrb_value obj; + mrb_get_args(mrb, "o", &obj); + + mrb_task_queue *q = (mrb_task_queue*)mrb_data_get_ptr(mrb, self, &mrb_task_queue_type); + if (!q) mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid queue"); + if (q->closed) mrb_raise(mrb, task_error_class_, "queue closed"); + + mrb_value items = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@items")); + mrb_ary_push(mrb, items, obj); + queue_wake_one_waiter(mrb, q); + return self; +} + +/* + * __pop_try: try to pop one item. Returns: + * - the item if available + * - nil if closed and empty + * - raises Task::Error if non_block and empty + * - Task::Queue::WAIT_RETRY sentinel if the current task was put to WAITING + * + * Ruby-level pop loops on WAIT_RETRY. + */ +static mrb_value +queue_pop_try(mrb_state *mrb, mrb_value self) +{ + mrb_bool non_block = FALSE; + mrb_get_args(mrb, "|b", &non_block); + + mrb_task_queue *q = (mrb_task_queue*)mrb_data_get_ptr(mrb, self, &mrb_task_queue_type); + if (!q) mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid queue"); + + mrb_value items = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@items")); + + /* Item available - return it */ + if (RARRAY_LEN(items) > 0) { + return mrb_ary_shift(mrb, items); + } + + /* Closed and empty */ + if (q->closed) { + return mrb_nil_value(); + } + + /* Non-blocking and empty */ + if (non_block) { + mrb_raise(mrb, task_error_class_, "queue empty"); + } + + /* Blocking pop only works inside a task */ + if (mrb->c == mrb->root_c) { + mrb_raise(mrb, E_RUNTIME_ERROR, "blocking pop can only be called from within a task"); + } + + /* Blocking pop requires the scheduler to be running */ + task_check_scheduler_lock(mrb); + + /* Guard against yielding from inside a C function boundary */ + mrb_callinfo *ci; + for (ci = mrb->c->ci; ci >= mrb->c->cibase; ci--) { + if (ci->cci > 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "blocking pop cannot be called from within a C function boundary"); + } + } + + /* Move current task to WAITING */ + mrb_task *current = MRB2TASK(mrb); + mrb_task_disable_irq(); + mrb_task_q_delete(mrb, current); + current->status = MRB_TASK_STATUS_WAITING; + current->reason = MRB_TASK_REASON_QUEUE; + current->wait.queue = q; + mrb_task_q_insert(mrb, current); + mrb_task_enable_irq(); + switching_ = TRUE; + + /* Return sentinel; the Ruby pop loop will retry after wakeup */ + return wait_retry_; +} + +static mrb_value +queue_size(mrb_state *mrb, mrb_value self) +{ + mrb_value items = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@items")); + return mrb_int_value(mrb, RARRAY_LEN(items)); +} + +static mrb_value +queue_empty_p(mrb_state *mrb, mrb_value self) +{ + mrb_value items = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@items")); + return mrb_bool_value(RARRAY_LEN(items) == 0); +} + +static mrb_value +queue_clear(mrb_state *mrb, mrb_value self) +{ + mrb_value items = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@items")); + mrb_ary_clear(mrb, items); + return self; +} + +static mrb_value +queue_close(mrb_state *mrb, mrb_value self) +{ + mrb_task_queue *q = (mrb_task_queue*)mrb_data_get_ptr(mrb, self, &mrb_task_queue_type); + if (!q) mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid queue"); + if (!q->closed) { + q->closed = 1; + queue_wake_all_waiters(mrb, q); + } + return self; +} + +static mrb_value +queue_closed_p(mrb_state *mrb, mrb_value self) +{ + mrb_task_queue *q = (mrb_task_queue*)mrb_data_get_ptr(mrb, self, &mrb_task_queue_type); + if (!q) mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid queue"); + return mrb_bool_value(q->closed); +} + +static mrb_value +queue_num_waiting(mrb_state *mrb, mrb_value self) +{ + mrb_task_queue *q = (mrb_task_queue*)mrb_data_get_ptr(mrb, self, &mrb_task_queue_type); + if (!q) mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid queue"); + uint32_t count = 0; + mrb_task_disable_irq(); + mrb_task *curr = q_waiting_; + while (curr) { + if (curr->reason == MRB_TASK_REASON_QUEUE && curr->wait.queue == q) { + count++; + } + curr = curr->next; + } + mrb_task_enable_irq(); + return mrb_int_value(mrb, (mrb_int)count); +} + +void +mrb_init_task_queue(mrb_state *mrb, struct RClass *task_class) +{ + struct RClass *queue_class; + + queue_class = mrb_define_class_under_id(mrb, task_class, MRB_SYM(Queue), mrb->object_class); + MRB_SET_INSTANCE_TT(queue_class, MRB_TT_DATA); + + task_error_class_ = mrb_class_get_under_id(mrb, task_class, MRB_SYM(Error)); + + /* Allocate and store WAIT_RETRY sentinel (rooted by the class constant table) */ + wait_retry_ = mrb_obj_new(mrb, mrb->object_class, 0, NULL); + mrb_define_const_id(mrb, queue_class, MRB_SYM(WAIT_RETRY), wait_retry_); + + mrb_define_method_id(mrb, queue_class, MRB_SYM(initialize), queue_initialize, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, queue_class, MRB_SYM(__push), queue_push, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, queue_class, MRB_SYM(__pop_try), queue_pop_try, MRB_ARGS_OPT(1)); + mrb_define_method_id(mrb, queue_class, MRB_SYM(size), queue_size, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, queue_class, MRB_SYM(length), queue_size, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, queue_class, MRB_SYM_Q(empty), queue_empty_p, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, queue_class, MRB_SYM(clear), queue_clear, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, queue_class, MRB_SYM(close), queue_close, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, queue_class, MRB_SYM_Q(closed), queue_closed_p, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, queue_class, MRB_SYM(num_waiting), queue_num_waiting, MRB_ARGS_NONE()); +} diff --git a/mrbgems/mruby-task/test/queue.rb b/mrbgems/mruby-task/test/queue.rb new file mode 100644 index 0000000000..33c5f99e2f --- /dev/null +++ b/mrbgems/mruby-task/test/queue.rb @@ -0,0 +1,163 @@ +# Task::Queue tests + +assert("Task::Queue.new creates a queue") do + q = Task::Queue.new + assert_kind_of Task::Queue, q +end + +assert("Task::Queue push and non-blocking pop return item in FIFO order") do + q = Task::Queue.new + q.push(1) + q.push(2) + q.push(3) + assert_equal 1, q.pop(true) + assert_equal 2, q.pop(true) + assert_equal 3, q.pop(true) +end + +assert("Task::Queue << alias works") do + q = Task::Queue.new + q << :a + q << :b + assert_equal :a, q.pop(true) + assert_equal :b, q.pop(true) +end + +assert("Task::Queue enq/deq aliases work") do + q = Task::Queue.new + q.enq(10) + assert_equal 10, q.deq(true) +end + +assert("Task::Queue shift alias works") do + q = Task::Queue.new + q.push(:x) + assert_equal :x, q.shift(true) +end + +assert("Task::Queue size and length") do + q = Task::Queue.new + assert_equal 0, q.size + assert_equal 0, q.length + q.push(1) + assert_equal 1, q.size + q.push(2) + assert_equal 2, q.length + q.pop(true) + assert_equal 1, q.size +end + +assert("Task::Queue empty?") do + q = Task::Queue.new + assert_true q.empty? + q.push(1) + assert_false q.empty? + q.pop(true) + assert_true q.empty? +end + +assert("Task::Queue clear") do + q = Task::Queue.new + q.push(1) + q.push(2) + q.clear + assert_true q.empty? + assert_equal 0, q.size +end + +assert("Task::Queue pop(true) raises Task::Error when empty") do + q = Task::Queue.new + assert_raise(Task::Error) { q.pop(true) } +end + +assert("Task::Queue close and closed?") do + q = Task::Queue.new + assert_false q.closed? + q.close + assert_true q.closed? +end + +assert("Task::Queue push raises Task::Error after close") do + q = Task::Queue.new + q.close + assert_raise(Task::Error) { q.push(1) } +end + +assert("Task::Queue pop(true) returns nil when closed and empty") do + q = Task::Queue.new + q.close + assert_equal nil, q.pop(true) +end + +assert("Task::Queue pops remaining items after close, then nil") do + q = Task::Queue.new + q.push(1) + q.push(2) + q.close + assert_equal 1, q.pop(true) + assert_equal 2, q.pop(true) + assert_equal nil, q.pop(true) +end + +assert("Task::Queue double close is no-op") do + q = Task::Queue.new + q.close + assert_nothing_raised { q.close } + assert_true q.closed? +end + +assert("Task::Queue num_waiting is 0 with no blocked tasks") do + q = Task::Queue.new + assert_equal 0, q.num_waiting +end + +assert("Task::Queue blocking pop wakes on push") do + q = Task::Queue.new + results = [] + + Task.new { results << q.pop } + Task.new { q.push(99) } + Task.run + + assert_equal [99], results +end + +assert("Task::Queue multiple producers and consumers") do + q = Task::Queue.new + received = [] + + Task.new { q.push(1) } + Task.new { q.push(2) } + Task.new { q.push(3) } + Task.new { received << q.pop } + Task.new { received << q.pop } + Task.new { received << q.pop } + Task.run + + assert_equal [1, 2, 3], received.sort +end + +assert("Task::Queue blocking pop returns nil when queue is closed") do + q = Task::Queue.new + results = [] + + Task.new { results << q.pop } + Task.new { q.close } + Task.run + + assert_equal [nil], results +end + +assert("Task::Queue num_waiting reflects blocked task count") do + q = Task::Queue.new + counts = [] + + Task.new { q.pop } + Task.new do + counts << q.num_waiting # consumer should be waiting + q.push(:done) + end + Task.run + + assert_equal [1], counts +end diff --git a/mrbgems/mruby-task/test/task.rb b/mrbgems/mruby-task/test/task.rb index e93c525af2..5bbe844e30 100644 --- a/mrbgems/mruby-task/test/task.rb +++ b/mrbgems/mruby-task/test/task.rb @@ -81,6 +81,10 @@ assert("Task#suspend doesn't raise") do task = Task.new { } assert_nothing_raised { task.suspend } + # Clean up: a suspended task left in q_suspended_ keeps a later + # Task.run from terminating (the scheduler idles waiting on it + # instead of exiting). + task.terminate end assert("Task#resume doesn't raise") do @@ -183,3 +187,24 @@ # Block should not execute until scheduler runs assert_false executed end + +assert("Task.run inside Task.run is a noop") do + assert_nothing_raised do + Task.new { Task.run } + Task.run + end +end + +assert("Task#value returns exception object for unhandled task errors") do + child = nil + + Task.new do + child = Task.new { raise "boom" } + end + + Task.run + + result = child.value + assert_kind_of RuntimeError, result + assert_equal "boom", result.message +end diff --git a/mrbgems/mruby-task/tools/mruby_task_demo/mruby_task_demo.c b/mrbgems/mruby-task/tools/mruby_task_demo/mruby_task_demo.c new file mode 100644 index 0000000000..a8e7126394 --- /dev/null +++ b/mrbgems/mruby-task/tools/mruby_task_demo/mruby_task_demo.c @@ -0,0 +1,329 @@ +/* +** mruby-task-demo.c +** +** Three-thread test of the mruby-task GLib HAL. Each thread owns its +** own mrb_state and its own GMainContext (the HAL is thread-local and +** picks up the thread-default context at mrb_open time, then spawns +** its own ticker thread internally). +** +** T1 pure foreign-loop driver. Tasks are registered, then +** g_main_loop_run is what dispatches the scheduler via the +** HAL's vm_run_src. No Task.run anywhere. +** +** T2 mix. Phase 1 registers tasks and calls Task.run to drain +** them synchronously. Phase 2 registers more tasks and lets +** g_main_loop_run drive them. Exercises both drivers on the +** same mrb_state in sequence. +** +** T3 Task.run only. Registers tasks and calls Task.run. The demo +** thread never enters g_main_loop_run -- Task.run is the +** scheduler driver, and the HAL's idle hook iterates vm_ctx +** from inside Task.run so the ticker's cross-thread wakes +** still get dispatched. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static gint64 start_us; + +static void +log_line(const char *msg) +{ + gint64 ms = (g_get_monotonic_time() - start_us) / 1000; + printf("[t=%5" PRId64 " ms] %s\n", ms, msg); + fflush(stdout); +} + +static mrb_value +rb_log(mrb_state *mrb, mrb_value self) +{ + const char *msg; + (void)self; + mrb_get_args(mrb, "z", &msg); + log_line(msg); + return mrb_nil_value(); +} + +static void +run_ruby(mrb_state *mrb, const char *code) +{ + mrb_load_string(mrb, code); + if (mrb->exc) { + mrb_value exc = mrb_obj_value(mrb->exc); + mrb_value str = mrb_funcall(mrb, exc, "to_s", 0); + fprintf(stderr, "Ruby error: %s\n", RSTRING_PTR(str)); + fflush(stderr); + mrb->exc = NULL; + } +} + +static void +banner(const char *msg) +{ + printf("===== %s =====\n", msg); + fflush(stdout); +} + +/* + * T1 -- pure foreign-loop driver. Runs on the main thread (no + * separate GThread for T1; the main thread is the foreign loop). + * + * Pulse task with mixed sleep styles, three staggered sleepers, and + * spinner + stopper for timeslice preemption. g_main_loop_run is the + * only scheduler driver. + */ +static void +run_glib_only(void) +{ + GMainContext *ctx; + GMainLoop *loop; + GSource *timeout; + mrb_state *mrb; + + ctx = g_main_context_new(); + g_main_context_push_thread_default(ctx); + loop = g_main_loop_new(ctx, FALSE); + + mrb = mrb_open(); + mrb_define_method(mrb, mrb->object_class, "log", rb_log, MRB_ARGS_REQ(1)); + + banner("T1 (glib-only): pulse + 3 staggered sleepers + spinner/stopper"); + + run_ruby(mrb, + "$t1_done = false\n" + "Task.new(name: 'T1.pulse') {\n" + " log 'T1.pulse: usleep 8000 x5'\n" + " 5.times { usleep 8000; log 'T1.pulse: micro' }\n" + " log 'T1.pulse: sleep_ms 120 x2'\n" + " 2.times { sleep_ms 120; log 'T1.pulse: chunk' }\n" + " log 'T1.pulse: sleep 0.3'\n" + " sleep 0.3\n" + " log 'T1.pulse: long done'\n" + "}\n" + "[30, 60, 90].each do |ms|\n" + " Task.new(name: 'T1.s' + ms.to_s) {\n" + " log 'T1.sleeper' + ms.to_s + ': sleeping'\n" + " sleep_ms ms\n" + " log 'T1.sleeper' + ms.to_s + ': woke'\n" + " }\n" + "end\n" + "Task.new(name: 'T1.spinner', priority: 200) {\n" + " log 'T1.spinner: entering tight loop'\n" + " loops = 0\n" + " loop {\n" + " loops += 1\n" + " break if $t1_done\n" + " break if loops > 200_000_000\n" + " }\n" + " log 'T1.spinner: exit loops=' + loops.to_s + ' done=' + $t1_done.to_s\n" + "}\n" + "Task.new(name: 'T1.stopper', priority: 50) {\n" + " log 'T1.stopper: sleeping 100 ms'\n" + " sleep 0.1\n" + " log 'T1.stopper: setting $t1_done'\n" + " $t1_done = true\n" + "}\n" + ); + + timeout = g_timeout_source_new(700); + g_source_set_callback(timeout, (GSourceFunc)g_main_loop_quit, loop, NULL); + g_source_attach(timeout, ctx); + g_source_unref(timeout); + + log_line("T1: entering g_main_loop_run (700 ms cap)"); + g_main_loop_run(loop); + log_line("T1: g_main_loop_run returned"); + + mrb_close(mrb); + g_main_loop_unref(loop); + g_main_context_pop_thread_default(ctx); + g_main_context_unref(ctx); +} + +/* + * T2 -- mixed driver. + * + * Phase 1: register a small task set, call Task.run, which blocks + * until those tasks drain. Phase 2: register more tasks and let + * g_main_loop_run drive them. + */ +static gpointer +thread_glib_and_taskrun(gpointer data) +{ + GMainContext *ctx; + GMainLoop *loop; + GSource *timeout; + mrb_state *mrb; + (void)data; + + ctx = g_main_context_new(); + g_main_context_push_thread_default(ctx); + loop = g_main_loop_new(ctx, FALSE); + + mrb = mrb_open(); + mrb_define_method(mrb, mrb->object_class, "log", rb_log, MRB_ARGS_REQ(1)); + + banner("T2 (mix): phase 1 = yield + suspend/resume, drained by Task.run"); + + run_ruby(mrb, + "victim = Task.new(name: 'T2.victim') {\n" + " log 'T2.victim: sleeping 200 ms'\n" + " sleep 0.2\n" + " log 'T2.victim: woke'\n" + "}\n" + "Task.new(name: 'T2.controller', priority: 50) {\n" + " sleep 0.05\n" + " log 'T2.controller: suspending victim (was ' + victim.status.to_s + ')'\n" + " victim.suspend\n" + " log 'T2.controller: victim now ' + victim.status.to_s\n" + " sleep 0.1\n" + " log 'T2.controller: resuming victim (was ' + victim.status.to_s + ')'\n" + " victim.resume\n" + " log 'T2.controller: victim now ' + victim.status.to_s\n" + "}\n" + "Task.new(name: 'T2.yieldA', priority: 100) {\n" + " 3.times { |i| log 'T2.yieldA: iter ' + i.to_s; Task.pass }\n" + "}\n" + "Task.new(name: 'T2.yieldB', priority: 100) {\n" + " 3.times { |i| log 'T2.yieldB: iter ' + i.to_s; Task.pass }\n" + "}\n" + "log 'T2: calling Task.run (drains phase 1)'\n" + "Task.run\n" + "log 'T2: Task.run returned'\n" + ); + + banner("T2 (mix): phase 2 = 3 staggered sleepers, driven by g_main_loop_run"); + + run_ruby(mrb, + "[40, 80, 120].each do |ms|\n" + " Task.new(name: 'T2.s' + ms.to_s) {\n" + " log 'T2.sleeper' + ms.to_s + ': sleeping'\n" + " sleep_ms ms\n" + " log 'T2.sleeper' + ms.to_s + ': woke'\n" + " }\n" + "end\n" + "log 'T2: phase 2 registered'\n" + ); + + timeout = g_timeout_source_new(400); + g_source_set_callback(timeout, (GSourceFunc)g_main_loop_quit, loop, NULL); + g_source_attach(timeout, ctx); + g_source_unref(timeout); + + log_line("T2: entering g_main_loop_run (400 ms cap)"); + g_main_loop_run(loop); + log_line("T2: g_main_loop_run returned"); + + mrb_close(mrb); + g_main_loop_unref(loop); + g_main_context_pop_thread_default(ctx); + g_main_context_unref(ctx); + return NULL; +} + +/* + * T3 -- Task.run only. + * + * No g_main_loop_run on the demo thread. Task.run drives the + * scheduler; mrb_hal_task_idle_cpu iterates vm_ctx from inside + * Task.run's idle loop so the ticker's cross-thread set_ready_time + * still wakes the demo thread when sleepers come due. Returns when + * all queues drain. + */ +static gpointer +thread_taskrun_only(gpointer data) +{ + GMainContext *ctx; + mrb_state *mrb; + (void)data; + + ctx = g_main_context_new(); + g_main_context_push_thread_default(ctx); + + mrb = mrb_open(); + mrb_define_method(mrb, mrb->object_class, "log", rb_log, MRB_ARGS_REQ(1)); + + banner("T3 (Task.run only): zombie/executioner + spinner/stopper + sleepers"); + + run_ruby(mrb, + "$t3_done = false\n" + "$t3_zombie_ticks = 0\n" + "zombie = Task.new(name: 'T3.zombie') {\n" + " log 'T3.zombie: alive (will tick every 50 ms forever)'\n" + " loop {\n" + " sleep_ms 50\n" + " $t3_zombie_ticks += 1\n" + " log 'T3.zombie: tick ' + $t3_zombie_ticks.to_s\n" + " }\n" + " log 'T3.zombie: NEVER REACHED'\n" + "}\n" + "Task.new(name: 'T3.executioner', priority: 50) {\n" + " sleep_ms 175\n" + " log 'T3.executioner: terminating zombie (was ' + zombie.status.to_s + ')'\n" + " zombie.terminate\n" + " log 'T3.executioner: zombie is now ' + zombie.status.to_s\n" + "}\n" + "Task.new(name: 'T3.spinner', priority: 200) {\n" + " log 'T3.spinner: entering tight loop'\n" + " loops = 0\n" + " loop {\n" + " loops += 1\n" + " break if $t3_done\n" + " break if loops > 200_000_000\n" + " }\n" + " log 'T3.spinner: exit loops=' + loops.to_s + ' done=' + $t3_done.to_s\n" + "}\n" + "Task.new(name: 'T3.stopper', priority: 50) {\n" + " log 'T3.stopper: sleeping 100 ms'\n" + " sleep 0.1\n" + " log 'T3.stopper: setting $t3_done'\n" + " $t3_done = true\n" + "}\n" + "[20, 40, 60].each do |ms|\n" + " Task.new(name: 'T3.s' + ms.to_s) {\n" + " log 'T3.sleeper' + ms.to_s + ': sleeping'\n" + " sleep_ms ms\n" + " log 'T3.sleeper' + ms.to_s + ': woke'\n" + " }\n" + "end\n" + "log 'T3: calling Task.run'\n" + "Task.run\n" + "log 'T3: Task.run returned (all queues empty)'\n" + ); + + mrb_close(mrb); + g_main_context_pop_thread_default(ctx); + g_main_context_unref(ctx); + return NULL; +} + +int main(int argc, char **argv) +{ + GThread *t2, *t3; + (void)argc; + (void)argv; + + start_us = g_get_monotonic_time(); + + log_line("main: spawning T2 + T3; running T1 (glib-only) on main thread"); + + t2 = g_thread_new("T2.mix", thread_glib_and_taskrun, NULL); + t3 = g_thread_new("T3.taskrun", thread_taskrun_only, NULL); + + run_glib_only(); + + g_thread_join(t2); + g_thread_join(t3); + + log_line("main: T2 and T3 joined"); + printf("All scenarios completed.\n"); + return 0; +} diff --git a/mrbgems/stdlib-io.gembox b/mrbgems/stdlib-io.gembox index 5993d5d480..3e13d183d4 100644 --- a/mrbgems/stdlib-io.gembox +++ b/mrbgems/stdlib-io.gembox @@ -12,4 +12,7 @@ MRuby::GemBox.new do |conf| # Use Dir class conf.gem :core => "mruby-dir" + + # Use ENV object + conf.gem :core => "mruby-env" end diff --git a/mrbgems/stdlib.gembox b/mrbgems/stdlib.gembox index 238d06c7e6..fe168fe244 100644 --- a/mrbgems/stdlib.gembox +++ b/mrbgems/stdlib.gembox @@ -31,6 +31,9 @@ MRuby::GemBox.new do |conf| # Use Object class extension conf.gem :core => "mruby-object-ext" + # Use Regexp class + conf.gem :core => "mruby-regexp" + # Use ObjectSpace class conf.gem :core => "mruby-objectspace" diff --git a/oss-fuzz/config/mruby.dict b/oss-fuzz/config/mruby.dict index a332d3505c..7780247b36 100644 --- a/oss-fuzz/config/mruby.dict +++ b/oss-fuzz/config/mruby.dict @@ -103,3 +103,26 @@ snippet_multi=" 1*1" string_single_q=" 'a'" string_dbl_q=" \"a\"" +regex_any="." +regex_start="^" +regex_end="$" +regex_star="*" +regex_plus="+" +regex_maybe="?" +regex_alt="|" +regex_group_start="(" +regex_group_end=")" +regex_class_start="[" +regex_class_end="]" +regex_digit="\\d" +regex_word="\\w" +regex_space="\\s" +regex_non_digit="\\D" +regex_non_word="\\W" +regex_non_space="\\S" +regex_named="(?)" +regex_atomic="(?>)" +regex_lookahead="(?=)" +regex_neg_lookahead="(?!)" +regex_lookbehind="(?<=)" +regex_neg_lookbehind="(? +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 1) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + /* mrb_load_irep_buf returns the result of the last expression */ + mrb_load_irep_buf(mrb, Data, size); + + mrb_close(mrb); + return 0; +} diff --git a/oss-fuzz/mruby_misc_fuzzer.c b/oss-fuzz/mruby_misc_fuzzer.c new file mode 100644 index 0000000000..7a22e57316 --- /dev/null +++ b/oss-fuzz/mruby_misc_fuzzer.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 2) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + uint8_t selector = Data[0] % 4; + const uint8_t *D = Data + 1; + size_t S = size - 1; + + if (selector == 0) { + /* Target Set */ + struct RClass *set_class_ptr = mrb_class_get(mrb, "Set"); + if (set_class_ptr) { + mrb_value set_class = mrb_obj_value(set_class_ptr); + mrb_value ary = mrb_ary_new(mrb); + for (size_t i = 0; i < S / 4 && i < 10; i++) { + mrb_ary_push(mrb, ary, mrb_str_new(mrb, (const char *)(D + i*4), 4)); + } + mrb_funcall(mrb, set_class, "new", 1, ary); + } + } else if (selector == 1) { + /* Target Time */ + struct RClass *time_class_ptr = mrb_class_get(mrb, "Time"); + if (time_class_ptr) { + mrb_value time_class = mrb_obj_value(time_class_ptr); + if (S >= 8) { + mrb_int sec = (mrb_int)D[0] | ((mrb_int)D[1] << 8) | ((mrb_int)D[2] << 16) | ((mrb_int)D[3] << 24); + mrb_funcall(mrb, time_class, "at", 1, mrb_fixnum_value(sec)); + } + mrb_funcall(mrb, time_class, "now", 0); + } + } else if (selector == 2) { + /* Target Random */ + struct RClass *random_class_ptr = mrb_class_get(mrb, "Random"); + if (random_class_ptr) { + mrb_value random_class = mrb_obj_value(random_class_ptr); + if (S >= 4) { + mrb_int seed = (mrb_int)D[0] | ((mrb_int)D[1] << 8); + mrb_value rnd = mrb_funcall(mrb, random_class, "new", 1, mrb_fixnum_value(seed)); + mrb_funcall(mrb, rnd, "rand", 1, mrb_fixnum_value(100)); + } + } + } else if (selector == 3) { + /* Target Range and Array-ext */ + if (S >= 4) { + mrb_int start = (mrb_int)D[0]; + mrb_int end = (mrb_int)D[1]; + mrb_value range = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Range", 2, mrb_fixnum_value(start), mrb_fixnum_value(end)); + mrb_funcall(mrb, range, "to_a", 0); + } + } + + mrb_close(mrb); + return 0; +} diff --git a/oss-fuzz/mruby_numeric_fuzzer.c b/oss-fuzz/mruby_numeric_fuzzer.c new file mode 100644 index 0000000000..15ad4a9c19 --- /dev/null +++ b/oss-fuzz/mruby_numeric_fuzzer.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 2) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + uint8_t selector = Data[0] % 4; + const uint8_t *D = Data + 1; + size_t S = size - 1; + + if (selector == 0) { + /* Integer / BigInt */ + if (S < 3) goto done; + uint8_t base = D[0] % 37; + if (base < 2 && base != 0) base = 10; + uint8_t op = D[1] % 5; + uint16_t split = D[2]; // Simplified split + if (S > 3) split = split % (S - 3); + else split = 0; + + mrb_value s1 = mrb_str_new(mrb, (const char *)(D + 3), split); + mrb_value s2 = mrb_str_new(mrb, (const char *)(D + 3 + split), S - 3 - split); + + mrb_value b1 = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Integer", 2, s1, mrb_fixnum_value(base)); + mrb_value b2 = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Integer", 2, s2, mrb_fixnum_value(base)); + + if (!mrb_nil_p(b1) && !mrb_nil_p(b2)) { + switch (op) { + case 0: mrb_funcall(mrb, b1, "+", 1, b2); break; + case 1: mrb_funcall(mrb, b1, "-", 1, b2); break; + case 2: mrb_funcall(mrb, b1, "*", 1, b2); break; + case 3: + if (mrb_test(mrb_funcall(mrb, b2, "!=", 1, mrb_fixnum_value(0)))) { + mrb_funcall(mrb, b1, "/", 1, b2); + } + break; + case 4: + if (mrb_test(mrb_funcall(mrb, b2, "<", 1, mrb_fixnum_value(1000)))) { + mrb_funcall(mrb, b1, "**", 1, b2); + } + break; + } + } + } else if (selector == 1) { + /* Rational */ + if (S < 4) goto done; + mrb_int n = (mrb_int)D[0] | ((mrb_int)D[1] << 8); + mrb_int d = (mrb_int)D[2] | ((mrb_int)D[3] << 8); + mrb_value r = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Rational", 2, mrb_fixnum_value(n), mrb_fixnum_value(d)); + if (S >= 8) { + mrb_int n2 = (mrb_int)D[4] | ((mrb_int)D[5] << 8); + mrb_int d2 = (mrb_int)D[6] | ((mrb_int)D[7] << 8); + mrb_value r2 = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Rational", 2, mrb_fixnum_value(n2), mrb_fixnum_value(d2)); + mrb_funcall(mrb, r, "+", 1, r2); + } + } else if (selector == 2) { + /* Complex */ + if (S < 4) goto done; + mrb_float re = (mrb_float)D[0]; + mrb_float im = (mrb_float)D[1]; + struct RClass *complex_class = mrb_class_get(mrb, "Complex"); + if (complex_class) { + mrb_value c = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Complex", 2, mrb_float_value(mrb, re), mrb_float_value(mrb, im)); + mrb_funcall(mrb, c, "abs", 0); + } + } else if (selector == 3) { + /* Math functions */ + if (S < 8) goto done; + double d1; + memcpy(&d1, D, sizeof(double)); + struct RClass *math_module = mrb_module_get(mrb, "Math"); + if (math_module) { + mrb_value m = mrb_obj_value(math_module); + mrb_funcall(mrb, m, "sin", 1, mrb_float_value(mrb, d1)); + mrb_funcall(mrb, m, "log", 1, mrb_float_value(mrb, d1)); + mrb_funcall(mrb, m, "sqrt", 1, mrb_float_value(mrb, d1)); + } + } + +done: + mrb_close(mrb); + return 0; +} diff --git a/oss-fuzz/mruby_pack_fuzzer.c b/oss-fuzz/mruby_pack_fuzzer.c new file mode 100644 index 0000000000..c2f578ab71 --- /dev/null +++ b/oss-fuzz/mruby_pack_fuzzer.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 2) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + uint8_t fmt_len = Data[0]; + if (fmt_len > size - 1) { + fmt_len = size - 1; + } + + mrb_value fmt = mrb_str_new(mrb, (const char *)(Data + 1), fmt_len); + mrb_value str = mrb_str_new(mrb, (const char *)(Data + 1 + fmt_len), size - 1 - fmt_len); + + /* Target String#unpack */ + mrb_funcall(mrb, str, "unpack", 1, fmt); + + /* Target Array#pack (using the result of unpack if it's an array) */ + /* Or just pack the original string as an array of bytes */ + mrb_value ary = mrb_ary_new_capa(mrb, size); + for (size_t i = 0; i < size; i++) { + mrb_ary_push(mrb, ary, mrb_fixnum_value(Data[i])); + } + mrb_funcall(mrb, ary, "pack", 1, fmt); + + mrb_close(mrb); + return 0; +} diff --git a/oss-fuzz/mruby_regexp_fuzzer.c b/oss-fuzz/mruby_regexp_fuzzer.c new file mode 100644 index 0000000000..c7faf8f076 --- /dev/null +++ b/oss-fuzz/mruby_regexp_fuzzer.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 3) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + /* Use first byte for flags */ + uint8_t flags_byte = Data[0]; + mrb_value flags = mrb_fixnum_value(flags_byte & 0x07); // i, m, x flags + + /* Split remaining data into pattern and string */ + uint8_t pattern_len = Data[1]; + if (pattern_len > size - 2) { + pattern_len = size - 2; + } + + mrb_value pattern = mrb_str_new(mrb, (const char *)(Data + 2), pattern_len); + mrb_value text = mrb_str_new(mrb, (const char *)(Data + 2 + pattern_len), size - 2 - pattern_len); + + /* Target Regexp.new(pattern, flags) */ + struct RClass *regexp_class_ptr = mrb_class_get(mrb, "Regexp"); + if (regexp_class_ptr) { + mrb_value regexp_class = mrb_obj_value(regexp_class_ptr); + mrb_value regexp = mrb_funcall(mrb, regexp_class, "new", 2, pattern, flags); + + /* Target Regexp#match(text) */ + if (!mrb_nil_p(regexp)) { + mrb_funcall(mrb, regexp, "match", 1, text); + } + } + + mrb_close(mrb); + return 0; +} diff --git a/oss-fuzz/mruby_sprintf_fuzzer.c b/oss-fuzz/mruby_sprintf_fuzzer.c new file mode 100644 index 0000000000..53df2c1a38 --- /dev/null +++ b/oss-fuzz/mruby_sprintf_fuzzer.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 1) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + /* Use the first byte as a format string length */ + uint8_t fmt_len = Data[0]; + if (fmt_len > size - 1) { + fmt_len = size - 1; + } + + mrb_value fmt = mrb_str_new(mrb, (const char *)(Data + 1), fmt_len); + + /* provide some arguments of different types to satisfy various format specifiers */ + mrb_value args[5]; + args[0] = mrb_fixnum_value(123); + args[1] = mrb_float_value(mrb, 3.14); + args[2] = mrb_str_new_cstr(mrb, "fuzz"); + args[3] = mrb_symbol_value(mrb_intern_cstr(mrb, "symbol")); + args[4] = mrb_ary_new(mrb); + + /* Call sprintf via mrb_funcall */ + /* We don't use all args every time, but it doesn't hurt to pass them */ + mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "sprintf", 6, fmt, args[0], args[1], args[2], args[3], args[4]); + + mrb_close(mrb); + return 0; +} diff --git a/scripts/check_makefiles_for_tabs.sh b/scripts/check_makefiles_for_tabs.sh new file mode 100755 index 0000000000..5d93e0428e --- /dev/null +++ b/scripts/check_makefiles_for_tabs.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Iterate over all files passed as arguments by pre-commit +for makefile in "$@"; do + # Check if the file exists and is a regular file + if [[ -f "$makefile" ]]; then + if grep -P '^\s' "$makefile" | grep -vP '^\t' > /dev/null; then + echo "Error: File '$makefile' contains spaces at the beginning of lines instead of tabs." + exit 1 + fi + fi +done +exit 0 diff --git a/src/array.c b/src/array.c index cf28c35d0a..166960f2f3 100644 --- a/src/array.c +++ b/src/array.c @@ -616,7 +616,7 @@ ary_replace(mrb_state *mrb, struct RArray *a, struct RArray *b) mrb_write_barrier(mrb, (struct RBasic*)a); return; } - if (!mrb_frozen_p(b) && len > ARY_REPLACE_SHARED_MIN) { + if (len > ARY_REPLACE_SHARED_MIN) { ary_make_shared(mrb, b); goto shared_b; } @@ -1127,7 +1127,9 @@ mrb_ary_set(mrb_state *mrb, mrb_value ary, mrb_int n, mrb_value val) static struct RArray* ary_dup(mrb_state *mrb, struct RArray *a) { - return ary_new_from_values(mrb, ARY_LEN(a), ARY_PTR(a)); + struct RArray *dup = ary_new_capa(mrb, 0); + ary_replace(mrb, dup, a); + return dup; } MRB_API mrb_value @@ -1195,6 +1197,14 @@ mrb_ary_splice(mrb_state *mrb, mrb_value ary, mrb_int head, mrb_int len, mrb_val } r = ary_dup(mrb, a); argv = ARY_PTR(r); + /* ary_dup -> ary_replace converts `a` to shared as a + copy-on-write optimization when len > ARY_REPLACE_SHARED_MIN. + Subsequent ARY_CAPA(a) reads would land on aux.shared's + pointer bits instead of the actual capacity, so the + expand-capa check below silently mis-sizes and value_move + walks past the buffer. Re-modify here to unshare before + mutating `a` in place. */ + ary_modify(mrb, a); } } else if (mrb_undef_p(rpl)) { @@ -1576,7 +1586,7 @@ mrb_ary_index_m(mrb_state *mrb, mrb_value self) mrb_value obj, blk; if (mrb_get_args(mrb, "|o&", &obj, &blk) == 0 && mrb_nil_p(blk)) { - return mrb_funcall_id(mrb, self, MRB_SYM(to_enum), 1, mrb_symbol_value(MRB_SYM(index))); + return mrb_funcall_argv1(mrb, self, MRB_SYM(to_enum), mrb_symbol_value(MRB_SYM(index))); } if (mrb_nil_p(blk)) { @@ -1618,7 +1628,7 @@ mrb_ary_rindex_m(mrb_state *mrb, mrb_value self) mrb_value obj, blk; if (mrb_get_args(mrb, "|o&", &obj, &blk) == 0 && mrb_nil_p(blk)) { - return mrb_funcall_id(mrb, self, MRB_SYM(to_enum), 1, mrb_symbol_value(MRB_SYM(rindex))); + return mrb_funcall_argv1(mrb, self, MRB_SYM(to_enum), mrb_symbol_value(MRB_SYM(rindex))); } for (mrb_int i = RARRAY_LEN(self) - 1; i >= 0; i--) { @@ -1933,7 +1943,7 @@ mrb_ary_eq(mrb_state *mrb, mrb_value ary1) int ai = mrb_gc_arena_save(mrb); for (mrb_int i=0; i= size) break; + if (child + 1 < size && mrb_integer(a[child + 1]) > mrb_integer(a[child])) { + child++; + } + if (mrb_integer(a[child]) <= val) break; + a[index] = a[child]; + index = child; + } + SET_FIXNUM_VALUE(a[index], val); +} + +/* Integer-specialized Floyd's bottom-up heap deletion */ +static void +heap_delete_root_fixnum(mrb_value *a, mrb_int size) +{ + mrb_int last = mrb_integer(a[0]); + + mrb_int hole = 0; + mrb_int child = 1; + while (child + 1 < size) { + if (mrb_integer(a[child + 1]) > mrb_integer(a[child])) { + child++; + } + a[hole] = a[child]; + hole = child; + child = 2 * hole + 1; + } + if (child < size) { + a[hole] = a[child]; + hole = child; + } + + while (hole > 0) { + mrb_int parent = (hole - 1) / 2; + if (mrb_integer(a[parent]) >= last) break; + a[hole] = a[parent]; + hole = parent; + } + SET_FIXNUM_VALUE(a[hole], last); +} + +/* Integer-specialized insertion sort */ +static void +insertion_sort_fixnum(mrb_value *a, mrb_int size) +{ + for (mrb_int i = 1; i < size; i++) { + mrb_int key = mrb_integer(a[i]); + mrb_int j = i - 1; + while (j >= 0 && mrb_integer(a[j]) > key) { + a[j + 1] = a[j]; + j--; + } + SET_FIXNUM_VALUE(a[j + 1], key); + } +} + +/* Check if all elements are plain String (not subclass) */ +static mrb_bool +ary_all_string_p(mrb_state *mrb, const mrb_value *a, mrb_int n) +{ + for (mrb_int i = 0; i < n; i++) { + if (!mrb_string_p(a[i])) return FALSE; + if (mrb_obj_ptr(a[i])->c != mrb->string_class) return FALSE; + } + return TRUE; +} + +/* String-specialized heapify using mrb_str_cmp directly */ +static void +heapify_str(mrb_state *mrb, mrb_value *a, mrb_int index, mrb_int size) +{ + mrb_value val = a[index]; + + while (1) { + mrb_int child = 2 * index + 1; + if (child >= size) break; + if (child + 1 < size && mrb_str_cmp(mrb, a[child + 1], a[child]) > 0) { + child++; + } + if (mrb_str_cmp(mrb, a[child], val) <= 0) break; + a[index] = a[child]; + index = child; + } + a[index] = val; +} + +/* String-specialized Floyd's bottom-up heap deletion */ +static void +heap_delete_root_str(mrb_state *mrb, mrb_value *a, mrb_int size) +{ + mrb_value last = a[0]; + + mrb_int hole = 0; + mrb_int child = 1; + while (child + 1 < size) { + if (mrb_str_cmp(mrb, a[child + 1], a[child]) > 0) { + child++; + } + a[hole] = a[child]; + hole = child; + child = 2 * hole + 1; + } + if (child < size) { + a[hole] = a[child]; + hole = child; + } + + while (hole > 0) { + mrb_int parent = (hole - 1) / 2; + if (mrb_str_cmp(mrb, a[parent], last) >= 0) break; + a[hole] = a[parent]; + hole = parent; + } + a[hole] = last; +} + +/* String-specialized insertion sort */ +static void +insertion_sort_str(mrb_state *mrb, mrb_value *a, mrb_int size) +{ + for (mrb_int i = 1; i < size; i++) { + mrb_value key = a[i]; + mrb_int j = i - 1; + while (j >= 0 && mrb_str_cmp(mrb, a[j], key) > 0) { + a[j + 1] = a[j]; + j--; + } + a[j + 1] = key; + } +} static mrb_bool sort_cmp(mrb_state *mrb, mrb_value ary, mrb_value a_val, mrb_value b_val, mrb_value blk) @@ -2137,35 +2295,72 @@ sort_cmp(mrb_state *mrb, mrb_value ary, mrb_value a_val, mrb_value b_val, mrb_va return cmp > 0; } +/* Hole-style sift-down: save root, move larger children up, write once at end. + Reduces assignments from 3 per level (swap) to 1 per level (move). */ static void heapify(mrb_state *mrb, mrb_value ary, mrb_value *a, mrb_int index, mrb_int size, mrb_value blk) { - /* Iterative heapify to avoid stack overflow on memory-constrained devices */ + int ai = mrb_gc_arena_save(mrb); + mrb_value val = a[index]; /* save root to hole */ + mrb_gc_protect(mrb, val); + while (1) { - mrb_int max = index; - mrb_int left_index = 2 * index + 1; - mrb_int right_index = left_index + 1; + mrb_int child = 2 * index + 1; + if (child >= size) break; - if (left_index < size && sort_cmp(mrb, ary, a[left_index], a[max], blk)) { - max = left_index; - } - if (right_index < size && sort_cmp(mrb, ary, a[right_index], a[max], blk)) { - max = right_index; + /* pick the larger child */ + if (child + 1 < size && sort_cmp(mrb, ary, a[child + 1], a[child], blk)) { + child++; } + /* if hole value >= larger child, done */ + if (!sort_cmp(mrb, ary, a[child], val, blk)) break; - if (max == index) { - /* Heap property satisfied, no more swaps needed */ - break; - } + a[index] = a[child]; /* move child up */ + index = child; + } + a[index] = val; /* place saved value */ + mrb_gc_arena_restore(mrb, ai); +} - /* Swap elements and continue heapifying down the affected subtree */ - mrb_value tmp = a[max]; - a[max] = a[index]; - a[index] = tmp; +/* Floyd's bottom-up heap deletion: sift the hole down to a leaf without + comparing against the removed root, then sift up from the leaf position. + This reduces comparisons from ~2 log n to ~log n per extraction, + because most elements end up near the bottom of the heap anyway. */ +static void +heap_delete_root(mrb_state *mrb, mrb_value ary, mrb_value *a, mrb_int size, mrb_value blk) +{ + int ai = mrb_gc_arena_save(mrb); + /* a[0] already holds the value to be re-inserted (set by caller) */ + mrb_value last = a[0]; + mrb_gc_protect(mrb, last); + + /* Phase 1: sift the hole down to a leaf (only child-child comparisons) */ + mrb_int hole = 0; + mrb_int child = 1; + while (child + 1 < size) { + /* pick the larger child - 1 comparison per level */ + if (sort_cmp(mrb, ary, a[child + 1], a[child], blk)) { + child++; + } + a[hole] = a[child]; + hole = child; + child = 2 * hole + 1; + } + /* handle single child at bottom */ + if (child < size) { + a[hole] = a[child]; + hole = child; + } - /* Continue with the affected child subtree */ - index = max; + /* Phase 2: sift up from hole to find correct position for last */ + while (hole > 0) { + mrb_int parent = (hole - 1) / 2; + if (!sort_cmp(mrb, ary, last, a[parent], blk)) break; + a[hole] = a[parent]; + hole = parent; } + a[hole] = last; + mrb_gc_arena_restore(mrb, ai); } static void @@ -2210,21 +2405,61 @@ mrb_ary_sort_bang(mrb_state *mrb, mrb_value ary) mrb_value *a = RARRAY_PTR(ary); - /* Algorithm selection based on array size */ + /* Integer fast path: no block and all elements are integers */ + if (mrb_nil_p(blk) && ary_all_fixnum_p(a, n)) { + if (n <= SMALL_ARRAY_SORT_THRESHOLD) { + insertion_sort_fixnum(a, n); + } + else { + for (mrb_int i = n / 2 - 1; i >= 0; i--) { + heapify_fixnum(a, i, n); + } + for (mrb_int i = n - 1; i > 0; i--) { + mrb_value tmp = a[0]; + a[0] = a[i]; + a[i] = tmp; + heap_delete_root_fixnum(a, i); + } + } + return ary; + } + + /* String fast path: no block and all elements are plain String */ + if (mrb_nil_p(blk) && ary_all_string_p(mrb, a, n)) { + if (n <= SMALL_ARRAY_SORT_THRESHOLD) { + insertion_sort_str(mrb, a, n); + } + else { + for (mrb_int i = n / 2 - 1; i >= 0; i--) { + heapify_str(mrb, a, i, n); + } + for (mrb_int i = n - 1; i > 0; i--) { + mrb_value tmp = a[0]; + a[0] = a[i]; + a[i] = tmp; + heap_delete_root_str(mrb, a, i); + } + } + return ary; + } + + /* General path */ if (n <= SMALL_ARRAY_SORT_THRESHOLD) { /* Use insertion sort for small arrays */ insertion_sort(mrb, ary, a, n, blk); } else { - /* Use heap sort for larger arrays */ + /* Heap sort with Floyd's bottom-up deletion */ + /* Phase 1: build max-heap (standard sift-down, hole style) */ for (mrb_int i = n / 2 - 1; i >= 0; i--) { heapify(mrb, ary, a, i, n, blk); } + /* Phase 2: extract max elements using Floyd's method */ for (mrb_int i = n - 1; i > 0; i--) { - mrb_value tmp = a[0]; - a[0] = a[i]; - a[i] = tmp; - heapify(mrb, ary, a, 0, i, blk); + mrb_value max = a[0]; + a[0] = a[i]; /* temporary for GC safety */ + a[i] = max; /* max goes to final position */ + heap_delete_root(mrb, ary, a, i, blk); } } return ary; diff --git a/src/cdump.c b/src/cdump.c index c453dfb97d..35809a6807 100644 --- a/src/cdump.c +++ b/src/cdump.c @@ -84,21 +84,9 @@ sym_name_word_p(const char *name, mrb_int len) } static mrb_bool -sym_name_with_equal_p(const char *name, mrb_int len) +sym_name_with_suffix_p(const char *name, mrb_int len, char suffix) { - return len >= 2 && name[len-1] == '=' && sym_name_word_p(name, len-1); -} - -static mrb_bool -sym_name_with_question_mark_p(const char *name, mrb_int len) -{ - return len >= 2 && name[len-1] == '?' && sym_name_word_p(name, len-1); -} - -static mrb_bool -sym_name_with_bang_p(const char *name, mrb_int len) -{ - return len >= 2 && name[len-1] == '!' && sym_name_word_p(name, len-1); + return len >= 2 && name[len-1] == suffix && sym_name_word_p(name, len-1); } static mrb_bool @@ -202,13 +190,13 @@ cdump_sym(mrb_state *mrb, mrb_sym sym, const char *var_name, int idx, mrb_value if (sym_name_word_p(name, len)) { fprintf(fp, "MRB_SYM(%s)", name); } - else if (sym_name_with_equal_p(name, len)) { + else if (sym_name_with_suffix_p(name, len, '=')) { fprintf(fp, "MRB_SYM_E(%.*s)", (int)(len-1), name); } - else if (sym_name_with_question_mark_p(name, len)) { + else if (sym_name_with_suffix_p(name, len, '?')) { fprintf(fp, "MRB_SYM_Q(%.*s)", (int)(len-1), name); } - else if (sym_name_with_bang_p(name, len)) { + else if (sym_name_with_suffix_p(name, len, '!')) { fprintf(fp, "MRB_SYM_B(%.*s)", (int)(len-1), name); } else if (sym_name_ivar_p(name, len)) { diff --git a/src/class.c b/src/class.c index c5e9428b6e..74d021ce74 100644 --- a/src/class.c +++ b/src/class.c @@ -490,7 +490,7 @@ struct RClass* mrb_vm_define_module(mrb_state *mrb, mrb_value outer, mrb_sym id) { check_if_class_or_module(mrb, outer); - if (mrb_const_defined_at(mrb, outer, id)) { + if (mrb_obj_iv_defined(mrb, mrb_obj_ptr(outer), id)) { mrb_value old = mrb_const_get(mrb, outer, id); if (!mrb_module_p(old)) { @@ -498,7 +498,9 @@ mrb_vm_define_module(mrb_state *mrb, mrb_value outer, mrb_sym id) } return mrb_class_ptr(old); } - return define_module(mrb, id, mrb_class_ptr(outer)); + struct RClass *m = mrb_module_new(mrb); + setup_class(mrb, mrb_class_ptr(outer), m, id); + return m; } /* @@ -647,7 +649,7 @@ mrb_vm_define_class(mrb_state *mrb, mrb_value outer, mrb_value super, mrb_sym id s = NULL; } check_if_class_or_module(mrb, outer); - if (mrb_const_defined_at(mrb, outer, id)) { + if (mrb_obj_iv_defined(mrb, mrb_obj_ptr(outer), id)) { mrb_value old = mrb_const_get(mrb, outer, id); if (!mrb_class_p(old)) { @@ -662,7 +664,8 @@ mrb_vm_define_class(mrb_state *mrb, mrb_value outer, mrb_value super, mrb_sym id } return c; } - c = define_class(mrb, id, s, mrb_class_ptr(outer)); + c = mrb_class_new(mrb, s); + setup_class(mrb, mrb_class_ptr(outer), c, id); mrb_class_inherited(mrb, mrb_class_real(c->super), c); return c; @@ -1302,9 +1305,169 @@ mrb_block_given_p(mrb_state *mrb) #define GET_ARG(_type) (ptr ? ((_type)(*ptr++)) : va_arg((*ap), _type)) +/* + * Per-character validation for the mrb_get_args fast path. + * Written as a switch rather than an array-designator lookup table so + * that the file can be compiled as C++ (array-index designators are a + * C99-only feature). Modern compilers typically lower this to a jump + * table, giving the same effective O(1) behavior as the original table. + * Returns 1 for a valid arg specifier, 2 for the separator, 0 otherwise. + */ +static inline uint8_t +fast_fmt_ok(char c) +{ + switch (c) { + case 'o': case 'S': case 'A': case 'H': case 'i': case 'b': + case 'f': case 'n': case 'z': case 'c': case 's': case 'a': + return 1; + case '|': + return 2; + default: + return 0; + } +} + +/* + * Fast path for simple format strings (no *, :, !, +, &, ?). + * Handles the most common patterns directly in one pass, + * skipping the two-pass format scanning of the general path. + * + * Returns -1 if the format is not eligible for fast path. + */ +static mrb_int +get_args_fast(mrb_state *mrb, const char *format, void** ptr, va_list *ap) +{ + mrb_callinfo *ci = mrb->c->ci; + mrb_int argc = ci->n; + const mrb_value *argv; + mrb_int i = 0; + + /* fast path only for non-packed, non-keyword args */ + if (argc >= 15 || ci->nk > 0) return -1; + argv = ci->stack + 1; + + /* validate format and count args in one scan (table lookup, no switch) */ + const char *p = format; + int req = 0, opt = 0; + mrb_bool in_opt = FALSE; + while (*p) { + uint8_t v = fast_fmt_ok(*p); + if (v == 0) return -1; /* unsupported specifier */ + if (v == 2) { in_opt = TRUE; p++; continue; } + if (in_opt) opt++; else req++; + p++; + } + if (argc < req || argc > req + opt) { + mrb_argnum_error(mrb, argc, req, req + opt); + } + + /* all specifiers validated — safe to consume GET_ARG now */ + p = format; + while (*p) { + char c = *p++; + if (c == '|') continue; + if (i >= argc) { + /* skip remaining optional args (just consume GET_ARG pointers) */ + switch (c) { + case 's': case 'a': + (void)GET_ARG(void*); + (void)GET_ARG(void*); + break; + default: + (void)GET_ARG(void*); + break; + } + continue; + } + switch (c) { + case 'o': { + mrb_value *vp = GET_ARG(mrb_value*); + *vp = argv[i++]; + break; + } + case 'S': { + mrb_value *vp = GET_ARG(mrb_value*); + mrb_ensure_string_type(mrb, argv[i]); + *vp = argv[i++]; + break; + } + case 'A': { + mrb_value *vp = GET_ARG(mrb_value*); + mrb_ensure_array_type(mrb, argv[i]); + *vp = argv[i++]; + break; + } + case 'H': { + mrb_value *vp = GET_ARG(mrb_value*); + mrb_ensure_hash_type(mrb, argv[i]); + *vp = argv[i++]; + break; + } + case 'i': { + mrb_int *ip = GET_ARG(mrb_int*); + *ip = mrb_as_int(mrb, argv[i++]); + break; + } + case 'b': { + mrb_bool *bp = GET_ARG(mrb_bool*); + *bp = mrb_test(argv[i++]); + break; + } + case 'f': { + mrb_float *fp = GET_ARG(mrb_float*); + *fp = mrb_as_float(mrb, argv[i++]); + break; + } + case 'n': { + mrb_sym *np = GET_ARG(mrb_sym*); + *np = to_sym(mrb, argv[i++]); + break; + } + case 'z': { + const char **zp = GET_ARG(const char**); + mrb_ensure_string_type(mrb, argv[i]); + *zp = RSTRING_CSTR(mrb, argv[i++]); + break; + } + case 's': { + const char **sp = GET_ARG(const char**); + mrb_int *lp = GET_ARG(mrb_int*); + mrb_ensure_string_type(mrb, argv[i]); + *sp = RSTRING_PTR(argv[i]); + *lp = RSTRING_LEN(argv[i]); + i++; + break; + } + case 'a': { + const mrb_value **pb = GET_ARG(const mrb_value**); + mrb_int *pl = GET_ARG(mrb_int*); + mrb_ensure_array_type(mrb, argv[i]); + struct RArray *a = mrb_ary_ptr(argv[i]); + *pb = ARY_PTR(a); + *pl = ARY_LEN(a); + i++; + break; + } + case 'c': { + struct RClass **cp = GET_ARG(struct RClass**); + ensure_class_type(mrb, argv[i]); + *cp = mrb_class_ptr(argv[i++]); + break; + } + default: + return -1; /* unknown specifier, fall back to slow path */ + } + } + return i; +} + static mrb_int get_args_v(mrb_state *mrb, mrb_args_format format, void** ptr, va_list *ap) { + /* try fast path first */ + mrb_int fast = get_args_fast(mrb, format, ptr, ap); + if (fast >= 0) return fast; + const char *fmt = format; char c; mrb_int i = 0; @@ -1967,6 +2130,7 @@ mrb_include_module(mrb_state *mrb, struct RClass *c, struct RClass *m) if (include_module_at(mrb, c, find_origin(c), m, 1) < 0) { mrb_raise(mrb, E_ARGUMENT_ERROR, "cyclic include detected"); } + mrb_const_cache_clear(mrb); if (c->tt == MRB_TT_MODULE && (c->flags & MRB_FL_CLASS_IS_INHERITED)) { struct RClass *data[2]; data[0] = c; @@ -2036,6 +2200,7 @@ mrb_prepend_module(mrb_state *mrb, struct RClass *c, struct RClass *m) if (include_module_at(mrb, c, c, m, 0) < 0) { mrb_raise(mrb, E_ARGUMENT_ERROR, "cyclic prepend detected"); } + mrb_const_cache_clear(mrb); if (c->tt == MRB_TT_MODULE && (c->flags & (MRB_FL_CLASS_IS_INHERITED|MRB_FL_CLASS_IS_PREPENDED))) { struct RClass *data[2]; @@ -2621,9 +2786,16 @@ mrb_vm_find_method(mrb_state *mrb, struct RClass *c, struct RClass **cp, mrb_sym mrb_method_t m; #ifndef MRB_NO_METHOD_CACHE struct RClass *oc = c; - int h = mrb_int_hash_func(mrb, ((intptr_t)oc) ^ mid) & (MRB_METHOD_CACHE_SIZE-1); - struct mrb_cache_entry *mc = &mrb->cache[h]; + int h = mrb_int_hash_func(mrb, ((intptr_t)oc >> 4) ^ mid) & (MRB_METHOD_CACHE_SIZE/2-1); + struct mrb_cache_entry *mc = &mrb->cache[h * 2]; + /* check way 0 */ + if (mc->c == c && mc->mid == mid) { + *cp = mc->c0; + return mc->m; + } + /* check way 1 */ + mc++; if (mc->c == c && mc->mid == mid) { *cp = mc->c0; return mc->m; @@ -2641,6 +2813,9 @@ mrb_vm_find_method(mrb_state *mrb, struct RClass *c, struct RClass **cp, mrb_sym *cp = c; m = create_method_value(mrb, flags, ptr); #ifndef MRB_NO_METHOD_CACHE + mc--; /* back to way 0 */ + if (mc->c != NULL && mc[1].c == NULL) + mc++; /* way 1 is empty, use it */ mc->c = oc; mc->c0 = c; mc->mid = mid; @@ -2902,10 +3077,6 @@ mrb_class_initialize(mrb_state *mrb, mrb_value obj) { struct RClass *c = mrb_class_ptr(obj); - if (c->iv) { - mrb_raise(mrb, E_TYPE_ERROR, "already initialized class"); - } - mrb_value a, b; mrb_get_args(mrb, "|C&", &a, &b); if (!mrb_nil_p(b)) { @@ -2924,6 +3095,7 @@ mrb_class_new_class(mrb_state *mrb, mrb_value cv) super = mrb_obj_value(mrb->object_class); } mrb_value new_class = mrb_obj_value(mrb_class_new(mrb, mrb_class_ptr(super))); + mrb_class_inherited(mrb, mrb_class_ptr(super), mrb_class_ptr(new_class)); mrb_sym mid = MRB_SYM(initialize); if (mrb_func_basic_p(mrb, new_class, mid, mrb_class_initialize)) { mrb_class_initialize(mrb, new_class); @@ -2931,7 +3103,6 @@ mrb_class_new_class(mrb_state *mrb, mrb_value cv) else { mrb_funcall_with_block(mrb, new_class, mid, n, &super, blk); } - mrb_class_inherited(mrb, mrb_class_ptr(super), mrb_class_ptr(new_class)); return new_class; } @@ -3795,7 +3966,7 @@ mrb_method_added(mrb_state *mrb, struct RClass *c, mrb_sym mid) } } -mrb_value +static mrb_value define_method_m(mrb_state *mrb, struct RClass *c, int vis) { mrb_sym mid; diff --git a/src/dump.c b/src/dump.c index 705f96ab33..b6e6e90d64 100644 --- a/src/dump.c +++ b/src/dump.c @@ -957,40 +957,31 @@ mrb_dump_irep_cfunc(mrb_state *mrb, const mrb_irep *irep, uint8_t flags, FILE *f } size_t bin_size, bin_idx = 0; int result = mrb_dump_irep(mrb, irep, flags, &bin, &bin_size); - if (result == MRB_DUMP_OK) { - if (fprintf(fp, "#include \n") < 0) { /* for uint8_t under at least Darwin */ - mrb_free(mrb, bin); - return MRB_DUMP_WRITE_FAULT; - } - if (fprintf(fp, - "%s\n" - "const uint8_t %s[] = {", - (flags & MRB_DUMP_STATIC) ? "static" - : "#ifdef __cplusplus\n" - "extern\n" - "#endif", - initname) < 0) { - mrb_free(mrb, bin); - return MRB_DUMP_WRITE_FAULT; - } - while (bin_idx < bin_size) { - if (bin_idx % 16 == 0) { - if (fputs("\n", fp) == EOF) { - mrb_free(mrb, bin); - return MRB_DUMP_WRITE_FAULT; - } - } - if (fprintf(fp, "0x%02x,", bin[bin_idx++]) < 0) { - mrb_free(mrb, bin); - return MRB_DUMP_WRITE_FAULT; - } - } - if (fputs("\n};\n", fp) == EOF) { - mrb_free(mrb, bin); - return MRB_DUMP_WRITE_FAULT; + if (result != MRB_DUMP_OK) goto exit; + + if (fprintf(fp, "#include \n") < 0) /* for uint8_t under at least Darwin */ + goto write_error; + if (fprintf(fp, + "%s\n" + "const uint8_t %s[] = {", + (flags & MRB_DUMP_STATIC) ? "static" + : "#ifdef __cplusplus\n" + "extern\n" + "#endif", + initname) < 0) + goto write_error; + while (bin_idx < bin_size) { + if (bin_idx % 16 == 0) { + if (fputs("\n", fp) == EOF) goto write_error; } + if (fprintf(fp, "0x%02x,", bin[bin_idx++]) < 0) goto write_error; } + if (fputs("\n};\n", fp) == EOF) goto write_error; + goto exit; +write_error: + result = MRB_DUMP_WRITE_FAULT; +exit: mrb_free(mrb, bin); return result; } diff --git a/src/error.c b/src/error.c index cce3825376..9ffd7c3377 100644 --- a/src/error.c +++ b/src/error.c @@ -592,18 +592,20 @@ mrb_make_exception(mrb_state *mrb, mrb_value exc, mrb_value mesg) MRB_API mrb_noreturn void mrb_sys_fail(mrb_state *mrb, const char *mesg) { + mrb_int no = (mrb_int)errno; + mrb_value mesg_str = mesg ? mrb_str_new_cstr(mrb, mesg) : mrb_nil_value(); + if (mrb_class_defined_id(mrb, MRB_SYM(SystemCallError))) { struct RClass *sce = mrb_class_get_id(mrb, MRB_SYM(SystemCallError)); - mrb_int no = (mrb_int)errno; if (mesg != NULL) { - mrb_funcall_id(mrb, mrb_obj_value(sce), MRB_SYM(_sys_fail), 2, mrb_fixnum_value(no), mrb_str_new_cstr(mrb, mesg)); + mrb_funcall_argv2(mrb, mrb_obj_value(sce), MRB_SYM(_sys_fail), mrb_fixnum_value(no), mesg_str); } else { - mrb_funcall_id(mrb, mrb_obj_value(sce), MRB_SYM(_sys_fail), 1, mrb_fixnum_value(no)); + mrb_funcall_argv1(mrb, mrb_obj_value(sce), MRB_SYM(_sys_fail), mrb_fixnum_value(no)); } } - mrb_raise(mrb, E_RUNTIME_ERROR, mesg); + mrb_exc_raise(mrb, mrb_exc_new_str(mrb, E_RUNTIME_ERROR, mesg ? mesg_str : mrb_str_new_lit(mrb, ""))); } /* diff --git a/src/fmt_fp.c b/src/fmt_fp.c deleted file mode 100644 index 173cc3b58f..0000000000 --- a/src/fmt_fp.c +++ /dev/null @@ -1,380 +0,0 @@ -#include -#include - -#ifndef MRB_NO_FLOAT -/*********************************************************************** - - Routine for converting a single-precision - floating-point number into a string. - - The code in this function was inspired from Fred Bayer's pdouble.c. - Since pdouble.c was released as Public Domain, I'm releasing this - code as public domain as well. - - Dave Hylands - - The original code can be found in https://github.com/dhylands/format-float -***********************************************************************/ - -/*********************************************************************** - - I modified the routine for mruby: - - * support `double` - * support `#` (alt_form) modifier - - My modifications in this file are also placed in the public domain. - - Matz (Yukihiro Matsumoto) - -***********************************************************************/ - -#include - -#ifdef MRB_USE_FLOAT32 - -// 1 sign bit, 8 exponent bits, and 23 mantissa bits. -// exponent values 0 and 255 are reserved, exponent can be 1 to 254. -// exponent is stored with a bias of 127. -// The min and max floats are on the order of 1x10^37 and 1x10^-37 - -#define FLT_DECEXP 32 -#define FLT_ROUND_TO_ONE 0.9999995F -#define FLT_MIN_BUF_SIZE 6 // -9e+99 - -#else - -// 1 sign bit, 11 exponent bits, and 52 mantissa bits. - -#define FLT_DECEXP 256 -#define FLT_ROUND_TO_ONE 0.999999999995 -#define FLT_MIN_BUF_SIZE 7 // -9e+199 - -#endif /* MRB_USE_FLOAT32 */ - -static const mrb_float g_pos_pow[] = { -#ifndef MRB_USE_FLOAT32 - 1e256, 1e128, 1e64, -#endif - 1e32, 1e16, 1e8, 1e4, 1e2, 1e1 -}; -static const mrb_float g_neg_pow[] = { -#ifndef MRB_USE_FLOAT32 - 1e-256, 1e-128, 1e-64, -#endif - 1e-32, 1e-16, 1e-8, 1e-4, 1e-2, 1e-1 -}; - -/* - * mrb_format_float(mrb_float f, char *buf, size_t buf_size, char fmt, int prec, char sign) - * - * fmt: should be one of 'e', 'E', 'f', 'F', 'g', or 'G'. (|0x80 for '#') - * prec: is the precision (as specified in printf) - * sign: should be '\0', '+', or ' ' ('\0' is the normal one - only print - * a sign if ```f``` is negative. Anything else is printed as the - * sign character for positive numbers. - */ - -int -mrb_format_float(mrb_float f, char *buf, size_t buf_size, char fmt, int prec, char sign) { - char *s = buf; - int buf_remaining = (int)buf_size - 1; - int alt_form = 0; - - if ((uint8_t)fmt & 0x80) { - fmt &= 0x7f; /* turn off alt_form flag */ - alt_form = 1; - } - if (buf_size <= FLT_MIN_BUF_SIZE) { - // Smallest exp notion is -9e+99 (-9e+199) which is 6 (7) chars plus terminating - // null. - - if (buf_size >= 2) { - *s++ = '?'; - } - if (buf_size >= 1) { - *s++ = '\0'; - } - return buf_size >= 2; - } - if (signbit(f)) { - *s++ = '-'; - f = -f; - } - else if (sign) { - *s++ = sign; - } - buf_remaining -= (int)(s - buf); // Adjust for sign - - { - char uc = fmt & 0x20; - if (isinf(f)) { - *s++ = 'I' ^ uc; - *s++ = 'N' ^ uc; - *s++ = 'F' ^ uc; - goto ret; - } - else if (isnan(f)) { - *s++ = 'N' ^ uc; - *s++ = 'A' ^ uc; - *s++ = 'N' ^ uc; - ret: - *s = '\0'; - return (int)(s - buf); - } - } - - if (prec < 0) { - prec = 6; - } - char e_char = 'E' | (fmt & 0x20); // e_char will match case of fmt - fmt |= 0x20; // Force fmt to be lowercase - char org_fmt = fmt; - if (fmt == 'g' && prec == 0) { - prec = 1; - } - int e, e1; - int dec = 0; - char e_sign = '\0'; - int num_digits = 0; - const mrb_float *pos_pow = g_pos_pow; - const mrb_float *neg_pow = g_neg_pow; - - if (f == 0.0) { - e = 0; - if (fmt == 'e') { - e_sign = '+'; - } - else if (fmt == 'f') { - num_digits = prec + 1; - } - } - else if (f < 1.0) { // f < 1.0 - char first_dig = '0'; - if (f >= FLT_ROUND_TO_ONE) { - first_dig = '1'; - } - - // Build negative exponent - for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) { - if (*neg_pow > f) { - e += e1; - f *= *pos_pow; - } - } - char e_sign_char = '-'; - if (f < 1.0) { - if (f >= FLT_ROUND_TO_ONE) { - f = 1.0; - if (e == 0) { - e_sign_char = '+'; - } - } - else { - e++; - f *= 10.0; - } - } - - // If the user specified 'g' format, and e is <= 4, then we'll switch - // to the fixed format ('f') - - if (fmt == 'f' || (fmt == 'g' && e <= 4)) { - fmt = 'f'; - dec = -1; - *s++ = first_dig; - - if (org_fmt == 'g') { - prec += (e - 1); - } - // truncate precision to prevent buffer overflow - if (prec + 2 > buf_remaining) { - prec = buf_remaining - 2; - } - num_digits = prec; - if (num_digits || alt_form) { - *s++ = '.'; - while (--e && num_digits) { - *s++ = '0'; - num_digits--; - } - } - } - else { - // For e & g formats, we'll be printing the exponent, so set the - // sign. - e_sign = e_sign_char; - dec = 0; - - if (prec > (buf_remaining - FLT_MIN_BUF_SIZE)) { - prec = buf_remaining - FLT_MIN_BUF_SIZE; - if (fmt == 'g') { - prec++; - } - } - } - } - else { - // Build positive exponent - for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) { - if (*pos_pow <= f) { - e += e1; - f *= *neg_pow; - } - } - // correct for FP rounding errors in the power-of-10 loop - // (e.g. x87 extended precision can leave f >= 10.0) - if (f >= 10.0) { - f *= 0.1; - e++; - } - - // If the user specified fixed format (fmt == 'f') and e makes the - // number too big to fit into the available buffer, then we'll - // switch to the 'e' format. - - if (fmt == 'f') { - if (e >= buf_remaining) { - fmt = 'e'; - } - else if ((e + prec + 2) > buf_remaining) { - prec = buf_remaining - e - 2; - if (prec < 0) { - // This means no decimal point, so we can add one back - // for the decimal. - prec++; - } - } - } - if (fmt == 'e' && prec > (buf_remaining - 6)) { - prec = buf_remaining - 6; - } - // If the user specified 'g' format, and e is < prec, then we'll switch - // to the fixed format. - - if (fmt == 'g' && e < prec) { - fmt = 'f'; - prec -= (e + 1); - } - if (fmt == 'f') { - dec = e; - num_digits = prec + e + 1; - } - else { - e_sign = '+'; - } - } - if (prec < 0) { - // This can happen when the prec is trimmed to prevent buffer overflow - prec = 0; - } - - // We now have f as a floating-point number between >= 1 and < 10 - // (or equal to zero), and e contains the absolute value of the power of - // 10 exponent, and (dec + 1) == the number of digits before the decimal. - - // For e, prec is # digits after the decimal - // For f, prec is # digits after the decimal - // For g, prec is the max number of significant digits - // - // For e & g there will be a single digit before the decimal - // for f there will be e digits before the decimal - - if (fmt == 'e') { - num_digits = prec + 1; - if (prec == 0) prec = 1; - } - else if (fmt == 'g') { - num_digits = prec; - } - - // Print the digits of the mantissa - for (int i = 0; i < num_digits; i++,dec--) { - int8_t d = (int8_t)f; - if (d > 9) d = 9; - if (d < 0) d = 0; - *s++ = '0' + d; - if (dec == 0 && (prec > 0 || alt_form)) { - *s++ = '.'; - } - f -= (mrb_float)d; - f *= 10.0; - } - - // Round - if (f >= 5.0) { - char *rs = s; - rs--; - while (1) { - if (*rs == '.') { - rs--; - continue; - } - if (*rs < '0' || *rs > '9') { - // + or - - rs++; // So we sit on the digit to the right of the sign - break; - } - if (*rs < '9') { - (*rs)++; - break; - } - *rs = '0'; - if (rs == buf) { - break; - } - rs--; - } - if (*rs == '0') { - // We need to insert a 1 - if (fmt != 'f' && rs[1] == '.') { - // We're going to round 9.99 to 10.00 - // Move the decimal point - rs[0] = '.'; - rs[1] = '0'; - if (e_sign == '-') { - e--; - } - else { - e++; - } - } - s++; - char *ss = s; - while (ss > rs) { - *ss = ss[-1]; - ss--; - } - *rs = '1'; - if (f < 1.0 && fmt == 'f') { - // We rounded up to 1.0 - prec--; - } - } - } - - if (org_fmt == 'g' && prec > 0 && !alt_form) { - // Remove trailing zeros and a trailing decimal point - while (s[-1] == '0') { - s--; - } - if (s[-1] == '.') { - s--; - } - } - // Append the exponent - if (e_sign) { - *s++ = e_char; - *s++ = e_sign; - if (e >= 100) { - *s++ = '0' + (e / 100); - e %= 100; - } - *s++ = '0' + (e / 10); - *s++ = '0' + (e % 10); - } - - *s = '\0'; - return (int)(s - buf); -} -#endif diff --git a/src/fp_uscale.c b/src/fp_uscale.c new file mode 100644 index 0000000000..03d22ec44b --- /dev/null +++ b/src/fp_uscale.c @@ -0,0 +1,1514 @@ +/* +** fp_uscale.c - Unrounded Scaling float conversion +** +** Unified float formatting and parsing using the uscale algorithm. +** Based on Russ Cox's "Unrounded Scaling" approach. +** See https://research.swtch.com/fp +** +** Replaces fmt_fp.c (formatting) and readfloat.c (parsing). +*/ + +#include + +#ifndef MRB_NO_FLOAT + +#include +#include +#include + +/* ======== Platform support ======== */ + +#if defined(__SIZEOF_INT128__) +static inline void mul64(uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo) +{ + __uint128_t r = (__uint128_t)a * b; + *hi = (uint64_t)(r >> 64); + *lo = (uint64_t)r; +} +#elif defined(_MSC_VER) && defined(_M_X64) +#include +static inline void mul64(uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo) +{ + *lo = _umul128(a, b, hi); +} +#else +static inline void mul64(uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo) +{ + uint64_t a_lo = (uint32_t)a, a_hi = a >> 32; + uint64_t b_lo = (uint32_t)b, b_hi = b >> 32; + uint64_t p0 = a_lo * b_lo; + uint64_t p1 = a_lo * b_hi; + uint64_t p2 = a_hi * b_lo; + uint64_t p3 = a_hi * b_hi; + uint64_t mid = (p0 >> 32) + (uint32_t)p1 + (uint32_t)p2; + *lo = (p0 & 0xFFFFFFFFULL) | (mid << 32); + *hi = p3 + (p1 >> 32) + (p2 >> 32) + (mid >> 32); +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define clz64(x) __builtin_clzll(x) +#elif defined(_MSC_VER) && defined(_M_X64) +static inline int clz64(uint64_t x) +{ + unsigned long idx; + _BitScanReverse64(&idx, x); + return 63 - (int)idx; +} +#else +static inline int clz64(uint64_t x) +{ + int n = 0; + if (x <= 0x00000000FFFFFFFFULL) { n += 32; x <<= 32; } + if (x <= 0x0000FFFFFFFFFFFFULL) { n += 16; x <<= 16; } + if (x <= 0x00FFFFFFFFFFFFFFULL) { n += 8; x <<= 8; } + if (x <= 0x0FFFFFFFFFFFFFFFULL) { n += 4; x <<= 4; } + if (x <= 0x3FFFFFFFFFFFFFFFULL) { n += 2; x <<= 2; } + if (x <= 0x7FFFFFFFFFFFFFFFULL) { n += 1; } + return n; +} +#endif + +#define bits_len64(x) (64 - clz64(x)) + +/* ======== pow10 table ======== */ + +typedef struct { + uint64_t hi; + uint64_t lo; +} pow10_entry; + +#define POW10_MIN (-343) +#define POW10_MAX 341 +#define POW10_TAB_SIZE (POW10_MAX - POW10_MIN + 1) + +/* generated by tools/gen_pow10_tab.rb */ +static const pow10_entry pow10_tab[POW10_TAB_SIZE] = { + {0xbf29dcaba82fdeafULL, 0x8bcd1178c77f03ccULL}, + {0xeef453d6923bd65bULL, 0xeec055d6f95ec4c0ULL}, + {0x9558b4661b6565f9ULL, 0xb53835a65bdb3af8ULL}, + {0xbaaee17fa23ebf77ULL, 0xa286430ff2d209b6ULL}, + {0xe95a99df8ace6f54ULL, 0x0b27d3d3ef868c23ULL}, + {0x91d8a02bb6c10595ULL, 0x86f8e46475b41796ULL}, + {0xb64ec836a47146faULL, 0x68b71d7d93211d7bULL}, + {0xe3e27a444d8d98b8ULL, 0x02e4e4dcf7e964daULL}, + {0x8e6d8c6ab0787f73ULL, 0x01cf0f0a1af1df08ULL}, + {0xb208ef855c969f50ULL, 0x4242d2cca1ae56caULL}, + {0xde8b2b66b3bc4724ULL, 0x52d3877fca19ec7dULL}, + {0x8b16fb203055ac77ULL, 0xb3c434afde5033ceULL}, + {0xaddcb9e83c6b1794ULL, 0x20b541dbd5e440c2ULL}, + {0xd953e8624b85dd79ULL, 0x28e29252cb5d50f2ULL}, + {0x87d4713d6f33aa6cULL, 0x798d9b73bf1a5297ULL}, + {0xa9c98d8ccb009507ULL, 0x97f10250aee0e73dULL}, + {0xd43bf0effdc0ba49ULL, 0xfded42e4da99210dULL}, + {0x84a57695fe98746eULL, 0xfeb449cf089fb4a8ULL}, + {0xa5ced43b7e3e9189ULL, 0xbe615c42cac7a1d2ULL}, + {0xcf42894a5dce35ebULL, 0xadf9b3537d798a46ULL}, + {0x818995ce7aa0e1b3ULL, 0x8cbc10142e6bf66cULL}, + {0xa1ebfb4219491a20ULL, 0xefeb14193a06f407ULL}, + {0xca66fa129f9b60a7ULL, 0x2be5d91f8888b109ULL}, + {0xfd00b897478238d1ULL, 0x76df4f676aaadd4bULL}, + {0x9e20735e8cb16383ULL, 0xaa4b91a0a2aaca4fULL}, + {0xc5a890362fddbc63ULL, 0x14de7608cb557ce2ULL}, + {0xf712b443bbd52b7cULL, 0x5a16138afe2adc1bULL}, + {0x9a6bb0aa55653b2eULL, 0xb84dcc36dedac991ULL}, + {0xc1069cd4eabe89f9ULL, 0x66613f4496917bf5ULL}, + {0xf148440a256e2c77ULL, 0x3ff98f15bc35daf2ULL}, + {0x96cd2a865764dbcbULL, 0xc7fbf96d95a1a8d7ULL}, + {0xbc807527ed3e12bdULL, 0x39faf7c8fb0a130dULL}, + {0xeba09271e88d976cULL, 0x0879b5bb39cc97d1ULL}, + {0x93445b8731587ea4ULL, 0x854c1195041fdee2ULL}, + {0xb8157268fdae9e4dULL, 0xa69f15fa4527d69bULL}, + {0xe61acf033d1a45e0ULL, 0x9046db78d671cc42ULL}, + {0x8fd0c16206306bacULL, 0x5a2c492b86071fa9ULL}, + {0xb3c4f1ba87bc8697ULL, 0x70b75b766788e793ULL}, + {0xe0b62e2929aba83dULL, 0xcce53254016b2178ULL}, + {0x8c71dcd9ba0b4926ULL, 0x600f3f7480e2f4ebULL}, + {0xaf8e5410288e1b70ULL, 0xf8130f51a11bb226ULL}, + {0xdb71e91432b1a24bULL, 0x3617d32609629eafULL}, + {0x892731ac9faf056fULL, 0x41cee3f7c5dda32dULL}, + {0xab70fe17c79ac6cbULL, 0x92429cf5b7550bf9ULL}, + {0xd64d3d9db981787eULL, 0xf6d34433252a4ef7ULL}, + {0x85f0468293f0eb4fULL, 0xda440a9ff73a715aULL}, + {0xa76c582338ed2622ULL, 0x50d50d47f5090db1ULL}, + {0xd1476e2c07286fabULL, 0xe50a5099f24b511eULL}, + {0x82cca4db847945cbULL, 0xaf267260376f12b2ULL}, + {0xa37fce126597973dULL, 0x1af00ef8454ad75fULL}, + {0xcc5fc196fefd7d0dULL, 0xe1ac12b6569d8d37ULL}, + {0xff77b1fcbebcdc50ULL, 0xda171763ec44f085ULL}, + {0x9faacf3df73609b2ULL, 0x884e6e9e73ab1653ULL}, + {0xc795830d75038c1eULL, 0x2a620a461095dbe8ULL}, + {0xf97ae3d0d2446f26ULL, 0xb4fa8cd794bb52e2ULL}, + {0x9becce62836ac578ULL, 0xb11c9806bcf513cdULL}, + {0xc2e801fb244576d6ULL, 0xdd63be086c3258c0ULL}, + {0xf3a20279ed56d48bULL, 0x94bcad8a873eeef0ULL}, + {0x9845418c345644d7ULL, 0x7cf5ec7694875556ULL}, + {0xbe5691ef416bd60dULL, 0xdc33679439a92aacULL}, + {0xedec366b11c6cb90ULL, 0xd340417948137557ULL}, + {0x94b3a202eb1c3f3aULL, 0x840828ebcd0c2956ULL}, + {0xb9e08a83a5e34f08ULL, 0x250a3326c04f33acULL}, + {0xe858ad248f5c22caULL, 0x2e4cbff070630097ULL}, + {0x91376c36d99995bfULL, 0xdceff7f6463de05eULL}, + {0xb58547448ffffb2eULL, 0x542bf5f3d7cd5875ULL}, + {0xe2e69915b3fff9faULL, 0xe936f370cdc0ae93ULL}, + {0x8dd01fad907ffc3cULL, 0x51c2582680986d1cULL}, + {0xb1442798f49ffb4bULL, 0x6632ee3020be8863ULL}, + {0xdd95317f31c7fa1eULL, 0xbfbfa9bc28ee2a7cULL}, + {0x8a7d3eef7f1cfc53ULL, 0xb7d7ca159994da8dULL}, + {0xad1c8eab5ee43b67ULL, 0x25cdbc9afffa1130ULL}, + {0xd863b256369d4a41ULL, 0x6f412bc1bff8957dULL}, + {0x873e4f75e2224e69ULL, 0xa588bb5917fb5d6eULL}, + {0xa90de3535aaae203ULL, 0x8eeaea2f5dfa34c9ULL}, + {0xd3515c2831559a84ULL, 0xf2a5a4bb3578c1fcULL}, + {0x8412d9991ed58092ULL, 0x17a786f5016b793dULL}, + {0xa5178fff668ae0b7ULL, 0x9d9168b241c6578dULL}, + {0xce5d73ff402d98e4ULL, 0x04f5c2ded237ed70ULL}, + {0x80fa687f881c7f8fULL, 0x831999cb4362f466ULL}, + {0xa139029f6a239f73ULL, 0xe3e0003e143bb17fULL}, + {0xc987434744ac874fULL, 0x5cd8004d994a9ddfULL}, + {0xfbe9141915d7a923ULL, 0xb40e0060ff9d4557ULL}, + {0x9d71ac8fada6c9b6ULL, 0x9088c03c9fc24b56ULL}, + {0xc4ce17b399107c23ULL, 0x34aaf04bc7b2de2cULL}, + {0xf6019da07f549b2cULL, 0x81d5ac5eb99f95b7ULL}, + {0x99c102844f94e0fcULL, 0xd1258bbb3403bd92ULL}, + {0xc0314325637a193aULL, 0x056eeeaa0104acf7ULL}, + {0xf03d93eebc589f89ULL, 0x86caaa548145d835ULL}, + {0x96267c7535b763b6ULL, 0xb43eaa74d0cba721ULL}, + {0xbbb01b9283253ca3ULL, 0x614e551204fe90e9ULL}, + {0xea9c227723ee8bccULL, 0xb9a1ea56863e3523ULL}, + {0x92a1958a76751760ULL, 0xf405327613e6e136ULL}, + {0xb749faed14125d37ULL, 0x31067f1398e09984ULL}, + {0xe51c79a85916f485ULL, 0x7d481ed87f18bfe5ULL}, + {0x8f31cc0937ae58d3ULL, 0x2e4d13474f6f77efULL}, + {0xb2fe3f0b8599ef08ULL, 0x79e05819234b55eaULL}, + {0xdfbdcece67006acaULL, 0x98586e1f6c1e2b65ULL}, + {0x8bd6a141006042beULL, 0x1f3744d3a392db1fULL}, + {0xaecc49914078536eULL, 0xa70516088c7791e7ULL}, + {0xda7f5bf590966849ULL, 0x50c65b8aaf957661ULL}, + {0x888f99797a5e012eULL, 0x927bf936adbd69fcULL}, + {0xaab37fd7d8f58179ULL, 0x371af784592cc47cULL}, + {0xd5605fcdcf32e1d7ULL, 0x04e1b5656f77f59bULL}, + {0x855c3be0a17fcd27ULL, 0xa30d115f65aaf980ULL}, + {0xa6b34ad8c9dfc070ULL, 0x0bd055b73f15b7e1ULL}, + {0xd0601d8efc57b08cULL, 0x0ec46b250edb25d9ULL}, + {0x823c12795db6ce58ULL, 0x893ac2f72948f7a7ULL}, + {0xa2cb1717b52481eeULL, 0xab8973b4f39b3591ULL}, + {0xcb7ddcdda26da269ULL, 0x566bd0a2308202f6ULL}, + {0xfe5d54150b090b03ULL, 0x2c06c4cabca283b3ULL}, + {0x9efa548d26e5a6e2ULL, 0x3b843afeb5e59250ULL}, + {0xc6b8e9b0709f109bULL, 0xca6549be635ef6e4ULL}, + {0xf867241c8cc6d4c1ULL, 0x3cfe9c2dfc36b49dULL}, + {0x9b407691d7fc44f9ULL, 0x861f219cbda230e2ULL}, + {0xc21094364dfb5637ULL, 0x67a6ea03ed0abd1bULL}, + {0xf294b943e17a2bc5ULL, 0xc190a484e84d6c62ULL}, + {0x979cf3ca6cec5b5bULL, 0x58fa66d3113063bdULL}, + {0xbd8430bd08277232ULL, 0xaf390087d57c7cacULL}, + {0xece53cec4a314ebeULL, 0x5b0740a9cadb9bd7ULL}, + {0x940f4613ae5ed137ULL, 0x78e4886a1ec94166ULL}, + {0xb913179899f68585ULL, 0xd71daa84a67b91c0ULL}, + {0xe757dd7ec07426e6ULL, 0xcce51525d01a7630ULL}, + {0x9096ea6f38489850ULL, 0xc00f2d37a21089deULL}, + {0xb4bca50b065abe64ULL, 0xf012f8858a94ac56ULL}, + {0xe1ebce4dc7f16dfcULL, 0x2c17b6a6ed39d76bULL}, + {0x8d3360f09cf6e4beULL, 0x9b8ed228544426a3ULL}, + {0xb080392cc4349dedULL, 0x427286b26955304cULL}, + {0xdca04777f541c568ULL, 0x130f285f03aa7c5fULL}, + {0x89e42caaf9491b61ULL, 0x0be9793b624a8dbbULL}, + {0xac5d37d5b79b623aULL, 0xcee3d78a3add312aULL}, + {0xd77485cb25823ac8ULL, 0x829ccd6cc9947d74ULL}, + {0x86a8d39ef77164bdULL, 0x51a20063fdfcce68ULL}, + {0xa8530886b54dbdecULL, 0x260a807cfd7c0203ULL}, + {0xd267caa862a12d67ULL, 0x2f8d209c3cdb0284ULL}, + {0x8380dea93da4bc61ULL, 0xbdb83461a608e192ULL}, + {0xa46116538d0deb79ULL, 0xad26417a0f8b19f7ULL}, + {0xcd795be870516657ULL, 0x986fd1d8936de074ULL}, + {0x806bd9714632dff7ULL, 0xff45e3275c24ac49ULL}, + {0xa086cfcd97bf97f4ULL, 0x7f175bf1332dd75bULL}, + {0xc8a883c0fdaf7df1ULL, 0x9edd32ed7ff94d32ULL}, + {0xfad2a4b13d1b5d6dULL, 0x86947fa8dff7a07eULL}, + {0x9cc3a6eec6311a64ULL, 0x341ccfc98bfac44fULL}, + {0xc3f490aa77bd60fdULL, 0x412403bbeef97563ULL}, + {0xf4f1b4d515acb93cULL, 0x116d04aaeab7d2bbULL}, + {0x991711052d8bf3c6ULL, 0x8ae422ead2b2e3b5ULL}, + {0xbf5cd54678eef0b7ULL, 0x2d9d2ba5875f9ca2ULL}, + {0xef340a98172aace5ULL, 0x7904768ee93783cbULL}, + {0x9580869f0e7aac0fULL, 0x2ba2ca1951c2b25fULL}, + {0xbae0a846d2195713ULL, 0x768b7c9fa6335ef6ULL}, + {0xe998d258869facd8ULL, 0xd42e5bc78fc036b4ULL}, + {0x91ff83775423cc07ULL, 0x849cf95cb9d82230ULL}, + {0xb67f6455292cbf09ULL, 0xe5c437b3e84e2abdULL}, + {0xe41f3d6a7377eecbULL, 0xdf3545a0e261b56cULL}, + {0x8e938662882af53fULL, 0xab814b848d7d1163ULL}, + {0xb23867fb2a35b28eULL, 0x16619e65b0dc55bcULL}, + {0xdec681f9f4c31f32ULL, 0x9bfa05ff1d136b2bULL}, + {0x8b3c113c38f9f37fULL, 0x217c43bf722c22fbULL}, + {0xae0b158b4738705fULL, 0x69db54af4eb72bbaULL}, + {0xd98ddaee19068c77ULL, 0xc45229db2264f6a8ULL}, + {0x87f8a8d4cfa417caULL, 0x1ab35a28f57f1a29ULL}, + {0xa9f6d30a038d1dbdULL, 0xa16030b332dee0b3ULL}, + {0xd47487cc8470652cULL, 0x89b83cdfff9698e0ULL}, + {0x84c8d4dfd2c63f3cULL, 0xd613260bffbe1f8cULL}, + {0xa5fb0a17c777cf0aULL, 0x0b97ef8effada76fULL}, + {0xcf79cc9db955c2cdULL, 0x8e7deb72bf99114bULL}, + {0x81ac1fe293d599c0ULL, 0x390eb327b7bfaacfULL}, + {0xa21727db38cb0030ULL, 0x47525ff1a5af9583ULL}, + {0xca9cf1d206fdc03cULL, 0x5926f7ee0f1b7ae3ULL}, + {0xfd442e4688bd304bULL, 0x6f70b5e992e2599cULL}, + {0x9e4a9cec15763e2fULL, 0x65a671b1fbcd7801ULL}, + {0xc5dd44271ad3cdbbULL, 0xbf100e1e7ac0d602ULL}, + {0xf7549530e188c129ULL, 0x2ed411a619710b83ULL}, + {0x9a94dd3e8cf578baULL, 0x7d448b07cfe6a731ULL}, + {0xc13a148e3032d6e8ULL, 0x1c95adc9c3e050feULL}, + {0xf18899b1bc3f8ca2ULL, 0x23bb193c34d8653eULL}, + {0x96f5600f15a7b7e6ULL, 0xd654efc5a1073f46ULL}, + {0xbcb2b812db11a5dfULL, 0x8bea2bb709490f18ULL}, + {0xebdf661791d60f57ULL, 0xeee4b6a4cb9b52deULL}, + {0x936b9fcebb25c996ULL, 0x354ef226ff4113cbULL}, + {0xb84687c269ef3bfcULL, 0xc2a2aeb0bf1158bdULL}, + {0xe65829b3046b0afbULL, 0xf34b5a5ceed5aeedULL}, + {0x8ff71a0fe2c2e6ddULL, 0xb80f187a15458d54ULL}, + {0xb3f4e093db73a094ULL, 0xa612de989a96f0a9ULL}, + {0xe0f218b8d25088b9ULL, 0xcf97963ec13cacd3ULL}, + {0x8c974f7383725574ULL, 0xe1bebde738c5ec04ULL}, + {0xafbd2350644eead0ULL, 0x1a2e6d6106f76705ULL}, + {0xdbac6c247d62a584ULL, 0x20ba08b948b540c6ULL}, + {0x894bc396ce5da773ULL, 0x94744573cd71487cULL}, + {0xab9eb47c81f51150ULL, 0xf99156d0c0cd9a9bULL}, + {0xd686619ba27255a3ULL, 0x37f5ac84f1010142ULL}, + {0x8613fd0145877586ULL, 0x42f98bd316a0a0c9ULL}, + {0xa798fc4196e952e8ULL, 0xd3b7eec7dc48c8fbULL}, + {0xd17f3b51fca3a7a1ULL, 0x08a5ea79d35afb3aULL}, + {0x82ef85133de648c5ULL, 0x6567b28c2418dd04ULL}, + {0xa3ab66580d5fdaf6ULL, 0x3ec19f2f2d1f1445ULL}, + {0xcc963fee10b7d1b4ULL, 0xce7206faf866d957ULL}, + {0xffbbcfe994e5c620ULL, 0x020e88b9b6808fadULL}, + {0x9fd561f1fd0f9bd4ULL, 0x01491574121059ccULL}, + {0xc7caba6e7c5382c9ULL, 0x019b5ad11694703fULL}, + {0xf9bd690a1b68637cULL, 0xc20231855c398c4fULL}, + {0x9c1661a651213e2eULL, 0xf9415ef359a3f7b1ULL}, + {0xc31bfa0fe5698db9ULL, 0xb791b6b0300cf59dULL}, + {0xf3e2f893dec3f127ULL, 0xa576245c3c103305ULL}, + {0x986ddb5c6b3a76b8ULL, 0x0769d6b9a58a1fe3ULL}, + {0xbe89523386091466ULL, 0x09444c680eeca7dcULL}, + {0xee2ba6c0678b5980ULL, 0x8b955f8212a7d1d3ULL}, + {0x94db483840b717f0ULL, 0x573d5bb14ba8e323ULL}, + {0xba121a4650e4ddecULL, 0x6d0cb29d9e931becULL}, + {0xe896a0d7e51e1567ULL, 0x884fdf450637e2e8ULL}, + {0x915e2486ef32cd61ULL, 0xf531eb8b23e2edd1ULL}, + {0xb5b5ada8aaff80b9ULL, 0xf27e666decdba945ULL}, + {0xe3231912d5bf60e7ULL, 0xef1e000968129396ULL}, + {0x8df5efabc5979c90ULL, 0x3572c005e10b9c3eULL}, + {0xb1736b96b6fd83b4ULL, 0x42cf7007594e834dULL}, + {0xddd0467c64bce4a1ULL, 0x53834c092fa22421ULL}, + {0x8aa22c0dbef60ee5ULL, 0x94320f85bdc55694ULL}, + {0xad4ab7112eb3929eULL, 0x793e93672d36ac39ULL}, + {0xd89d64d57a607745ULL, 0x178e3840f8845748ULL}, + {0x87625f056c7c4a8cULL, 0xeeb8e3289b52b68dULL}, + {0xa93af6c6c79b5d2eULL, 0x2a671bf2c2276430ULL}, + {0xd389b4787982347aULL, 0xb500e2ef72b13d3cULL}, + {0x843610cb4bf160ccULL, 0x31208dd5a7aec645ULL}, + {0xa54394fe1eedb8ffULL, 0x3d68b14b119a77d7ULL}, + {0xce947a3da6a9273fULL, 0x8cc2dd9dd60115cdULL}, + {0x811ccc668829b888ULL, 0xf7f9ca82a5c0ada0ULL}, + {0xa163ff802a3426a9ULL, 0x35f83d234f30d908ULL}, + {0xc9bcff6034c13053ULL, 0x03764c6c22fd0f4aULL}, + {0xfc2c3f3841f17c68ULL, 0x4453df872bbc531dULL}, + {0x9d9ba7832936edc1ULL, 0x2ab46bb47b55b3f2ULL}, + {0xc5029163f384a932ULL, 0xf56186a19a2b20eeULL}, + {0xf64335bcf065d37eULL, 0xb2b9e84a00b5e92aULL}, + {0x99ea0196163fa42fULL, 0xafb4312e4071b1baULL}, + {0xc06481fb9bcf8d3aULL, 0x1ba13d79d08e1e29ULL}, + {0xf07da27a82c37089ULL, 0xa2898cd844b1a5b3ULL}, + {0x964e858c91ba2656ULL, 0xc595f8072aef0790ULL}, + {0xbbe226efb628afebULL, 0x76fb7608f5aac974ULL}, + {0xeadab0aba3b2dbe6ULL, 0xd4ba538b33157bd1ULL}, + {0x92c8ae6b464fc970ULL, 0xc4f47436ffed6d62ULL}, + {0xb77ada0617e3bbccULL, 0xf6319144bfe8c8bbULL}, + {0xe55990879ddcaabeULL, 0x33bdf595efe2faeaULL}, + {0x8f57fa54c2a9eab7ULL, 0x6056b97db5eddcd2ULL}, + {0xb32df8e9f3546565ULL, 0xb86c67dd23695406ULL}, + {0xdff9772470297ebeULL, 0xa68781d46c43a908ULL}, + {0x8bfbea76c619ef37ULL, 0xa814b124c3aa49a5ULL}, + {0xaefae51477a06b04ULL, 0x1219dd6df494dc0eULL}, + {0xdab99e59958885c5ULL, 0x16a054c971ba1312ULL}, + {0x88b402f7fd75539cULL, 0xee2434fde7144bebULL}, + {0xaae103b5fcd2a882ULL, 0x29ad423d60d95ee6ULL}, + {0xd59944a37c0752a3ULL, 0xb41892ccb90fb6a0ULL}, + {0x857fcae62d8493a6ULL, 0x908f5bbff3a9d224ULL}, + {0xa6dfbd9fb8e5b88fULL, 0x34b332aff09446adULL}, + {0xd097ad07a71f26b3ULL, 0x81dfff5becb95858ULL}, + {0x825ecc24c8737830ULL, 0x712bff9973f3d737ULL}, + {0xa2f67f2dfa90563cULL, 0x8d76ff7fd0f0cd05ULL}, + {0xcbb41ef979346bcbULL, 0xb0d4bf5fc52d0046ULL}, + {0xfea126b7d78186bdULL, 0x1d09ef37b6784057ULL}, + {0x9f24b832e6b0f437ULL, 0xf2263582d20b2836ULL}, + {0xc6ede63fa05d3144ULL, 0x6eafc2e3868df244ULL}, + {0xf8a95fcf88747d95ULL, 0x8a5bb39c68316ed5ULL}, + {0x9b69dbe1b548ce7dULL, 0x36795041c11ee545ULL}, + {0xc24452da229b021cULL, 0x0417a45231669e97ULL}, + {0xf2d56790ab41c2a3ULL, 0x051d8d66bdc0463cULL}, + {0x97c560ba6b0919a6ULL, 0x2332786036982be5ULL}, + {0xbdb6b8e905cb6010ULL, 0xabff1678443e36dfULL}, + {0xed246723473e3814ULL, 0xd6fedc16554dc497ULL}, + {0x9436c0760c86e30cULL, 0x065f498df5509adeULL}, + {0xb94470938fa89bcfULL, 0x07f71bf172a4c196ULL}, + {0xe7958cb87392c2c3ULL, 0x49f4e2edcf4df1fbULL}, + {0x90bd77f3483bb9baULL, 0x4e390dd4a190b73dULL}, + {0xb4ecd5f01a4aa829ULL, 0xe1c75149c9f4e50cULL}, + {0xe2280b6c20dd5233ULL, 0xda39259c3c721e4fULL}, + {0x8d590723948a5360ULL, 0xa863b781a5c752f1ULL}, + {0xb0af48ec79ace838ULL, 0xd27ca5620f3927aeULL}, + {0xdcdb1b2798182245ULL, 0x071bceba9307719aULL}, + {0x8a08f0f8bf0f156cULL, 0xe47161349be4a700ULL}, + {0xac8b2d36eed2dac6ULL, 0x1d8db981c2ddd0c0ULL}, + {0xd7adf884aa879178ULL, 0xa4f127e2339544f0ULL}, + {0x86ccbb52ea94baebULL, 0x6716b8ed603d4b16ULL}, + {0xa87fea27a539e9a6ULL, 0xc0dc6728b84c9ddbULL}, + {0xd29fe4b18e88640fULL, 0x711380f2e65fc552ULL}, + {0x83a3eeeef9153e8aULL, 0xe6ac3097cffbdb53ULL}, + {0xa48ceaaab75a8e2cULL, 0xa0573cbdc3fad228ULL}, + {0xcdb02555653131b7ULL, 0xc86d0bed34f986b2ULL}, + {0x808e17555f3ebf12ULL, 0x1d442774411bf42fULL}, + {0xa0b19d2ab70e6ed7ULL, 0xa49531515162f13bULL}, + {0xc8de047564d20a8cULL, 0x0dba7da5a5bbad8aULL}, + {0xfb158592be068d2fULL, 0x11291d0f0f2a98edULL}, + {0x9ced737bb6c4183eULL, 0xaab9b229697a9f94ULL}, + {0xc428d05aa4751e4dULL, 0x55681eb3c3d94779ULL}, + {0xf53304714d9265e0ULL, 0x2ac22660b4cf9957ULL}, + {0x993fe2c6d07b7facULL, 0x1ab957fc7101bfd6ULL}, + {0xbf8fdb78849a5f97ULL, 0x2167adfb8d422fccULL}, + {0xef73d256a5c0f77dULL, 0x69c1997a7092bbbfULL}, + {0x95a8637627989aaeULL, 0x2218ffec865bb557ULL}, + {0xbb127c53b17ec15aULL, 0xaa9f3fe7a7f2a2adULL}, + {0xe9d71b689dde71b0ULL, 0x55470fe191ef4b59ULL}, + {0x9226712162ab070eULL, 0x354c69ecfb358f17ULL}, + {0xb6b00d69bb55c8d2ULL, 0xc29f84683a02f2ddULL}, + {0xe45c10c42a2b3b06ULL, 0x734765824883af95ULL}, + {0x8eb98a7a9a5b04e4ULL, 0x880c9f716d524dbdULL}, + {0xb267ed1940f1c61dULL, 0xaa0fc74dc8a6e12cULL}, + {0xdf01e85f912e37a4ULL, 0x9493b9213ad09977ULL}, + {0x8b61313bbabce2c7ULL, 0xdcdc53b4c4c25feaULL}, + {0xae397d8aa96c1b78ULL, 0x541368a1f5f2f7e5ULL}, + {0xd9c7dced53c72256ULL, 0x691842ca736fb5deULL}, + {0x881cea14545c7576ULL, 0x81af29be8825d1abULL}, + {0xaa242499697392d3ULL, 0x221af42e2a2f4616ULL}, + {0xd4ad2dbfc3d07788ULL, 0x6aa1b139b4bb179bULL}, + {0x84ec3c97da624ab5ULL, 0x42a50ec410f4eec1ULL}, + {0xa6274bbdd0fadd62ULL, 0x134e527515322a71ULL}, + {0xcfb11ead453994bbULL, 0x9821e7125a7eb50dULL}, + {0x81ceb32c4b43fcf5ULL, 0x7f15306b788f3128ULL}, + {0xa2425ff75e14fc32ULL, 0x5eda7c8656b2fd72ULL}, + {0xcad2f7f5359a3b3fULL, 0xf6911ba7ec5fbccfULL}, + {0xfd87b5f28300ca0eULL, 0x74356291e777ac03ULL}, + {0x9e74d1b791e07e49ULL, 0x88a15d9b30aacb82ULL}, + {0xc612062576589ddbULL, 0x6ac9b501fcd57e62ULL}, + {0xf79687aed3eec552ULL, 0xc57c22427c0addfbULL}, + {0x9abe14cd44753b53ULL, 0x3b6d95698d86cabdULL}, + {0xc16d9a0095928a28ULL, 0x8a48fac3f0e87d6cULL}, + {0xf1c90080baf72cb2ULL, 0xacdb3974ed229cc7ULL}, + {0x971da05074da7befULL, 0x2c0903e91435a1fcULL}, + {0xbce5086492111aebULL, 0x770b44e359430a7bULL}, + {0xec1e4a7db69561a6ULL, 0xd4ce161c2f93cd1aULL}, + {0x9392ee8e921d5d08ULL, 0xc500cdd19dbc6030ULL}, + {0xb877aa3236a4b44aULL, 0xf6410146052b783dULL}, + {0xe69594bec44de15cULL, 0xb3d141978676564cULL}, + {0x901d7cf73ab0acdaULL, 0xf062c8feb409f5efULL}, + {0xb424dc35095cd810ULL, 0xac7b7b3e610c736bULL}, + {0xe12e13424bb40e14ULL, 0xd79a5a0df94f9046ULL}, + {0x8cbccc096f5088ccULL, 0x06c07848bbd1ba2cULL}, + {0xafebff0bcb24aaffULL, 0x0870965aeac628b7ULL}, + {0xdbe6fecebdedd5bfULL, 0x4a8cbbf1a577b2e4ULL}, + {0x89705f4136b4a598ULL, 0xce97f577076acfcfULL}, + {0xabcc77118461cefdULL, 0x023df2d4c94583c2ULL}, + {0xd6bf94d5e57a42bdULL, 0xc2cd6f89fb96e4b3ULL}, + {0x8637bd05af6c69b6ULL, 0x59c065b63d3e4ef0ULL}, + {0xa7c5ac471b478424ULL, 0xf0307f23cc8de2acULL}, + {0xd1b71758e219652cULL, 0x2c3c9eecbfb15b57ULL}, + {0x83126e978d4fdf3cULL, 0x9ba5e353f7ced916ULL}, + {0xa3d70a3d70a3d70bULL, 0xc28f5c28f5c28f5cULL}, + {0xcccccccccccccccdULL, 0x3333333333333333ULL}, + {0x8000000000000000ULL, 0x0000000000000000ULL}, + {0xa000000000000000ULL, 0x0000000000000000ULL}, + {0xc800000000000000ULL, 0x0000000000000000ULL}, + {0xfa00000000000000ULL, 0x0000000000000000ULL}, + {0x9c40000000000000ULL, 0x0000000000000000ULL}, + {0xc350000000000000ULL, 0x0000000000000000ULL}, + {0xf424000000000000ULL, 0x0000000000000000ULL}, + {0x9896800000000000ULL, 0x0000000000000000ULL}, + {0xbebc200000000000ULL, 0x0000000000000000ULL}, + {0xee6b280000000000ULL, 0x0000000000000000ULL}, + {0x9502f90000000000ULL, 0x0000000000000000ULL}, + {0xba43b74000000000ULL, 0x0000000000000000ULL}, + {0xe8d4a51000000000ULL, 0x0000000000000000ULL}, + {0x9184e72a00000000ULL, 0x0000000000000000ULL}, + {0xb5e620f480000000ULL, 0x0000000000000000ULL}, + {0xe35fa931a0000000ULL, 0x0000000000000000ULL}, + {0x8e1bc9bf04000000ULL, 0x0000000000000000ULL}, + {0xb1a2bc2ec5000000ULL, 0x0000000000000000ULL}, + {0xde0b6b3a76400000ULL, 0x0000000000000000ULL}, + {0x8ac7230489e80000ULL, 0x0000000000000000ULL}, + {0xad78ebc5ac620000ULL, 0x0000000000000000ULL}, + {0xd8d726b7177a8000ULL, 0x0000000000000000ULL}, + {0x878678326eac9000ULL, 0x0000000000000000ULL}, + {0xa968163f0a57b400ULL, 0x0000000000000000ULL}, + {0xd3c21bcecceda100ULL, 0x0000000000000000ULL}, + {0x84595161401484a0ULL, 0x0000000000000000ULL}, + {0xa56fa5b99019a5c8ULL, 0x0000000000000000ULL}, + {0xcecb8f27f4200f3aULL, 0x0000000000000000ULL}, + {0x813f3978f8940985ULL, 0xc000000000000000ULL}, + {0xa18f07d736b90be6ULL, 0xb000000000000000ULL}, + {0xc9f2c9cd04674edfULL, 0x5c00000000000000ULL}, + {0xfc6f7c4045812297ULL, 0xb300000000000000ULL}, + {0x9dc5ada82b70b59eULL, 0x0fe0000000000000ULL}, + {0xc5371912364ce306ULL, 0x93d8000000000000ULL}, + {0xf684df56c3e01bc7ULL, 0x38ce000000000000ULL}, + {0x9a130b963a6c115dULL, 0xc380c00000000000ULL}, + {0xc097ce7bc90715b4ULL, 0xb460f00000000000ULL}, + {0xf0bdc21abb48db21ULL, 0xe1792c0000000000ULL}, + {0x96769950b50d88f5ULL, 0xecebbb8000000000ULL}, + {0xbc143fa4e250eb32ULL, 0xe826aa6000000000ULL}, + {0xeb194f8e1ae525feULL, 0xa23054f800000000ULL}, + {0x92efd1b8d0cf37bfULL, 0xa55e351b00000000ULL}, + {0xb7abc627050305aeULL, 0x0eb5c261c0000000ULL}, + {0xe596b7b0c643c71aULL, 0x926332fa30000000ULL}, + {0x8f7e32ce7bea5c70ULL, 0x1b7dffdc5e000000ULL}, + {0xb35dbf821ae4f38cULL, 0x225d7fd375800000ULL}, + {0xe0352f62a19e306fULL, 0x2af4dfc852e00000ULL}, + {0x8c213d9da502de46ULL, 0xbad90bdd33cc0000ULL}, + {0xaf298d050e4395d7ULL, 0x698f4ed480bf0000ULL}, + {0xdaf3f04651d47b4dULL, 0xc3f32289a0eec000ULL}, + {0x88d8762bf324cd10ULL, 0x5a77f59604953800ULL}, + {0xab0e93b6efee0054ULL, 0x7115f2fb85ba8600ULL}, + {0xd5d238a4abe98069ULL, 0x8d5b6fba67292780ULL}, + {0x85a36366eb71f042ULL, 0xb85925d48079b8b0ULL}, + {0xa70c3c40a64e6c52ULL, 0x666f6f49a09826dcULL}, + {0xd0cf4b50cfe20766ULL, 0x000b4b1c08be3093ULL}, + {0x82818f1281ed44a0ULL, 0x40070ef18576de5bULL}, + {0xa321f2d7226895c8ULL, 0x5008d2ade6d495f2ULL}, + {0xcbea6f8ceb02bb3aULL, 0x640b07596089bb6fULL}, + {0xfee50b7025c36a09ULL, 0xfd0dc92fb8ac2a4bULL}, + {0x9f4f2726179a2246ULL, 0xfe289dbdd36b9a6fULL}, + {0xc722f0ef9d80aad7ULL, 0xbdb2c52d4846810aULL}, + {0xf8ebad2b84e0d58cULL, 0x2d1f76789a58214dULL}, + {0x9b934c3b330c8578ULL, 0x9c33aa0b607714d0ULL}, + {0xc2781f49ffcfa6d6ULL, 0xc340948e3894da04ULL}, + {0xf316271c7fc3908bULL, 0x7410b9b1c6ba1085ULL}, + {0x97edd871cfda3a57ULL, 0x688a740f1c344a53ULL}, + {0xbde94e8e43d0c8edULL, 0xc2ad1112e3415ce8ULL}, + {0xed63a231d4c4fb28ULL, 0xb35855579c11b422ULL}, + {0x945e455f24fb1cf9ULL, 0x70173556c18b1095ULL}, + {0xb975d6b6ee39e437ULL, 0x4c1d02ac71edd4bbULL}, + {0xe7d34c64a9c85d45ULL, 0x9f2443578e6949e9ULL}, + {0x90e40fbeea1d3a4bULL, 0x4376aa16b901ce32ULL}, + {0xb51d13aea4a488deULL, 0x9454549c674241beULL}, + {0xe264589a4dcdab15ULL, 0x396969c38112d22eULL}, + {0x8d7eb76070a08aedULL, 0x03e1e21a30abc35dULL}, + {0xb0de65388cc8ada9ULL, 0xc4da5aa0bcd6b434ULL}, + {0xdd15fe86affad913ULL, 0xb610f148ec0c6141ULL}, + {0x8a2dbf142dfcc7acULL, 0x91ca96cd9387bcc8ULL}, + {0xacb92ed9397bf997ULL, 0xb63d3c80f869abfbULL}, + {0xd7e77a8f87daf7fcULL, 0x23cc8ba1368416f9ULL}, + {0x86f0ac99b4e8dafeULL, 0x965fd744c2128e5cULL}, + {0xa8acd7c0222311bdULL, 0x3bf7cd15f29731f3ULL}, + {0xd2d80db02aabd62cULL, 0x0af5c05b6f3cfe6fULL}, + {0x83c7088e1aab65dcULL, 0x86d9983925861f05ULL}, + {0xa4b8cab1a1563f53ULL, 0xa88ffe476ee7a6c7ULL}, + {0xcde6fd5e09abcf27ULL, 0x12b3fdd94aa19079ULL}, + {0x80b05e5ac60b6179ULL, 0xabb07ea7cea4fa4bULL}, + {0xa0dc75f1778e39d7ULL, 0x969c9e51c24e38deULL}, + {0xc913936dd571c84dULL, 0xfc43c5e632e1c716ULL}, + {0xfb5878494ace3a60ULL, 0xfb54b75fbf9a38dcULL}, + {0x9d174b2dcec0e47cULL, 0x9d14f29bd7c06389ULL}, + {0xc45d1df942711d9bULL, 0xc45a2f42cdb07c6bULL}, + {0xf5746577930d6501ULL, 0x3570bb13811c9b86ULL}, + {0x9968bf6abbe85f21ULL, 0x816674ec30b1e134ULL}, + {0xbfc2ef456ae276e9ULL, 0x61c012273cde5981ULL}, + {0xefb3ab16c59b14a3ULL, 0x3a3016b10c15efe1ULL}, + {0x95d04aee3b80ece6ULL, 0x445e0e2ea78db5edULL}, + {0xbb445da9ca612820ULL, 0xd57591ba51712368ULL}, + {0xea1575143cf97227ULL, 0x0ad2f628e5cd6c42ULL}, + {0x924d692ca61be759ULL, 0xa6c3d9d98fa063a9ULL}, + {0xb6e0c377cfa2e12fULL, 0x9074d04ff3887c93ULL}, + {0xe498f455c38b997bULL, 0xf4920463f06a9bb8ULL}, + {0x8edf98b59a373fedULL, 0xb8db42be7642a153ULL}, + {0xb2977ee300c50fe8ULL, 0xa712136e13d349a8ULL}, + {0xdf3d5e9bc0f653e2ULL, 0xd0d6984998c81c12ULL}, + {0x8b865b215899f46dULL, 0x42861f2dff7d118bULL}, + {0xae67f1e9aec07188ULL, 0x1327a6f97f5c55eeULL}, + {0xda01ee641a708deaULL, 0x17f190b7df336b6aULL}, + {0x884134fe908658b3ULL, 0xcef6fa72eb802322ULL}, + {0xaa51823e34a7eedfULL, 0x42b4b90fa6602beaULL}, + {0xd4e5e2cdc1d1ea97ULL, 0x9361e7538ff836e5ULL}, + {0x850fadc09923329fULL, 0xfc1d309439fb224fULL}, + {0xa6539930bf6bff46ULL, 0x7b247cb94879eae3ULL}, + {0xcfe87f7cef46ff17ULL, 0x19ed9be79a98659cULL}, + {0x81f14fae158c5f6fULL, 0xb0348170c09f3f81ULL}, + {0xa26da3999aef774aULL, 0x1c41a1ccf0c70f62ULL}, + {0xcb090c8001ab551dULL, 0xa3520a402cf8d33aULL}, + {0xfdcb4fa002162a64ULL, 0x8c268cd038370809ULL}, + {0x9e9f11c4014dda7fULL, 0xd798180223226505ULL}, + {0xc646d63501a1511eULL, 0x4d7e1e02abeafe47ULL}, + {0xf7d88bc24209a566ULL, 0xe0dda58356e5bdd9ULL}, + {0x9ae7575969460760ULL, 0xcc8a8772164f96a7ULL}, + {0xc1a12d2fc3978938ULL, 0xffad294e9be37c51ULL}, + {0xf209787bb47d6b85ULL, 0x3f9873a242dc5b65ULL}, + {0x9745eb4d50ce6333ULL, 0x07bf484569c9b91fULL}, + {0xbd176620a501fc00ULL, 0x49af1a56c43c2767ULL}, + {0xec5d3fa8ce427b00ULL, 0x5c1ae0ec754b3141ULL}, + {0x93ba47c980e98ce0ULL, 0x3990cc93c94efec8ULL}, + {0xb8a8d9bbe123f018ULL, 0x47f4ffb8bba2be7bULL}, + {0xe6d3102ad96cec1eULL, 0x59f23fa6ea8b6e1aULL}, + {0x9043ea1ac7e41393ULL, 0x783767c8529724d0ULL}, + {0xb454e4a179dd1878ULL, 0xd64541ba673cee04ULL}, + {0xe16a1dc9d8545e95ULL, 0x0bd69229010c2985ULL}, + {0x8ce2529e2734bb1eULL, 0xe7661b59a0a799f3ULL}, + {0xb01ae745b101e9e5ULL, 0xa13fa23008d18070ULL}, + {0xdc21a1171d42645eULL, 0x898f8abc0b05e08cULL}, + {0x899504ae72497ebbULL, 0x95f9b6b586e3ac57ULL}, + {0xabfa45da0edbde6aULL, 0xfb782462e89c976dULL}, + {0xd6f8d7509292d604ULL, 0xba562d7ba2c3bd49ULL}, + {0x865b86925b9bc5c3ULL, 0xf475dc6d45ba564dULL}, + {0xa7f26836f282b733ULL, 0x719353889728ebe1ULL}, + {0xd1ef0244af236500ULL, 0xcdf8286abcf326d9ULL}, + {0x8335616aed761f20ULL, 0x80bb1942b617f847ULL}, + {0xa402b9c5a8d3a6e8ULL, 0xa0e9df93639df659ULL}, + {0xcd036837130890a2ULL, 0xc92457783c8573f0ULL}, + {0x802221226be55a65ULL, 0x3db6b6ab25d36876ULL}, + {0xa02aa96b06deb0feULL, 0x0d246455ef484293ULL}, + {0xc83553c5c8965d3eULL, 0x906d7d6b6b1a5338ULL}, + {0xfa42a8b73abbf48dULL, 0x3488dcc645e0e806ULL}, + {0x9c69a97284b578d8ULL, 0x00d589fbebac9104ULL}, + {0xc38413cf25e2d70eULL, 0x010aec7ae697b545ULL}, + {0xf46518c2ef5b8cd2ULL, 0x814da799a03da296ULL}, + {0x98bf2f79d5993803ULL, 0x10d088c00426859eULL}, + {0xbeeefb584aff8604ULL, 0x5504aaf005302705ULL}, + {0xeeaaba2e5dbf6785ULL, 0x6a45d5ac067c30c7ULL}, + {0x952ab45cfa97a0b3ULL, 0x226ba58b840d9e7cULL}, + {0xba756174393d88e0ULL, 0x6b068eee6511061bULL}, + {0xe912b9d1478ceb18ULL, 0x85c832a9fe5547a2ULL}, + {0x91abb422ccb812efULL, 0x539d1faa3ef54cc5ULL}, + {0xb616a12b7fe617abULL, 0xa8846794ceb29ff6ULL}, + {0xe39c49765fdf9d95ULL, 0x12a5817a025f47f4ULL}, + {0x8e41ade9fbebc27eULL, 0xeba770ec417b8cf8ULL}, + {0xb1d219647ae6b31dULL, 0xa6914d2751da7037ULL}, + {0xde469fbd99a05fe4ULL, 0x9035a07126510c44ULL}, + {0x8aec23d680043befULL, 0xda218446b7f2a7abULL}, + {0xada72ccc20054aeaULL, 0x50a9e55865ef5195ULL}, + {0xd910f7ff28069da5ULL, 0xe4d45eae7f6b25fbULL}, + {0x87aa9aff79042287ULL, 0x6f04bb2d0fa2f7bdULL}, + {0xa99541bf57452b29ULL, 0xcac5e9f8538bb5acULL}, + {0xd3fa922f2d1675f3ULL, 0xbd776476686ea317ULL}, + {0x847c9b5d7c2e09b8ULL, 0x966a9eca014525eeULL}, + {0xa59bc234db398c26ULL, 0xbc05467c81966f6aULL}, + {0xcf02b2c21207ef2fULL, 0x6b06981ba1fc0b44ULL}, + {0x8161afb94b44f57eULL, 0xe2e41f11453d870aULL}, + {0xa1ba1ba79e1632ddULL, 0x9b9d26d5968ce8cdULL}, + {0xca28a291859bbf94ULL, 0x8284708afc302301ULL}, + {0xfcb2cb35e702af79ULL, 0xa3258cadbb3c2bc1ULL}, + {0x9defbf01b061adacULL, 0xc5f777ec95059b58ULL}, + {0xc56baec21c7a1917ULL, 0xf77555e7ba47022fULL}, + {0xf6c69a72a3989f5cULL, 0x7552ab61a8d8c2baULL}, + {0x9a3c2087a63f639aULL, 0xc953ab1d098779b4ULL}, + {0xc0cb28a98fcf3c80ULL, 0x7ba895e44be95822ULL}, + {0xf0fdf2d3f3c30ba0ULL, 0x9a92bb5d5ee3ae2aULL}, + {0x969eb7c47859e744ULL, 0x609bb51a5b4e4cdaULL}, + {0xbc4665b596706115ULL, 0x78c2a260f221e011ULL}, + {0xeb57ff22fc0c795aULL, 0x56f34af92eaa5815ULL}, + {0x9316ff75dd87cbd9ULL, 0xf6580edbbd2a770dULL}, + {0xb7dcbf5354e9becfULL, 0xf3ee1292ac7514d0ULL}, + {0xe5d3ef282a242e82ULL, 0x70e9973757925a05ULL}, + {0x8fa475791a569d11ULL, 0x0691fe8296bb7843ULL}, + {0xb38d92d760ec4456ULL, 0xc8367e233c6a5653ULL}, + {0xe070f78d3927556bULL, 0x7a441dac0b84ebe8ULL}, + {0x8c469ab843b89563ULL, 0x6c6a928b87331371ULL}, + {0xaf58416654a6babcULL, 0xc785372e68ffd84dULL}, + {0xdb2e51bfe9d0696bULL, 0xf96684fa033fce61ULL}, + {0x88fcf317f22241e3ULL, 0xbbe0131c4207e0fcULL}, + {0xab3c2fddeeaad25bULL, 0x2ad817e35289d93cULL}, + {0xd60b3bd56a5586f2ULL, 0x758e1ddc272c4f8bULL}, + {0x85c7056562757457ULL, 0x0978d2a9987bb1b6ULL}, + {0xa738c6bebb12d16dULL, 0x4bd70753fe9a9e24ULL}, + {0xd106f86e69d785c8ULL, 0x1eccc928fe4145adULL}, + {0x82a45b450226b39dULL, 0x133ffdb99ee8cb8cULL}, + {0xa34d721642b06085ULL, 0xd80ffd2806a2fe6fULL}, + {0xcc20ce9bd35c78a6ULL, 0xce13fc72084bbe0bULL}, + {0xff290242c83396cfULL, 0x8198fb8e8a5ead8eULL}, + {0x9f79a169bd203e42ULL, 0xf0ff9d39167b2c79ULL}, + {0xc75809c42c684dd2ULL, 0xad3f84875c19f797ULL}, + {0xf92e0c3537826146ULL, 0x588f65a93320757dULL}, + {0x9bbcc7a142b17cccULL, 0x77599f89bff4496eULL}, + {0xc2abf989935ddbffULL, 0x9530076c2ff15bcaULL}, + {0xf356f7ebf83552ffULL, 0xfa7c09473bedb2bcULL}, + {0x98165af37b2153dfULL, 0x3c8d85cc85748fb5ULL}, + {0xbe1bf1b059e9a8d7ULL, 0x8bb0e73fa6d1b3a3ULL}, + {0xeda2ee1c7064130dULL, 0xee9d210f9086208cULL}, + {0x9485d4d1c63e8be8ULL, 0x752234a9ba53d457ULL}, + {0xb9a74a0637ce2ee2ULL, 0x926ac1d428e8c96dULL}, + {0xe8111c87c5c1ba9aULL, 0x370572493322fbc8ULL}, + {0x910ab1d4db9914a1ULL, 0xe263676dbff5dd5dULL}, + {0xb54d5e4a127f59c9ULL, 0xdafc41492ff354b4ULL}, + {0xe2a0b5dc971f303bULL, 0xd1bb519b7bf029e2ULL}, + {0x8da471a9de737e25ULL, 0xa31513012d761a2dULL}, + {0xb10d8e1456105daeULL, 0x8bda57c178d3a0b8ULL}, + {0xdd50f1996b947519ULL, 0x2ed0edb1d70888e6ULL}, + {0x8a5296ffe33cc930ULL, 0x7d42948f26655590ULL}, + {0xace73cbfdc0bfb7cULL, 0x9c9339b2effeaaf4ULL}, + {0xd8210befd30efa5bULL, 0xc3b8081fabfe55b1ULL}, + {0x8714a775e3e95c79ULL, 0x9a530513cb7ef58eULL}, + {0xa8d9d1535ce3b397ULL, 0x80e7c658be5eb2f2ULL}, + {0xd31045a8341ca07dULL, 0xe121b7eeedf65fafULL}, + {0x83ea2b892091e44eULL, 0x6cb512f554b9fbcdULL}, + {0xa4e4b66b68b65d61ULL, 0x07e257b2a9e87ac0ULL}, + {0xce1de40642e3f4baULL, 0xc9daed9f54629971ULL}, + {0x80d2ae83e9ce78f4ULL, 0x3e28d48394bd9fe6ULL}, + {0xa1075a24e4421731ULL, 0x4db309a479ed07e0ULL}, + {0xc94930ae1d529cfdULL, 0x211fcc0d986849d8ULL}, + {0xfb9b7cd9a4a7443dULL, 0xe967bf10fe825c4eULL}, + {0x9d412e0806e88aa6ULL, 0x71e0d76a9f1179b1ULL}, + {0xc491798a08a2ad4fULL, 0x0e590d4546d5d81dULL}, + {0xf5b5d7ec8acb58a3ULL, 0x51ef5096988b4e24ULL}, + {0x9991a6f3d6bf1766ULL, 0x5335925e1f5710d6ULL}, + {0xbff610b0cc6edd40ULL, 0xe802f6f5a72cd50cULL}, + {0xeff394dcff8a948fULL, 0x2203b4b310f80a4fULL}, + {0x95f83d0a1fb69cdaULL, 0xb54250efea9b0671ULL}, + {0xbb764c4ca7a44410ULL, 0x6292e52be541c80eULL}, + {0xea53df5fd18d5514ULL, 0x7b379e76de923a12ULL}, + {0x92746b9be2f8552dULL, 0xcd02c30a4b1b644bULL}, + {0xb7118682dbb66a78ULL, 0xc04373ccdde23d5eULL}, + {0xe4d5e82392a40516ULL, 0xf05450c0155accb5ULL}, + {0x8f05b1163ba6832eULL, 0xd634b2780d58bff1ULL}, + {0xb2c71d5bca9023f9ULL, 0x8bc1df1610aeefedULL}, + {0xdf78e4b2bd342cf7ULL, 0x6eb256db94daabe9ULL}, + {0x8bab8eefb6409c1bULL, 0xe52f76493d08ab71ULL}, + {0xae9672aba3d0c321ULL, 0x5e7b53db8c4ad64eULL}, + {0xda3c0f568cc4f3e9ULL, 0x361a28d26f5d8be1ULL}, + {0x8865899617fb1872ULL, 0x81d05983859a776dULL}, + {0xaa7eebfb9df9de8eULL, 0x22446fe467011548ULL}, + {0xd51ea6fa85785632ULL, 0xaad58bdd80c15a9aULL}, + {0x8533285c936b35dfULL, 0x2ac5776a7078d8a0ULL}, + {0xa67ff273b8460357ULL, 0x7576d5450c970ec8ULL}, + {0xd01fef10a657842dULL, 0xd2d48a964fbcd27aULL}, + {0x8213f56a67f6b29cULL, 0x63c4d69df1d6038cULL}, + {0xa298f2c501f45f43ULL, 0x7cb60c456e4b8470ULL}, + {0xcb3f2f7642717714ULL, 0xdbe38f56c9de658cULL}, + {0xfe0efb53d30dd4d8ULL, 0x12dc732c7c55feefULL}, + {0x9ec95d1463e8a507ULL, 0x0bc9c7fbcdb5bf55ULL}, + {0xc67bb4597ce2ce49ULL, 0x4ebc39fac1232f2aULL}, + {0xf81aa16fdc1b81dbULL, 0x226b4879716bfaf5ULL}, + {0x9b10a4e5e9913129ULL, 0x35830d4be6e37cd9ULL}, + {0xc1d4ce1f63f57d73ULL, 0x02e3d09ee09c5c0fULL}, + {0xf24a01a73cf2dcd0ULL, 0x439cc4c698c37313ULL}, + {0x976e41088617ca02ULL, 0x2a41fafc1f7a27ecULL}, + {0xbd49d14aa79dbc83ULL, 0xb4d279bb2758b1e7ULL}, + {0xec9c459d51852ba3ULL, 0x22071829f12ede61ULL}, + {0x93e1ab8252f33b46ULL, 0x35446f1a36bd4afcULL}, + {0xb8da1662e7b00a18ULL, 0xc2958ae0c46c9dbcULL}, + {0xe7109bfba19c0c9eULL, 0xf33aed98f587c52bULL}, + {0x906a617d450187e3ULL, 0xd804d47f9974db3aULL}, + {0xb484f9dc9641e9dbULL, 0x4e06099f7fd21209ULL}, + {0xe1a63853bbd26452ULL, 0xa1878c075fc6968cULL}, + {0x8d07e33455637eb3ULL, 0x24f4b7849bdc1e17ULL}, + {0xb049dc016abc5e60ULL, 0x6e31e565c2d3259dULL}, + {0xdc5c5301c56b75f8ULL, 0x89be5ebf3387ef04ULL}, + {0x89b9b3e11b6329bbULL, 0x5616fb378034f562ULL}, + {0xac2820d9623bf42aULL, 0xab9cba05604232bbULL}, + {0xd732290fbacaf134ULL, 0x5683e886b852bf6aULL}, + {0x867f59a9d4bed6c1ULL, 0xb61271543333b7a2ULL}, + {0xa81f301449ee8c71ULL, 0xa3970da94000a58bULL}, + {0xd226fc195c6a2f8dULL, 0x8c7cd1139000ceeeULL}, + {0x83585d8fd9c25db8ULL, 0x37ce02ac3a008154ULL}, + {0xa42e74f3d032f526ULL, 0x45c183574880a1aaULL}, + {0xcd3a1230c43fb270ULL, 0xd731e42d1aa0ca14ULL}, + {0x80444b5e7aa7cf86ULL, 0x867f2e9c30a47e4cULL}, + {0xa0555e361951c367ULL, 0x281efa433ccd9de0ULL}, + {0xc86ab5c39fa63441ULL, 0x7226b8d40c010558ULL}, + {0xfa856334878fc151ULL, 0x4eb067090f0146aeULL}, + {0x9c935e00d4b9d8d3ULL, 0x912e4065a960cc2cULL}, + {0xc3b8358109e84f08ULL, 0xf579d07f13b8ff37ULL}, + {0xf4a642e14c6262c9ULL, 0x32d8449ed8a73f05ULL}, + {0x98e7e9cccfbd7dbeULL, 0x7fc72ae347688763ULL}, + {0xbf21e44003acdd2dULL, 0x1fb8f59c1942a93cULL}, + {0xeeea5d5004981479ULL, 0xe7a733031f93538bULL}, + {0x95527a5202df0cccULL, 0xf0c87fe1f3bc1437ULL}, + {0xbaa718e68396cffeULL, 0x2cfa9fda70ab1945ULL}, + {0xe950df20247c83feULL, 0xb83947d10cd5df96ULL}, + {0x91d28b7416cdd27fULL, 0xb323cce2a805abbeULL}, + {0xb6472e511c81471eULL, 0x1fecc01b520716adULL}, + {0xe3d8f9e563a198e6ULL, 0xa7e7f0222688dc59ULL}, + {0x8e679c2f5e44ff90ULL, 0xa8f0f615581589b7ULL}, + {0xb201833b35d63f74ULL, 0xd32d339aae1aec25ULL}, + {0xde81e40a034bcf50ULL, 0x07f8808159a1a72eULL}, + {0x8b112e86420f6192ULL, 0x04fb5050d805087dULL}, + {0xadd57a27d29339f7ULL, 0x863a24650e064a9cULL}, + {0xd94ad8b1c7380875ULL, 0xe7c8ad7e5187dd43ULL}, + {0x87cec76f1c830549ULL, 0x70dd6c6ef2f4ea4aULL}, + {0xa9c2794ae3a3c69bULL, 0x4d14c78aafb224ddULL}, + {0xd433179d9c8cb842ULL, 0xa059f96d5b9eae14ULL}, + {0x849feec281d7f329ULL, 0x24383be459432cccULL}, + {0xa5c7ea73224deff4ULL, 0xed464add6f93f7ffULL}, + {0xcf39e50feae16bf0ULL, 0x2897dd94cb78f5ffULL}, + {0x81842f29f2cce376ULL, 0x195eea7cff2b99bfULL}, + {0xa1e53af46f801c54ULL, 0x9fb6a51c3ef6802fULL}, + {0xca5e89b18b602369ULL, 0xc7a44e634eb4203bULL}, + {0xfcf62c1dee382c43ULL, 0xb98d61fc2261284aULL}, + {0x9e19db92b4e31baaULL, 0x93f85d3d957cb92eULL}, + {0xc5a05277621be294ULL, 0x38f6748cfadbe77aULL}, + {0xf70867153aa2db39ULL, 0x473411b03992e158ULL}, + {0x9a65406d44a5c904ULL, 0x8c808b0e23fbccd7ULL}, + {0xc0fe908895cf3b45ULL, 0xafa0add1acfac00dULL}, + {0xf13e34aabb430a16ULL, 0x9b88d94618397010ULL}, + {0x96c6e0eab509e64eULL, 0xa13587cbcf23e60aULL}, + {0xbc789925624c5fe1ULL, 0x4982e9bec2ecdf8dULL}, + {0xeb96bf6ebadf77d9ULL, 0x1be3a42e73a81770ULL}, + {0x933e37a534cbaae8ULL, 0x716e469d08490ea6ULL}, + {0xb80dc58e81fe95a2ULL, 0x8dc9d8444a5b524fULL}, + {0xe61136f2227e3b0aULL, 0x313c4e555cf226e3ULL}, + {0x8fcac257558ee4e7ULL, 0xdec5b0f55a17584eULL}, + {0xb3bd72ed2af29e20ULL, 0x56771d32b09d2e62ULL}, + {0xe0accfa875af45a8ULL, 0x6c14e47f5cc479faULL}, + {0x8c6c01c9498d8b89ULL, 0x438d0ecf99facc3cULL}, + {0xaf87023b9bf0ee6bULL, 0x1470528380797f4bULL}, + {0xdb68c2ca82ed2a06ULL, 0x598c67246097df1eULL}, +}; + +/* ======== Uscale core ======== */ + +static inline int log10_pow2(int x) +{ + return (x * 78913) >> 18; +} + +static inline int log2_pow10(int x) +{ + return (x * 108853) >> 15; +} + +typedef struct { + pow10_entry pm; + int s; +} scaler; + +static inline scaler prescale(int e, int p, int lp) +{ + scaler c; + c.pm = pow10_tab[p - POW10_MIN]; + c.s = -(e + lp + 3); + return c; +} + +typedef uint64_t unrounded; + +static inline uint64_t ur_floor(unrounded u) { return (u + 0) >> 2; } +static inline uint64_t ur_round(unrounded u) { return (u + 1 + ((u >> 2) & 1)) >> 2; } +static inline uint64_t ur_ceil(unrounded u) { return (u + 3) >> 2; } + +static inline unrounded ur_div(unrounded u, uint64_t d) +{ + return (u / d) | (u & 1) | (u % d != 0 ? 1 : 0); +} + +static inline unrounded ur_nudge(unrounded u, int delta) +{ + return u + delta; +} + +static unrounded uscale(uint64_t x, scaler c) +{ + uint64_t hi, mid, mid2, lo_unused; + mul64(x, c.pm.hi, &hi, &mid); + + uint64_t sticky = 1; + + if (c.s >= 64) { + /* x * 10^p < 2^(c.s-64), i.e. < 1 in the unrounded "1.0 = 4" encoding; + rounds to 0 with sticky=1 */ + return sticky; + } + + uint64_t mask = (1ULL << c.s) - 1; + + if ((hi & mask) == 0) { + mul64(x, c.pm.lo, &mid2, &lo_unused); + sticky = (mid - mid2) > 1 ? 1 : 0; + hi -= (mid < mid2) ? 1 : 0; + } + + return (hi >> c.s) | sticky; +} + +static void unpack64(double f, uint64_t *m, int *e) +{ + const int shift = 64 - 53; + const int min_exp = -(1074 + shift); + union { double d; uint64_t u; } u; + u.d = f; + uint64_t bits = u.u; + int exp; + + *m = (1ULL << 63) | ((bits & ((1ULL << 52) - 1)) << shift); + exp = (int)((bits >> 52) & ((1 << shift) - 1)); + + if (exp == 0) { + int s; + *m &= ~(1ULL << 63); + *e = min_exp; + s = clz64(*m); + *m <<= s; + *e -= s; + } + else { + *e = (exp - 1) + min_exp; + } +} + +static double pack64(uint64_t m, int e) +{ + union { double d; uint64_t u; } u; + if ((m & (1ULL << 52)) == 0) { + /* subnormal */ + u.u = m; + } + else { + int biased = 1075 + e; + if (biased >= 2047) { + /* exponent overflow -> infinity */ + u.u = 0x7FF0000000000000ULL; + } + else { + u.u = (m & ~(1ULL << 52)) | ((uint64_t)biased << 52); + } + } + return u.d; +} + +/* ======== Algorithm helpers ======== */ + +static const uint64_t uint64_pow10[20] = { + 1ULL, 10ULL, 100ULL, 1000ULL, 10000ULL, + 100000ULL, 1000000ULL, 10000000ULL, 100000000ULL, 1000000000ULL, + 10000000000ULL, 100000000000ULL, 1000000000000ULL, 10000000000000ULL, + 100000000000000ULL, 1000000000000000ULL, 10000000000000000ULL, + 100000000000000000ULL, 1000000000000000000ULL, 10000000000000000000ULL, +}; + +static int count_digits(uint64_t d) +{ + if (d == 0) return 1; /* clz64(0) is UB; "0" is one digit */ + int nd = log10_pow2(bits_len64(d)); + return nd + (d >= uint64_pow10[nd] ? 1 : 0); +} + +static void fixed_width(double f, int n, uint64_t *d, int *p) +{ + uint64_t m; + int e; + unrounded u; + + unpack64(f, &m, &e); + *p = n - 1 - log10_pow2(e + 63); + u = uscale(m, prescale(e, *p, log2_pow10(*p))); + *d = ur_round(u); + + if (*d >= uint64_pow10[n]) { + *d = ur_round(ur_div(u, 10)); + (*p)--; + } + *p = -(*p); +} + +/* + * skewed returns floor(log10(3/4 * 2^e)) + * Used for shortest-width printing at powers of 2 + */ +static inline int skewed(int e) +{ + return (e * 631305 - 261663) >> 21; +} + +/* + * trim_zeros removes trailing zeros from x * 10^p + */ +static void trim_zeros(uint64_t *x, int *p) +{ + const uint64_t inv5 = 0xcccccccccccccccdULL; + const uint64_t inv5p2 = 0x8f5c28f5c28f5c29ULL; + const uint64_t inv5p4 = 0xd288ce703afb7e91ULL; + const uint64_t inv5p8 = 0xc767074b22e90e21ULL; + uint64_t d; + + /* cut 1 zero, or else return */ + d = (*x * inv5); + d = (d >> 1) | (d << 63); + if (d <= UINT64_MAX / 10) { + *x = d; + (*p)++; + } + else { + return; + } + + /* cut 8 zeros */ + d = (*x * inv5p8); + d = (d >> 8) | (d << 56); + if (d <= UINT64_MAX / 100000000ULL) { + *x = d; + *p += 8; + } + + /* cut 4 zeros */ + d = (*x * inv5p4); + d = (d >> 4) | (d << 60); + if (d <= UINT64_MAX / 10000) { + *x = d; + *p += 4; + } + + /* cut 2 zeros */ + d = (*x * inv5p2); + d = (d >> 2) | (d << 62); + if (d <= UINT64_MAX / 100) { + *x = d; + *p += 2; + } + + /* cut 1 zero */ + d = (*x * inv5); + d = (d >> 1) | (d << 63); + if (d <= UINT64_MAX / 10) { + *x = d; + (*p)++; + } +} + +/* + * shortest computes the shortest formatting of f + * Returns d and p such that d * 10^p equals f when parsed + */ +static void shortest(double f, uint64_t *d, int *p) +{ + const int min_exp = -1085; + uint64_t m, min, max; + int e, z, odd, lp; + scaler pre; + uint64_t dmin, dmax; + + unpack64(f, &m, &e); + z = 11; + + if (m == (1ULL << 63) && e > min_exp) { + *p = -skewed(e + z); + min = m - (1ULL << (z - 2)); + } + else { + if (e < min_exp) { + z = 11 + (min_exp - e); + } + *p = -log10_pow2(e + z); + min = m - (1ULL << (z - 1)); + } + max = m + (1ULL << (z - 1)); + odd = (int)((m >> z) & 1); + + lp = log2_pow10(*p); + pre = prescale(e, *p, lp); + + dmin = ur_ceil(ur_nudge(uscale(min, pre), +odd)); + dmax = ur_floor(ur_nudge(uscale(max, pre), -odd)); + + /* check if a multiple of 10 is in range */ + *d = dmax / 10; + if (*d * 10 >= dmin) { + int new_p = -(*p - 1); + trim_zeros(d, &new_p); + *p = new_p; + return; + } + + /* multiple valid values: pick the rounded one */ + if (dmin < dmax) { + *d = ur_round(uscale(m, pre)); + } + else { + *d = dmin; + } + *p = -(*p); +} + +static double parse_decimal(uint64_t d, int p) +{ + int b, lp, e; + unrounded u; + uint64_t m; + + if (d == 0) return 0.0; + + b = bits_len64(d); + lp = log2_pow10(p); + e = 53 - b - lp; + if (e > 1074) e = 1074; + + u = uscale(d << (64 - b), prescale(e - (64 - b), p, lp)); + m = ur_round(u); + + if (m >= (1ULL << 53)) { + u = (u >> 1) | (u & 1); + m = ur_round(u); + e--; + } + + return pack64(m, -e); +} + +/* ======== Formatting helpers ======== */ + +static const char i2a[] = + "00010203040506070809" + "10111213141516171819" + "20212223242526272829" + "30313233343536373839" + "40414243444546474849" + "50515253545556575859" + "60616263646566676869" + "70717273747576777879" + "80818283848586878889" + "90919293949596979899"; + +static void format_base10(char *buf, int nd, uint64_t u) +{ + int idx; + while (nd >= 2) { + nd -= 2; + idx = (int)(u % 100) * 2; + u /= 100; + buf[nd] = i2a[idx]; + buf[nd + 1] = i2a[idx + 1]; + } + if (nd > 0) { + buf[0] = '0' + (char)u; + } +} + +static int +emit_exp(char *s, char e_char, int exp) +{ + char *p = s; + *p++ = e_char; + if (exp < 0) { + *p++ = '-'; + exp = -exp; + } + else { + *p++ = '+'; + } + if (exp >= 100) { + *p++ = '0' + exp / 100; + exp %= 100; + } + *p++ = '0' + exp / 10; + *p++ = '0' + exp % 10; + return (int)(p - s); +} + +/* ======== mrb_format_float ======== */ + +#ifdef MRB_USE_FLOAT32 +#define FLT_MIN_BUF_SIZE 6 +#else +#define FLT_MIN_BUF_SIZE 7 +#endif + +int +mrb_format_float(mrb_float f, char *buf, size_t buf_size, char fmt, int prec, char sign) +{ + char *s = buf; + int buf_remaining = (int)buf_size - 1; + int alt_form = 0; + char e_char; + + if ((uint8_t)fmt & 0x80) { + fmt &= 0x7f; + alt_form = 1; + } + if (buf_size <= (size_t)FLT_MIN_BUF_SIZE) { + if (buf_size >= 2) *s++ = '?'; + if (buf_size >= 1) *s = '\0'; + return buf_size >= 2; + } + if (signbit(f)) { + *s++ = '-'; + f = -f; + } + else if (sign) { + *s++ = sign; + } + buf_remaining -= (int)(s - buf); + + { + char uc = fmt & 0x20; + if (isinf(f)) { + *s++ = 'I' ^ uc; + *s++ = 'N' ^ uc; + *s++ = 'F' ^ uc; + goto done; + } + if (isnan(f)) { + *s++ = 'N' ^ uc; + *s++ = 'A' ^ uc; + *s++ = 'N' ^ uc; + goto done; + } + } + + { + int use_shortest = (prec == -2); + if (prec < 0) prec = 6; + e_char = 'E' | (fmt & 0x20); + fmt |= 0x20; + if (fmt == 'g') { + if (use_shortest) fmt = 'S'; /* shortest mode */ + else if (prec == 0) prec = 1; + } + } + + if (f == 0.0) { + /* zero */ + if (fmt == 'e') { + *s++ = '0'; + if (prec > 0 || alt_form) { + int i; + if (prec > buf_remaining - 5) prec = buf_remaining - 5; + *s++ = '.'; + for (i = 0; i < prec; i++) *s++ = '0'; + } + s += emit_exp(s, e_char, 0); + } + else if (fmt == 'f') { + int i; + if (prec > buf_remaining - 2) prec = buf_remaining - 2; + *s++ = '0'; + if (prec > 0 || alt_form) { + *s++ = '.'; + for (i = 0; i < prec; i++) *s++ = '0'; + } + } + else { /* g */ + *s++ = '0'; + if (alt_form && prec > 1) { + int i; + *s++ = '.'; + for (i = 1; i < prec; i++) *s++ = '0'; + } + } + } + else { + /* nonzero finite */ + uint64_t d; + int p, nd, exp; + char digs[20] = {0}; /* gcc -Wmaybe-uninitialized false positive: count_digits() always returns >= 1 */ + + if (fmt == 'S') { + /* shortest representation for to_s */ + int i; + shortest((double)f, &d, &p); + nd = count_digits(d); + exp = p + nd - 1; + format_base10(digs, nd, d); + +#ifdef MRB_USE_FLOAT32 + if (exp < -4 || exp >= 7) { +#else + if (exp < -4 || exp >= 15) { +#endif + /* e format */ + *s++ = digs[0]; + if (nd > 1) { + *s++ = '.'; + for (i = 1; i < nd; i++) *s++ = digs[i]; + } + s += emit_exp(s, e_char, exp); + } + else { + /* f format */ + if (exp < 0) { + *s++ = '0'; + *s++ = '.'; + for (i = 0; i < -(exp + 1); i++) *s++ = '0'; + for (i = 0; i < nd; i++) *s++ = digs[i]; + } + else { + for (i = 0; i <= exp && i < nd; i++) *s++ = digs[i]; + for (; i <= exp; i++) *s++ = '0'; + if (nd > exp + 1) { + *s++ = '.'; + for (i = exp + 1; i < nd; i++) *s++ = digs[i]; + } + } + } + } + else if (fmt == 'g') { + /* g/G format */ + int fprec, n = prec; + if (n > 18) n = 18; + if (n < 1) n = 1; + fixed_width((double)f, n, &d, &p); + nd = count_digits(d); + exp = p + nd - 1; + + if (exp < -4 || exp >= prec) { + /* use e format with prec-1 fractional digits */ + fprec = prec - 1; + format_base10(digs, nd, d); + *s++ = digs[0]; + if (fprec > 0 || alt_form) { + int i, end = fprec < nd - 1 ? fprec : nd - 1; + *s++ = '.'; + for (i = 0; i < end; i++) *s++ = digs[1 + i]; + for (; i < fprec; i++) *s++ = '0'; + } + if (!alt_form) { + /* strip trailing zeros */ + while (s > buf && s[-1] == '0') s--; + if (s > buf && s[-1] == '.') s--; + } + s += emit_exp(s, e_char, exp); + } + else { + /* use f format with prec-(exp+1) fractional digits */ + int i; + fprec = prec - (exp + 1); + format_base10(digs, nd, d); + /* integer part */ + if (exp < 0) { + *s++ = '0'; + } + else { + for (i = 0; i <= exp && i < nd; i++) *s++ = digs[i]; + for (; i <= exp; i++) *s++ = '0'; + } + if (fprec > 0 || alt_form) { + int frac_avail; + *s++ = '.'; + if (exp < 0) { + int zeros = -(exp + 1); + for (i = 0; i < zeros && i < fprec; i++) *s++ = '0'; + frac_avail = fprec - zeros; + for (i = 0; i < frac_avail && i < nd; i++) *s++ = digs[i]; + for (; i < frac_avail; i++) *s++ = '0'; + } + else { + frac_avail = nd - exp - 1; + if (frac_avail < 0) frac_avail = 0; + for (i = 0; i < frac_avail && i < fprec; i++) *s++ = digs[exp + 1 + i]; + for (; i < fprec; i++) *s++ = '0'; + } + } + if (!alt_form && fprec > 0) { + while (s[-1] == '0') s--; + if (s[-1] == '.') s--; + } + } + } + else if (fmt == 'e') { + /* e/E format */ + int n = prec + 1; + int i; + if (n > 18) n = 18; + if (n < 1) n = 1; + fixed_width((double)f, n, &d, &p); + nd = count_digits(d); + exp = p + nd - 1; + format_base10(digs, nd, d); + *s++ = digs[0]; + if (prec > 0 || alt_form) { + *s++ = '.'; + for (i = 1; i < nd; i++) *s++ = digs[i]; + for (i = nd - 1; i < prec; i++) *s++ = '0'; + } + s += emit_exp(s, e_char, exp); + } + else { + /* f/F format */ + int exp_est, i; + union { double d; uint64_t u; } uf; + uf.d = (double)f; + exp_est = log10_pow2((int)((uf.u >> 52) & 0x7FF) - 1023); + + if (exp_est >= buf_remaining) { + /* too big for f, use e */ + int n = prec + 1; + if (n > 18) n = 18; + if (n < 1) n = 1; + fixed_width((double)f, n, &d, &p); + nd = count_digits(d); + exp = p + nd - 1; + format_base10(digs, nd, d); + *s++ = digs[0]; + if (prec > 0 || alt_form) { + *s++ = '.'; + for (i = 1; i < nd; i++) *s++ = digs[i]; + for (i = nd - 1; i < prec; i++) *s++ = '0'; + } + s += emit_exp(s, e_char, exp); + } + else { + if ((exp_est + prec + 2) > buf_remaining) { + prec = buf_remaining - exp_est - 2; + if (prec < 0) prec = 0; + } + + if (exp_est + prec <= 17) { + /* Direct uscale: compute round(f * 10^prec) */ + uint64_t m; + int e, lp; + unrounded u; + unpack64((double)f, &m, &e); + lp = log2_pow10(prec); + u = uscale(m, prescale(e, prec, lp)); + d = ur_round(u); + nd = count_digits(d); + exp = nd - prec - 1; + } + else { + /* Large number: use fixed_width, pad with zeros */ + int n = 18; + fixed_width((double)f, n, &d, &p); + nd = count_digits(d); + exp = p + nd - 1; + } + format_base10(digs, nd, d); + + if (exp >= 0) { + for (i = 0; i < nd && i <= exp; i++) *s++ = digs[i]; + for (; i <= exp; i++) *s++ = '0'; + if (prec > 0 || alt_form) { + int frac_avail = nd - exp - 1; + if (frac_avail < 0) frac_avail = 0; + *s++ = '.'; + for (i = 0; i < frac_avail && i < prec; i++) *s++ = digs[exp + 1 + i]; + for (; i < prec; i++) *s++ = '0'; + } + } + else { + int zeros, frac_avail; + *s++ = '0'; + if (prec > 0 || alt_form) { + *s++ = '.'; + zeros = -(exp + 1); + for (i = 0; i < zeros && i < prec; i++) *s++ = '0'; + frac_avail = prec - zeros; + for (i = 0; i < frac_avail && i < nd; i++) *s++ = digs[i]; + for (; i < frac_avail; i++) *s++ = '0'; + } + } + } + } + } + +done: + *s = '\0'; + return (int)(s - buf); +} + +/* ======== mrb_read_float ======== */ + +MRB_API mrb_bool +mrb_read_float(const char *str, char **endp, double *fp) +{ + const char *p = str; + const char *a = p; + uint64_t d = 0; + int nd = 0; + int dp = 0; + int trunc = 0; + int sign = 1; + int any_digits = 0; + + while (ISSPACE((unsigned char)*p)) p++; + + if (*p == '-') { sign = -1; p++; } + else if (*p == '+') p++; + + /* skip leading zeros */ + while (*p == '0') { p++; any_digits = 1; } + + /* integer part */ + while (ISDIGIT(*p)) { + if (nd < 19) { + d = d * 10 + (*p - '0'); + nd++; + } + else { + trunc++; + } + any_digits = 1; + a = ++p; + } + + /* fractional part */ + if (*p == '.') { + p++; + if (nd == 0) { + while (*p == '0') { dp++; p++; any_digits = 1; } + } + while (ISDIGIT(*p)) { + if (nd < 19) { + d = d * 10 + (*p - '0'); + nd++; + dp++; + } + any_digits = 1; + p++; + } + a = p; + } + + if (!any_digits) { + if (endp) *endp = (char*)str; + *fp = 0.0; + return FALSE; + } + + /* exponent (optional). On malformed exponent ("5e", "5e+"), keep `a` + pointing at the 'e' so the caller's *endp reflects where parsing + stopped, and fall through using the mantissa-only result. */ + int final_p = trunc - dp; + if ((*p | 32) == 'e') { + int e = 0; + int exp_sign = 1; + p++; + if (*p == '-') { exp_sign = -1; p++; } + else if (*p == '+') p++; + + if (ISDIGIT(*p)) { + while (ISDIGIT(*p)) { + if (e < 10000) e = e * 10 + (*p - '0'); + p++; + } + final_p += e * exp_sign; + a = p; + } + } + + { + double res; + if (d == 0) { + res = 0.0; + } + else if (final_p > 308) { + res = HUGE_VAL; + } + else if (final_p < POW10_MIN) { + res = 0.0; + } + else { + res = parse_decimal(d, final_p); + } + if (sign < 0) res = -res; + *fp = res; + } + + if (endp) *endp = (char*)a; + return TRUE; +} + +#endif /* MRB_NO_FLOAT */ diff --git a/src/gc.c b/src/gc.c index 37bb04e064..f8021c6b16 100644 --- a/src/gc.c +++ b/src/gc.c @@ -224,6 +224,17 @@ mrb_realloc_simple(mrb_state *mrb, void *p, size_t len) p2 = mrb_basic_alloc_func(p, len); } + if (p2 && len > 0) { + mrb->gc.malloc_increase += len; + if (mrb->gc.malloc_threshold > 0 && + mrb->gc.malloc_increase >= mrb->gc.malloc_threshold && + mrb->gc.state == MRB_GC_STATE_ROOT && + !mrb->gc.disabled && !mrb->gc.iterating) { + mrb->gc.malloc_increase = 0; + mrb_incremental_gc(mrb); + } + } + return p2; } @@ -552,42 +563,19 @@ mrb_gc_unregister(mrb_state *mrb, mrb_value obj) ARY_SET_LEN(a, w); } -MRB_API struct RBasic* -mrb_obj_alloc(mrb_state *mrb, enum mrb_vtype ttype, struct RClass *cls) +/* Core allocation without type validation. + Used internally by mrb_proc_new, mrb_env_new, etc. */ +struct RBasic* +mrb_obj_alloc_core(mrb_state *mrb, enum mrb_vtype ttype, struct RClass *cls) { static const RVALUE RVALUE_zero = { { { NULL, MRB_TT_FALSE } } }; mrb_gc *gc = &mrb->gc; - if (cls) { - enum mrb_vtype tt; - - switch (cls->tt) { - case MRB_TT_CLASS: - case MRB_TT_SCLASS: - case MRB_TT_MODULE: - case MRB_TT_ENV: - break; - default: - mrb_raise(mrb, E_TYPE_ERROR, "allocation failure"); - } - tt = MRB_INSTANCE_TT(cls); - if (ttype != MRB_TT_SCLASS && - ttype != MRB_TT_ICLASS && - ttype != MRB_TT_ENV && - ttype != MRB_TT_BIGINT && - ttype != tt && - !(cls == mrb->object_class && (ttype == MRB_TT_CPTR || ttype == MRB_TT_CDATA || ttype == MRB_TT_ISTRUCT))) { - mrb_raisef(mrb, E_TYPE_ERROR, "allocation failure of %C", cls); - } - } - if (ttype <= MRB_TT_FREE) { - mrb_raisef(mrb, E_TYPE_ERROR, "allocation failure of %C (type %d)", cls, (int)ttype); - } - #ifdef MRB_GC_STRESS mrb_full_gc(mrb); #endif - if (gc->threshold < gc->live) { + gc->gc_debt++; + if (gc->gc_debt > 0) { mrb_incremental_gc(mrb); } gc_arena_keep(mrb, gc); @@ -613,6 +601,37 @@ mrb_obj_alloc(mrb_state *mrb, enum mrb_vtype ttype, struct RClass *cls) return &p->as.basic; } +MRB_API struct RBasic* +mrb_obj_alloc(mrb_state *mrb, enum mrb_vtype ttype, struct RClass *cls) +{ + if (cls) { + enum mrb_vtype tt; + + switch (cls->tt) { + case MRB_TT_CLASS: + case MRB_TT_SCLASS: + case MRB_TT_MODULE: + case MRB_TT_ENV: + break; + default: + mrb_raise(mrb, E_TYPE_ERROR, "allocation failure"); + } + tt = MRB_INSTANCE_TT(cls); + if (ttype != MRB_TT_SCLASS && + ttype != MRB_TT_ICLASS && + ttype != MRB_TT_ENV && + ttype != MRB_TT_BIGINT && + ttype != tt && + !(cls == mrb->object_class && (ttype == MRB_TT_CPTR || ttype == MRB_TT_CDATA || ttype == MRB_TT_ISTRUCT))) { + mrb_raisef(mrb, E_TYPE_ERROR, "allocation failure of %C", cls); + } + } + if (ttype <= MRB_TT_FREE) { + mrb_raisef(mrb, E_TYPE_ERROR, "allocation failure of %C (type %d)", cls, (int)ttype); + } + return mrb_obj_alloc_core(mrb, ttype, cls); +} + static inline void add_gray_list(mrb_gc *gc, struct RBasic *obj) { @@ -844,6 +863,31 @@ mrb_gc_mark(mrb_state *mrb, struct RBasic *obj) if (!is_white(obj)) return; if (is_red(obj)) return; mrb_assert((obj)->tt != MRB_TT_FREE); + switch (obj->tt) { + case MRB_TT_STRING: + /* most strings have no children; handle fshared inline */ + paint_black(obj); + mrb_gc_mark(mrb, (struct RBasic*)obj->c); + if (RSTR_FSHARED_P(obj)) { + struct RString *s = (struct RString*)obj; + mrb_gc_mark(mrb, (struct RBasic*)s->as.heap.aux.fshared); + } + return; + case MRB_TT_INTEGER: + case MRB_TT_CPTR: +#ifdef MRB_USE_BIGINT + case MRB_TT_BIGINT: +#endif +#ifdef MRB_USE_COMPLEX + case MRB_TT_COMPLEX: +#endif + /* leaf types: no children besides class */ + paint_black(obj); + mrb_gc_mark(mrb, (struct RBasic*)obj->c); + return; + default: + break; + } add_gray_list(&mrb->gc, obj); } @@ -1172,36 +1216,33 @@ incremental_sweep_phase(mrb_state *mrb, mrb_gc *gc, size_t limit) size_t tried_sweep = 0; while (page && (tried_sweep < limit)) { - RVALUE *p = page->objects; - RVALUE *e = p + MRB_HEAP_PAGE_SIZE; size_t freed = 0; mrb_bool dead_slot = TRUE; if (is_minor_gc(gc) && page->old) { /* skip a slot which doesn't contain any young object */ - p = e; dead_slot = FALSE; } - while (pas.basic)) { - if (p->as.basic.tt != MRB_TT_FREE) { - obj_free(mrb, &p->as.basic, FALSE); - if (p->as.basic.tt == MRB_TT_FREE) { + else { + RVALUE *p = page->objects; + RVALUE *e = p + MRB_HEAP_PAGE_SIZE; + while (pas.basic)) { + if (p->as.basic.tt != MRB_TT_FREE) { + obj_free(mrb, &p->as.basic, FALSE); + mrb_assert(p->as.basic.tt == MRB_TT_FREE); p->as.free.next = page->freelist; page->freelist = p; freed++; } - else { - dead_slot = FALSE; - } } + else { + if (!is_generational(gc)) + paint_partial_white(gc, &p->as.basic); /* next gc target */ + dead_slot = FALSE; + } + p++; } - else { - if (!is_generational(gc)) - paint_partial_white(gc, &p->as.basic); /* next gc target */ - dead_slot = FALSE; - } - p++; } /* free dead slot */ @@ -1286,13 +1327,16 @@ incremental_gc_step(mrb_state *mrb, mrb_gc *gc) { size_t limit = 0, result = 0; limit = (GC_STEP_SIZE/100) * gc->step_ratio; + if (gc->step_limit > 0 && limit > gc->step_limit) { + limit = gc->step_limit; + } while (result < limit) { result += incremental_gc(mrb, gc, limit); if (gc->state == MRB_GC_STATE_ROOT) break; } - gc->threshold = gc->live + GC_STEP_SIZE; + gc->gc_debt -= (mrb_int)result; } static void @@ -1322,17 +1366,30 @@ mrb_incremental_gc(mrb_state *mrb) if (gc->disabled || gc->iterating) return; if (is_minor_gc(gc)) { +#ifdef MRB_GC_STATS + gc->gc_total_count++; + gc->minor_gc_count++; +#endif incremental_gc_finish(mrb, gc); } else { +#ifdef MRB_GC_STATS + if (gc->state == MRB_GC_STATE_ROOT) { + gc->gc_total_count++; + gc->major_gc_count++; + } +#endif incremental_gc_step(mrb, gc); } if (gc->state == MRB_GC_STATE_ROOT) { + gc->malloc_increase = 0; mrb_assert(gc->live >= gc->live_after_mark); - gc->threshold = (gc->live_after_mark/100) * gc->interval_ratio; - if (gc->threshold < GC_STEP_SIZE) { - gc->threshold = GC_STEP_SIZE; + { + mrb_int credit = (mrb_int)((gc->live_after_mark/100) * gc->interval_ratio) + - (mrb_int)gc->live_after_mark; + if (credit < (mrb_int)GC_STEP_SIZE) credit = (mrb_int)GC_STEP_SIZE; + gc->gc_debt = -credit; } if (is_major_gc(gc)) { @@ -1364,6 +1421,10 @@ mrb_full_gc(mrb_state *mrb) if (!mrb->c) return; if (gc->disabled || gc->iterating) return; +#ifdef MRB_GC_STATS + gc->gc_total_count++; + gc->major_gc_count++; +#endif if (is_generational(gc)) { /* clear all the old objects back to young */ clear_all_old(mrb, gc); @@ -1375,7 +1436,12 @@ mrb_full_gc(mrb_state *mrb) } incremental_gc_finish(mrb, gc); - gc->threshold = (gc->live_after_mark/100) * gc->interval_ratio; + { + mrb_int credit = (mrb_int)((gc->live_after_mark/100) * gc->interval_ratio) + - (mrb_int)gc->live_after_mark; + if (credit < (mrb_int)GC_STEP_SIZE) credit = (mrb_int)GC_STEP_SIZE; + gc->gc_debt = -credit; + } if (is_generational(gc)) { gc->oldgen_threshold = gc->live_after_mark/100 * MAJOR_GC_INC_RATIO; @@ -1573,6 +1639,44 @@ gc_step_ratio_set(mrb_state *mrb, mrb_value obj) return mrb_nil_value(); } +static mrb_value +gc_step_limit_get(mrb_state *mrb, mrb_value obj) +{ + return mrb_int_value(mrb, (mrb_int)mrb->gc.step_limit); +} + +static mrb_value +gc_step_limit_set(mrb_state *mrb, mrb_value obj) +{ + mrb_int limit; + + mrb_get_args(mrb, "i", &limit); + if (limit < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "step_limit must be non-negative"); + } + mrb->gc.step_limit = (size_t)limit; + return mrb_int_value(mrb, limit); +} + +static mrb_value +gc_malloc_threshold_get(mrb_state *mrb, mrb_value obj) +{ + return mrb_int_value(mrb, (mrb_int)mrb->gc.malloc_threshold); +} + +static mrb_value +gc_malloc_threshold_set(mrb_state *mrb, mrb_value obj) +{ + mrb_int threshold; + + mrb_get_args(mrb, "i", &threshold); + if (threshold < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "malloc_threshold must be non-negative"); + } + mrb->gc.malloc_threshold = (size_t)threshold; + return mrb_int_value(mrb, threshold); +} + static void change_gen_gc_mode(mrb_state *mrb, mrb_gc *gc, mrb_bool enable) { @@ -1649,11 +1753,8 @@ gc_each_objects(mrb_state *mrb, mrb_gc *gc, mrb_each_object_callback *callback, void mrb_objspace_each_objects(mrb_state *mrb, mrb_each_object_callback *callback, void *data) { - mrb_bool iterating = mrb->gc.iterating; - mrb_full_gc(mrb); - mrb->gc.iterating = TRUE; - if (iterating) { + if (mrb->gc.iterating) { gc_each_objects(mrb, &mrb->gc, callback, data); } else { @@ -1662,11 +1763,12 @@ mrb_objspace_each_objects(mrb_state *mrb, mrb_each_object_callback *callback, vo MRB_TRY(&c_jmp) { mrb->jmp = &c_jmp; + mrb->gc.iterating = TRUE; gc_each_objects(mrb, &mrb->gc, callback, data); mrb->jmp = prev_jmp; - mrb->gc.iterating = iterating; + mrb->gc.iterating = FALSE; } MRB_CATCH(&c_jmp) { - mrb->gc.iterating = iterating; + mrb->gc.iterating = FALSE; mrb->jmp = prev_jmp; MRB_THROW(prev_jmp); } MRB_END_EXC(&c_jmp); @@ -1680,6 +1782,43 @@ mrb_objspace_page_slot_size(void) } +/* + * call-seq: + * GC.stat -> Hash + * + * Returns a Hash with GC statistics. + * Keys: :live, :debt, :state, :generational, :full, + * :step_limit, :malloc_increase, :malloc_threshold + * With MRB_GC_STATS: :total, :minor, :major + * + */ + +static mrb_value +gc_stat(mrb_state *mrb, mrb_value self) +{ + mrb_gc *gc = &mrb->gc; + mrb_value hash = mrb_hash_new_capa(mrb, 8); + + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(live)), mrb_int_value(mrb, (mrb_int)gc->live)); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(debt)), mrb_int_value(mrb, gc->gc_debt)); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(state)), mrb_int_value(mrb, (mrb_int)gc->state)); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(generational)), mrb_bool_value(gc->generational)); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(full)), mrb_bool_value(gc->full)); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(step_limit)), mrb_int_value(mrb, (mrb_int)gc->step_limit)); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(malloc_increase)), mrb_int_value(mrb, (mrb_int)gc->malloc_increase)); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(malloc_threshold)), mrb_int_value(mrb, (mrb_int)gc->malloc_threshold)); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(symbol_count)), mrb_int_value(mrb, (mrb_int)(MRB_PRESYM_MAX + mrb->symidx))); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(dynamic_symbol_count)), mrb_int_value(mrb, (mrb_int)mrb->dynamic_sym_count)); + +#ifdef MRB_GC_STATS + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(total)), mrb_int_value(mrb, (mrb_int)gc->gc_total_count)); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(minor)), mrb_int_value(mrb, (mrb_int)gc->minor_gc_count)); + mrb_hash_set(mrb, hash, mrb_symbol_value(MRB_SYM(major)), mrb_int_value(mrb, (mrb_int)gc->major_gc_count)); +#endif + + return hash; +} + void mrb_init_gc(mrb_state *mrb) { @@ -1695,6 +1834,7 @@ mrb_init_gc(mrb_state *mrb) gc = mrb_define_module_id(mrb, MRB_SYM(GC)); + mrb_define_class_method_id(mrb, gc, MRB_SYM(stat), gc_stat, MRB_ARGS_NONE()); mrb_define_class_method_id(mrb, gc, MRB_SYM(start), gc_start, MRB_ARGS_NONE()); mrb_define_class_method_id(mrb, gc, MRB_SYM(enable), gc_enable, MRB_ARGS_NONE()); mrb_define_class_method_id(mrb, gc, MRB_SYM(disable), gc_disable, MRB_ARGS_NONE()); @@ -1702,6 +1842,10 @@ mrb_init_gc(mrb_state *mrb) mrb_define_class_method_id(mrb, gc, MRB_SYM_E(interval_ratio), gc_interval_ratio_set, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, gc, MRB_SYM(step_ratio), gc_step_ratio_get, MRB_ARGS_NONE()); mrb_define_class_method_id(mrb, gc, MRB_SYM_E(step_ratio), gc_step_ratio_set, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, gc, MRB_SYM(step_limit), gc_step_limit_get, MRB_ARGS_NONE()); + mrb_define_class_method_id(mrb, gc, MRB_SYM_E(step_limit), gc_step_limit_set, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, gc, MRB_SYM(malloc_threshold), gc_malloc_threshold_get, MRB_ARGS_NONE()); + mrb_define_class_method_id(mrb, gc, MRB_SYM_E(malloc_threshold), gc_malloc_threshold_set, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, gc, MRB_SYM_E(generational_mode), gc_generational_mode_set, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, gc, MRB_SYM(generational_mode), gc_generational_mode_get, MRB_ARGS_NONE()); } diff --git a/src/hash.c b/src/hash.c index 1dba928be0..3d82df6f0b 100644 --- a/src/hash.c +++ b/src/hash.c @@ -315,9 +315,8 @@ h_check_modified_validate(mrb_state *mrb, struct h_check_modified *checker, stru static uint32_t float_hash_code(mrb_float f) { - if (f == 0.0) return 0; /* normalize -0.0 to 0.0 */ - if (f == -0.0) f = 0.0; + if (f == 0.0) f = 0.0; return mrb_byte_hash((const uint8_t*)&f, sizeof(f)); } #endif @@ -361,7 +360,10 @@ mrb_obj_hash_code(mrb_state *mrb, mrb_value key) hash_code = U32(tt) ^ U32(mrb_integer(hash_code_obj)); break; } - return hash_code ^ (hash_code << 2) ^ (hash_code >> 2); + hash_code ^= hash_code >> 16; + hash_code *= 0x45d9f3b; + hash_code ^= hash_code >> 16; + return hash_code; } static uint32_t @@ -1266,7 +1268,7 @@ hash_default(mrb_state *mrb, mrb_value hash, mrb_value key) { if (MRB_RHASH_DEFAULT_P(hash)) { if (MRB_RHASH_PROCDEFAULT_P(hash)) { - return mrb_funcall_id(mrb, RHASH_PROCDEFAULT(hash), MRB_SYM(call), 2, hash, key); + return mrb_funcall_argv2(mrb, RHASH_PROCDEFAULT(hash), MRB_SYM(call), hash, key); } else { return RHASH_IFNONE(hash); @@ -1543,7 +1545,7 @@ mrb_hash_default(mrb_state *mrb, mrb_value hash) if (MRB_RHASH_DEFAULT_P(hash)) { if (MRB_RHASH_PROCDEFAULT_P(hash)) { if (!given) return mrb_nil_value(); - return mrb_funcall_id(mrb, RHASH_PROCDEFAULT(hash), MRB_SYM(call), 2, hash, key); + return mrb_funcall_argv2(mrb, RHASH_PROCDEFAULT(hash), MRB_SYM(call), hash, key); } else { return RHASH_IFNONE(hash); diff --git a/src/init.c b/src/init.c index 83706fa8b8..53e29a6a5c 100644 --- a/src/init.c +++ b/src/init.c @@ -6,45 +6,32 @@ #include -void mrb_init_symtbl(mrb_state*); -void mrb_init_class(mrb_state*); -void mrb_init_object(mrb_state*); -void mrb_init_kernel(mrb_state*); -void mrb_init_enumerable(mrb_state*); -void mrb_init_symbol(mrb_state*); -void mrb_init_string(mrb_state*); -void mrb_init_exception(mrb_state*); -void mrb_init_proc(mrb_state*); -void mrb_init_array(mrb_state*); -void mrb_init_hash(mrb_state*); -void mrb_init_numeric(mrb_state*); -void mrb_init_range(mrb_state*); -void mrb_init_gc(mrb_state*); -void mrb_init_math(mrb_state*); -void mrb_init_version(mrb_state*); -void mrb_init_mrblib(mrb_state*); +#define INIT_FUNC_FOREACH(def) \ + def(mrb_init_symtbl) \ + def(mrb_init_class) \ + def(mrb_init_object) \ + def(mrb_init_kernel) \ + def(mrb_init_enumerable) \ + def(mrb_init_symbol) \ + def(mrb_init_string) \ + def(mrb_init_exception) \ + def(mrb_init_proc) \ + def(mrb_init_array) \ + def(mrb_init_hash) \ + def(mrb_init_numeric) \ + def(mrb_init_range) \ + def(mrb_init_gc) \ + def(mrb_init_version) \ + def(mrb_init_mrblib) + +#define INIT_FUNC_DECLS(func) void func(mrb_state*); +INIT_FUNC_FOREACH(INIT_FUNC_DECLS) #define DONE mrb_gc_arena_restore(mrb, 0); void mrb_init_core(mrb_state *mrb) { - mrb_init_symtbl(mrb); DONE; - - mrb_init_class(mrb); DONE; - mrb_init_object(mrb); DONE; - mrb_init_kernel(mrb); DONE; - mrb_init_enumerable(mrb); DONE; - - mrb_init_symbol(mrb); DONE; - mrb_init_string(mrb); DONE; - mrb_init_exception(mrb); DONE; - mrb_init_proc(mrb); DONE; - mrb_init_array(mrb); DONE; - mrb_init_hash(mrb); DONE; - mrb_init_numeric(mrb); DONE; - mrb_init_range(mrb); DONE; - mrb_init_gc(mrb); DONE; - mrb_init_version(mrb); DONE; - mrb_init_mrblib(mrb); DONE; +#define INIT_FUNC_CALL(func) func(mrb); DONE; + INIT_FUNC_FOREACH(INIT_FUNC_CALL) } #undef DONE diff --git a/src/kernel.c b/src/kernel.c index 315124cba6..98be70cd5b 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -597,7 +597,7 @@ obj_respond_to(mrb_state *mrb, mrb_value self) if (!respond_to_p) { mrb_sym rtm_id = MRB_SYM_Q(respond_to_missing); if (!mrb_func_basic_p(mrb, self, rtm_id, mrb_false)) { - mrb_value v = mrb_funcall_id(mrb, self, rtm_id, 2, mrb_symbol_value(id), mrb_bool_value(priv)); + mrb_value v = mrb_funcall_argv2(mrb, self, rtm_id, mrb_symbol_value(id), mrb_bool_value(priv)); return mrb_bool_value(mrb_bool(v)); } } diff --git a/src/numeric.c b/src/numeric.c index 7c6b08469f..a6a59e96bd 100644 --- a/src/numeric.c +++ b/src/numeric.c @@ -351,6 +351,14 @@ flo_idiv(mrb_state *mrb, mrb_value xv) mrb_float x = mrb_float(xv); mrb_check_num_exact(mrb, x); mrb_int y = mrb_as_int(mrb, mrb_get_arg1(mrb)); + /* (mrb_int)x is UB when x is outside mrb_int range. */ + if (!FIXABLE_FLOAT(x)) { +#ifdef MRB_USE_BIGINT + return mrb_bint_div(mrb, mrb_bint_new_float(mrb, x), mrb_int_value(mrb, y)); +#else + mrb_int_overflow(mrb, "div"); +#endif + } return mrb_div_int_value(mrb, (mrb_int)x, y); } @@ -418,13 +426,8 @@ mrb_value mrb_float_to_str(mrb_state *mrb, mrb_value flo, const char *fmt) { char buf[25]; -#ifdef MRB_USE_FLOAT32 - const int prec = 7; -#else - const int prec = 15; -#endif - mrb_format_float(mrb_float(flo), buf, sizeof(buf), 'g', prec, '\0'); + mrb_format_float(mrb_float(flo), buf, sizeof(buf), 'g', -2, '\0'); for (char *p = buf; *p; p++) { if (*p == '.') goto exit; if (*p == 'e') { @@ -1349,6 +1352,7 @@ int_rev(mrb_state *mrb, mrb_value num) } #define bit_op(x,y,op1,op2) do {\ + if (!mrb_integer_p(y)) mrb_int_noconv(mrb, y);\ return mrb_int_value(mrb, (mrb_integer(x) op2 mrb_integer(y)));\ } while(0) diff --git a/src/proc.c b/src/proc.c index 546d949076..93f070b9be 100644 --- a/src/proc.c +++ b/src/proc.c @@ -47,7 +47,7 @@ mrb_proc_new(mrb_state *mrb, const mrb_irep *irep) struct RProc *p; mrb_callinfo *ci = mrb->c->ci; - p = MRB_OBJ_ALLOC(mrb, MRB_TT_PROC, mrb->proc_class); + p = (struct RProc*)mrb_obj_alloc_core(mrb, MRB_TT_PROC, mrb->proc_class); if (ci) { struct RClass *tc = NULL; @@ -76,7 +76,7 @@ mrb_env_new(mrb_state *mrb, struct mrb_context *c, mrb_callinfo *ci, int nstacks int n = ci->n; int nk = ci->nk; - e = MRB_OBJ_ALLOC(mrb, MRB_TT_ENV, NULL); + e = (struct REnv*)mrb_obj_alloc_core(mrb, MRB_TT_ENV, NULL); e->c = tc; MRB_ENV_SET_LEN(e, nstacks); bidx += (n == 15) ? 1 : n; @@ -322,6 +322,14 @@ mrb_proc_init_copy(mrb_state *mrb, mrb_value self) check_proc(mrb, proc); mrb_proc_copy(mrb, mrb_proc_ptr(self), mrb_proc_ptr(proc)); + /* A copied Proc is always treated as an orphan block: it cannot + `break` / `return` from the original yielding method. This is + stricter than CRuby (which only marks the copy orphan once the + original becomes orphan), but matches mruby's memory-first + design — tracking the original via a back pointer would grow + RProc and the GC mark set. See limitations.md for the spec + divergence note. */ + mrb_proc_ptr(self)->flags |= MRB_PROC_ORPHAN; return self; } @@ -411,8 +419,28 @@ mrb_proc_arity(const struct RProc *p) mrb_aspec aspec; int ma, op, ra, pa, arity; + /* Resolve alias procs first: an alias carries body.mid (a symbol), not an + irep, with `upper` pointing at the original proc. Without this the irep + branch below reads body.mid as an mrb_irep* and dereferences it -> SEGV. + Mirrors the guard already present in mrb_proc_source_location / mrb_proc_eql. */ + while (p && MRB_PROC_ALIAS_P(p)) { + p = p->upper; + } + if (!p) return 0; + if (MRB_PROC_CFUNC_P(p)) { - /* TODO cfunc aspec not implemented yet */ + uint32_t caspec_bits = p->flags & MRB_PROC_CASPEC_MASK; + if (caspec_bits != 0) { + aspec = mrb_proc_decompress_caspec(caspec_bits); + ma = MRB_ASPEC_REQ(aspec); + op = MRB_ASPEC_OPT(aspec); + ra = MRB_ASPEC_REST(aspec); + pa = MRB_ASPEC_POST(aspec); + return ra || op ? -(ma + pa + 1) : ma + pa; + } + if (MRB_PROC_NOARG_P(p)) { + return 0; + } return -1; } diff --git a/src/readfloat.c b/src/readfloat.c deleted file mode 100644 index 6c0d9aec60..0000000000 --- a/src/readfloat.c +++ /dev/null @@ -1,228 +0,0 @@ -#include - -#ifndef MRB_NO_FLOAT - -#include -#include -#include - -// Powers of 10 lookup table for better performance and accuracy -static const double pow10_positive[] = { - 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, - 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, - 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, - 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39, - 1e40, 1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47, 1e48, 1e49, - 1e50, 1e51, 1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59, - 1e60, 1e61, 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69, - 1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, 1e79, - 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87, 1e88, 1e89, - 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, 1e98, 1e99, - 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109, - 1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119, - 1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129, - 1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139, - 1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149, - 1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159, - 1e160, 1e161, 1e162, 1e163, 1e164, 1e165, 1e166, 1e167, 1e168, 1e169, - 1e170, 1e171, 1e172, 1e173, 1e174, 1e175, 1e176, 1e177, 1e178, 1e179, - 1e180, 1e181, 1e182, 1e183, 1e184, 1e185, 1e186, 1e187, 1e188, 1e189, - 1e190, 1e191, 1e192, 1e193, 1e194, 1e195, 1e196, 1e197, 1e198, 1e199, - 1e200, 1e201, 1e202, 1e203, 1e204, 1e205, 1e206, 1e207, 1e208, 1e209, - 1e210, 1e211, 1e212, 1e213, 1e214, 1e215, 1e216, 1e217, 1e218, 1e219, - 1e220, 1e221, 1e222, 1e223, 1e224, 1e225, 1e226, 1e227, 1e228, 1e229, - 1e230, 1e231, 1e232, 1e233, 1e234, 1e235, 1e236, 1e237, 1e238, 1e239, - 1e240, 1e241, 1e242, 1e243, 1e244, 1e245, 1e246, 1e247, 1e248, 1e249, - 1e250, 1e251, 1e252, 1e253, 1e254, 1e255, 1e256, 1e257, 1e258, 1e259, - 1e260, 1e261, 1e262, 1e263, 1e264, 1e265, 1e266, 1e267, 1e268, 1e269, - 1e270, 1e271, 1e272, 1e273, 1e274, 1e275, 1e276, 1e277, 1e278, 1e279, - 1e280, 1e281, 1e282, 1e283, 1e284, 1e285, 1e286, 1e287, 1e288, 1e289, - 1e290, 1e291, 1e292, 1e293, 1e294, 1e295, 1e296, 1e297, 1e298, 1e299, - 1e300, 1e301, 1e302, 1e303, 1e304, 1e305, 1e306, 1e307, 1e308 -}; - -static const double pow10_negative[] = { - 1e0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8, 1e-9, - 1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16, 1e-17, 1e-18, 1e-19, - 1e-20, 1e-21, 1e-22, 1e-23, 1e-24, 1e-25, 1e-26, 1e-27, 1e-28, 1e-29, - 1e-30, 1e-31, 1e-32, 1e-33, 1e-34, 1e-35, 1e-36, 1e-37, 1e-38, 1e-39, - 1e-40, 1e-41, 1e-42, 1e-43, 1e-44, 1e-45, 1e-46, 1e-47, 1e-48, 1e-49, - 1e-50, 1e-51, 1e-52, 1e-53, 1e-54, 1e-55, 1e-56, 1e-57, 1e-58, 1e-59, - 1e-60, 1e-61, 1e-62, 1e-63, 1e-64, 1e-65, 1e-66, 1e-67, 1e-68, 1e-69, - 1e-70, 1e-71, 1e-72, 1e-73, 1e-74, 1e-75, 1e-76, 1e-77, 1e-78, 1e-79, - 1e-80, 1e-81, 1e-82, 1e-83, 1e-84, 1e-85, 1e-86, 1e-87, 1e-88, 1e-89, - 1e-90, 1e-91, 1e-92, 1e-93, 1e-94, 1e-95, 1e-96, 1e-97, 1e-98, 1e-99, - 1e-100, 1e-101, 1e-102, 1e-103, 1e-104, 1e-105, 1e-106, 1e-107, 1e-108, 1e-109, - 1e-110, 1e-111, 1e-112, 1e-113, 1e-114, 1e-115, 1e-116, 1e-117, 1e-118, 1e-119, - 1e-120, 1e-121, 1e-122, 1e-123, 1e-124, 1e-125, 1e-126, 1e-127, 1e-128, 1e-129, - 1e-130, 1e-131, 1e-132, 1e-133, 1e-134, 1e-135, 1e-136, 1e-137, 1e-138, 1e-139, - 1e-140, 1e-141, 1e-142, 1e-143, 1e-144, 1e-145, 1e-146, 1e-147, 1e-148, 1e-149, - 1e-150, 1e-151, 1e-152, 1e-153, 1e-154, 1e-155, 1e-156, 1e-157, 1e-158, 1e-159, - 1e-160, 1e-161, 1e-162, 1e-163, 1e-164, 1e-165, 1e-166, 1e-167, 1e-168, 1e-169, - 1e-170, 1e-171, 1e-172, 1e-173, 1e-174, 1e-175, 1e-176, 1e-177, 1e-178, 1e-179, - 1e-180, 1e-181, 1e-182, 1e-183, 1e-184, 1e-185, 1e-186, 1e-187, 1e-188, 1e-189, - 1e-190, 1e-191, 1e-192, 1e-193, 1e-194, 1e-195, 1e-196, 1e-197, 1e-198, 1e-199, - 1e-200, 1e-201, 1e-202, 1e-203, 1e-204, 1e-205, 1e-206, 1e-207, 1e-208, 1e-209, - 1e-210, 1e-211, 1e-212, 1e-213, 1e-214, 1e-215, 1e-216, 1e-217, 1e-218, 1e-219, - 1e-220, 1e-221, 1e-222, 1e-223, 1e-224, 1e-225, 1e-226, 1e-227, 1e-228, 1e-229, - 1e-230, 1e-231, 1e-232, 1e-233, 1e-234, 1e-235, 1e-236, 1e-237, 1e-238, 1e-239, - 1e-240, 1e-241, 1e-242, 1e-243, 1e-244, 1e-245, 1e-246, 1e-247, 1e-248, 1e-249, - 1e-250, 1e-251, 1e-252, 1e-253, 1e-254, 1e-255, 1e-256, 1e-257, 1e-258, 1e-259, - 1e-260, 1e-261, 1e-262, 1e-263, 1e-264, 1e-265, 1e-266, 1e-267, 1e-268, 1e-269, - 1e-270, 1e-271, 1e-272, 1e-273, 1e-274, 1e-275, 1e-276, 1e-277, 1e-278, 1e-279, - 1e-280, 1e-281, 1e-282, 1e-283, 1e-284, 1e-285, 1e-286, 1e-287, 1e-288, 1e-289, - 1e-290, 1e-291, 1e-292, 1e-293, 1e-294, 1e-295, 1e-296, 1e-297, 1e-298, 1e-299, - 1e-300, 1e-301, 1e-302, 1e-303, 1e-304, 1e-305, 1e-306, 1e-307, 1e-308, 1e-309, - 1e-310, 1e-311, 1e-312, 1e-313, 1e-314, 1e-315, 1e-316, 1e-317, 1e-318, 1e-319, - 1e-320, 1e-321, 1e-322, 1e-323 -}; - -#define POW10_POSITIVE_SIZE (sizeof(pow10_positive) / sizeof(pow10_positive[0])) -#define POW10_NEGATIVE_SIZE (sizeof(pow10_negative) / sizeof(pow10_negative[0])) - -static double -mrb_pow10(int exp) -{ - if (exp >= 0) { - if (exp < (int)POW10_POSITIVE_SIZE) { - return pow10_positive[exp]; - } - return HUGE_VAL; - } - else { - exp = -exp; - if (exp < (int)POW10_NEGATIVE_SIZE) { - return pow10_negative[exp]; - } - return 0.0; - } -} - -/* -** Parses a string representation of a floating-point number. -** -** @param str The input string to parse. -** @param endp A pointer to a char* that will be updated to point to the -** character in str after the last character used in the -** conversion. Can be NULL. -** @param fp A pointer to a double that will be set to the parsed -** floating-point number. -** @return TRUE if a float is successfully parsed, FALSE otherwise. -*/ -MRB_API mrb_bool -mrb_read_float(const char *str, char **endp, double *fp) -{ - const char *p = str; - const char *a = p; - uint64_t int_part = 0; - uint64_t frac_part = 0; - int frac_digits = 0; - int sign = 1; - int digits = 0; - int overflow = 0; - - // Skip whitespace - while (ISSPACE((unsigned char)*p)) p++; - - // Handle sign - if (*p == '-') { sign = -1; p++; } - else if (*p == '+') p++; - - // Parse integer part using integer arithmetic for better accuracy - while (ISDIGIT(*p)) { - if (int_part > (UINT64_MAX - 9) / 10) { - overflow = 1; - // Continue parsing to find the end - while (ISDIGIT(*p)) p++; - break; - } - int_part = int_part * 10 + (*p - '0'); - digits++; - a = ++p; - } - - // Parse fractional part - if (*p == '.') { - p++; - while (ISDIGIT(*p) && frac_digits < 17) { // Limit precision to avoid overflow - frac_part = frac_part * 10 + (*p - '0'); - frac_digits++; - digits++; - p++; - } - // Skip remaining fractional digits if any - while (ISDIGIT(*p)) p++; - a = p; - } - - // If no digits were found, return FALSE - if (digits == 0) { - if (endp) *endp = (char*)str; - *fp = 0.0; - return FALSE; - } - - double res; - if (overflow) { - // For overflow case, fall back to floating point parsing - res = (double)int_part; - } - else { - // Fast path: combine integer and fractional parts - res = (double)int_part; - if (frac_digits > 0) { - res += (double)frac_part * mrb_pow10(-frac_digits); - } - } - - // Handle exponent - if ((*p | 32) == 'e') { - int e = 0; - int exp_sign = 1; - p++; - if (*p == '-') { exp_sign = -1; p++; } - else if (*p == '+') p++; - - // If no digits follow 'e', ignore the exponent part - if (!ISDIGIT(*p)) goto done; - - while (ISDIGIT(*p)) { - if (e < 10000) { // Prevent integer overflow - e = e * 10 + (*p - '0'); - } - p++; - } - e *= exp_sign; - - // Apply exponent directly - let mrb_pow10 handle overflow - // Large exponents will return HUGE_VAL (infinity) or 0.0 as appropriate - - res *= mrb_pow10(e); - a = p; - } - - // Apply sign - res *= sign; - - // Set endp - done: - if (endp) *endp = (char*)a; - *fp = res; - - // strtod(3) stores ERANGE to errno for overflow/underflow - // mruby does not require those checks -#if 0 - // Check for underflow after applying the exponent - if (res != 0.0 && fabs(res) < DBL_MIN) { - return FALSE; - } - - // Check if the result is infinity or NaN - if (isinf(res) || isnan(res)) { - return FALSE; - } -#endif - return TRUE; -} - -#endif diff --git a/src/string.c b/src/string.c index d490ec310c..900ce079aa 100644 --- a/src/string.c +++ b/src/string.c @@ -1358,6 +1358,16 @@ mrb_str_dup(mrb_state *mrb, mrb_value str) return str_replace(mrb, dup, s); } +MRB_API mrb_value +mrb_str_dup_frozen(mrb_state *mrb, mrb_value str) +{ + if (!mrb_frozen_p(mrb_basic_ptr(str))) { + str = mrb_str_dup(mrb, str); + mrb_basic_ptr(str)->frozen = TRUE; + } + return str; +} + enum str_convert_range { /* `beg` and `len` are byte unit in `0 ... str.bytesize` */ STR_BYTE_RANGE_CORRECTED = 1, @@ -2014,18 +2024,18 @@ mrb_byte_hash_step(const uint8_t *s, mrb_int len, uint32_t hval) const uint8_t *send = s + len; /* - * FNV-1 hash each octet in the buffer + * FNV-1a hash each octet in the buffer */ while (s < send) { + /* xor the bottom with the current octet */ + hval ^= (uint32_t)*s++; + /* multiply by the 32-bit FNV magic prime mod 2^32 */ #if defined(NO_FNV_GCC_OPTIMIZATION) hval *= FNV_32_PRIME; #else hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); #endif - - /* xor the bottom with the current octet */ - hval ^= (uint32_t)*s++; } /* return our new hash value */ diff --git a/src/symbol.c b/src/symbol.c index f24987e17c..d769e33783 100644 --- a/src/symbol.c +++ b/src/symbol.c @@ -6,9 +6,14 @@ #include #include +#include #include #include #include +#include +#include +#include +#include #include #ifndef MRB_PRESYM_SCANNING @@ -48,6 +53,14 @@ presym_sym2name(mrb_sym sym, mrb_int *lenp) /* ------------------------------------------------------ */ +/* Per-symbol flags (stored in mrb->sym_flags[]) */ +#define SYM_FL_DYNAMIC 0x01 /* created at runtime (to_sym, send, etc.) */ +#define SYM_FL_MARK 0x02 /* marked during symbol GC */ + +#if MRB_SYMBOL_MAX > 0 +static void mrb_symbol_gc(mrb_state *mrb); +#endif + /* LSB pointer tagging for literal flags */ #define SYMTBL_LITERAL_FLAG ((uintptr_t)1) @@ -176,6 +189,7 @@ static mrb_bool sym_check(mrb_state *mrb, const char *name, size_t len, mrb_sym i) { const char *tagged_ptr = mrb->symtbl[i]; + if (tagged_ptr == NULL) return FALSE; /* tombstone (freed by symbol GC) */ const char *symname = symtbl_get_ptr(tagged_ptr); /* Untag for access */ size_t symlen; @@ -198,6 +212,7 @@ find_symbol_linear(mrb_state *mrb, const char *name, size_t len) mrb_sym i; for (i = 1; i <= mrb->symidx; i++) { + if (mrb->symtbl[i] == NULL) continue; /* skip tombstones */ if (sym_check(mrb, name, len, i)) { return (i + MRB_PRESYM_MAX); } @@ -318,6 +333,8 @@ sym_intern_common(mrb_state *mrb, const char *name, size_t len, mrb_bool lit) if (symcapa == 0) symcapa = 100; else symcapa = (size_t)(symcapa * 6 / 5); mrb->symtbl = (const char**)mrb_realloc(mrb, (void*)mrb->symtbl, sizeof(char*)*symcapa); + mrb->sym_flags = (uint8_t*)mrb_realloc(mrb, mrb->sym_flags, symcapa); + memset(mrb->sym_flags + mrb->symcapa, 0, symcapa - mrb->symcapa); if (using_hash_table(mrb)) { struct mrb_sym_hash_table *ht = mrb->symhash; ht->symlink = (uint8_t*)mrb_realloc(mrb, ht->symlink, symcapa); @@ -335,10 +352,17 @@ sym_intern_common(mrb_state *mrb, const char *name, size_t len, mrb_bool lit) } else { heap_allocation:; - /* Always heap-allocate when not explicitly literal */ uint32_t ulen = (uint32_t)len; size_t ilen = mrb_packed_int_len(ulen); - char *p = sym_pool_alloc(mrb, len+ilen+1); + char *p; + if (lit) { + /* Static symbol from unaligned literal: use pool (not individually freeable) */ + p = sym_pool_alloc(mrb, len+ilen+1); + } + else { + /* Dynamic symbol: use individual malloc (freeable by symbol GC) */ + p = (char*)mrb_malloc(mrb, len+ilen+1); + } mrb_packed_int_encode(ulen, (uint8_t*)p); memcpy(p+ilen, name, len); p[ilen+len] = 0; @@ -346,6 +370,13 @@ sym_intern_common(mrb_state *mrb, const char *name, size_t len, mrb_bool lit) } mrb->symidx = sym; + if (!lit) { + mrb->sym_flags[sym] = SYM_FL_DYNAMIC; + mrb->dynamic_sym_count++; + } + else { + mrb->sym_flags[sym] = 0; + } return sym; } @@ -387,6 +418,18 @@ sym_intern(mrb_state *mrb, const char *name, size_t len, mrb_bool lit) sym = find_symbol(mrb, name, len, NULL); if (sym > 0) return sym; +#if MRB_SYMBOL_MAX > 0 + if (!lit && mrb->dynamic_sym_count >= MRB_SYMBOL_MAX) { + mrb_symbol_gc(mrb); + /* re-check: the symbol might have been reclaimed and re-interned */ + sym = find_symbol(mrb, name, len, NULL); + if (sym > 0) return sym; + if (mrb->dynamic_sym_count >= MRB_SYMBOL_MAX) { + mrb_raise(mrb, E_RUNTIME_ERROR, "symbol table overflow"); + } + } +#endif + /* Check if we need to migrate to hash table */ if (!using_hash_table(mrb) && mrb->symidx >= MRB_SYMBOL_LINEAR_THRESHOLD) { migrate_to_hash_table(mrb); @@ -586,6 +629,7 @@ sym2name_len(mrb_state *mrb, mrb_sym sym, char *buf, mrb_int *lenp) } const char *tagged_ptr = mrb->symtbl[sym]; + if (tagged_ptr == NULL) goto outofsym; /* tombstone (freed by symbol GC) */ const char *symname = symtbl_get_ptr(tagged_ptr); /* Untag for access */ if (!symtbl_is_literal(tagged_ptr)) { @@ -621,10 +665,217 @@ mrb_sym_name_len(mrb_state *mrb, mrb_sym sym, mrb_int *lenp) #endif } +/* + * Symbol GC: mark and sweep unreferenced dynamic symbols. + * Called lazily when dynamic symbol count reaches MRB_SYMBOL_MAX. + */ +#if MRB_SYMBOL_MAX > 0 + +/* Mark a runtime symbol as live (skip presym/inline) */ +static void +sym_gc_mark(mrb_state *mrb, mrb_sym sym) +{ + if (sym == 0) return; + if (SYMBOL_INLINE_P(sym)) return; + if (sym <= MRB_PRESYM_MAX) return; + mrb_sym idx = sym - MRB_PRESYM_MAX; + if (idx > mrb->symidx) return; + mrb->sym_flags[idx] |= SYM_FL_MARK; +} + +/* Callback: mark symbols in method table keys */ +static int +sym_gc_mark_mt(mrb_state *mrb, mrb_sym sym, mrb_method_t m, void *p) +{ + sym_gc_mark(mrb, sym); + return 0; +} + +/* Callback: mark symbols in IV table keys */ +static int +sym_gc_mark_iv(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) +{ + sym_gc_mark(mrb, sym); + /* also mark symbol values stored in IV tables */ + if (mrb_symbol_p(v)) { + sym_gc_mark(mrb, mrb_symbol(v)); + } + return 0; +} + +/* Callback: mark symbols in hash keys and values */ +static int +sym_gc_mark_hash_entry(mrb_state *mrb, mrb_value key, mrb_value val, void *p) +{ + if (mrb_symbol_p(key)) sym_gc_mark(mrb, mrb_symbol(key)); + if (mrb_symbol_p(val)) sym_gc_mark(mrb, mrb_symbol(val)); + return 0; +} + +/* Mark symbols from a single object */ +static int +sym_gc_mark_object(mrb_state *mrb, struct RBasic *obj, void *data) +{ + if (mrb_object_dead_p(mrb, obj)) return MRB_EACH_OBJ_OK; + + switch (obj->tt) { + case MRB_TT_CLASS: + case MRB_TT_MODULE: + case MRB_TT_SCLASS: + mrb_mt_foreach(mrb, (struct RClass*)obj, sym_gc_mark_mt, NULL); + /* fall through for IV */ + case MRB_TT_OBJECT: + case MRB_TT_EXCEPTION: + case MRB_TT_CDATA: + mrb_iv_foreach(mrb, mrb_obj_value(obj), sym_gc_mark_iv, NULL); + break; + case MRB_TT_ICLASS: + if (MRB_FLAG_TEST(obj, MRB_FL_CLASS_IS_ORIGIN)) { + mrb_mt_foreach(mrb, (struct RClass*)obj, sym_gc_mark_mt, NULL); + } + break; + case MRB_TT_ENV: + { + struct REnv *e = (struct REnv*)obj; + sym_gc_mark(mrb, e->mid); + mrb_int len = MRB_ENV_LEN(e); + for (mrb_int i = 0; i < len; i++) { + if (mrb_symbol_p(e->stack[i])) { + sym_gc_mark(mrb, mrb_symbol(e->stack[i])); + } + } + } + break; + case MRB_TT_STRUCT: + case MRB_TT_ARRAY: + { + struct RArray *a = (struct RArray*)obj; + mrb_int len = ARY_LEN(a); + const mrb_value *p = ARY_PTR(a); + for (mrb_int i = 0; i < len; i++) { + if (mrb_symbol_p(p[i])) { + sym_gc_mark(mrb, mrb_symbol(p[i])); + } + } + } + break; + case MRB_TT_HASH: + mrb_iv_foreach(mrb, mrb_obj_value(obj), sym_gc_mark_iv, NULL); + mrb_hash_foreach(mrb, (struct RHash*)obj, sym_gc_mark_hash_entry, NULL); + break; + default: + break; + } + return MRB_EACH_OBJ_OK; +} + +/* Mark symbols from VM stack and callinfo */ +static void +sym_gc_mark_context(mrb_state *mrb, struct mrb_context *c) +{ + if (!c || !c->stbase) return; + + /* Mark symbols on value stack */ + mrb_value *stend = c->ci ? c->ci->stack + mrb_ci_nregs(c->ci) : c->stbase; + if (stend > c->stend) stend = c->stend; + for (mrb_value *v = c->stbase; v < stend; v++) { + if (mrb_symbol_p(*v)) { + sym_gc_mark(mrb, mrb_symbol(*v)); + } + } + + /* Mark method IDs in call stack */ + if (c->cibase) { + for (mrb_callinfo *ci = c->cibase; ci <= c->ci; ci++) { + sym_gc_mark(mrb, ci->mid); + } + } +} + +static void +mrb_symbol_gc(mrb_state *mrb) +{ + static mrb_bool in_symbol_gc = FALSE; + mrb_sym i; + + if (mrb->symidx == 0) return; + if (in_symbol_gc) return; /* prevent recursive invocation */ + in_symbol_gc = TRUE; + + /* Phase 1: clear marks on all dynamic symbols */ + for (i = 1; i <= mrb->symidx; i++) { + mrb->sym_flags[i] &= ~SYM_FL_MARK; + } + + /* Phase 2: mark symbols referenced from all objects */ + /* Note: mrb_objspace_each_objects runs full GC first, then iterates */ + mrb_objspace_each_objects(mrb, sym_gc_mark_object, NULL); + + /* Mark symbols from root context */ + sym_gc_mark_context(mrb, mrb->root_c); + if (mrb->c != mrb->root_c) { + sym_gc_mark_context(mrb, mrb->c); + } + + /* Mark symbols from global variable table */ + if (mrb->globals) { + mrb_iv_foreach(mrb, mrb_obj_value(mrb->object_class), sym_gc_mark_iv, NULL); + } + + /* Phase 3: sweep unmarked dynamic symbols */ + mrb_sym freed = 0; + for (i = 1; i <= mrb->symidx; i++) { + if ((mrb->sym_flags[i] & SYM_FL_DYNAMIC) && + !(mrb->sym_flags[i] & SYM_FL_MARK)) { + /* Free individually-allocated string */ + mrb_free(mrb, (void*)mrb->symtbl[i]); + mrb->symtbl[i] = NULL; /* tombstone */ + mrb->sym_flags[i] = 0; + freed++; + } + } + mrb->dynamic_sym_count -= freed; + + /* Phase 4: rebuild hash table if in hash mode (chains may be broken) */ + if (freed > 0 && using_hash_table(mrb)) { + struct mrb_sym_hash_table *ht = mrb->symhash; + memset(ht->buckets, 0, sizeof(ht->buckets)); + memset(ht->symlink, 0, mrb->symcapa); + for (i = 1; i <= mrb->symidx; i++) { + if (mrb->symtbl[i] == NULL) continue; /* skip tombstones */ + const char *name = symtbl_get_ptr(mrb->symtbl[i]); + size_t len; + if (symtbl_is_literal(mrb->symtbl[i])) { + len = strlen(name); + } + else { + len = mrb_packed_int_decode((const uint8_t*)name, (const uint8_t**)&name); + } + uint8_t hash = mrb_byte_hash((const uint8_t*)name, len); + if (ht->buckets[hash] != 0) { + mrb_sym diff = i - ht->buckets[hash]; + ht->symlink[i] = (diff > 0xff) ? 0xff : (uint8_t)diff; + } + ht->buckets[hash] = i; + } + } + in_symbol_gc = FALSE; +} +#endif /* MRB_SYMBOL_MAX > 0 */ + void mrb_free_symtbl(mrb_state *mrb) { - /* Free symbol string pool chunks */ + /* Free individually-allocated dynamic symbol strings */ + if (mrb->sym_flags) { + for (mrb_sym i = 1; i <= mrb->symidx; i++) { + if ((mrb->sym_flags[i] & SYM_FL_DYNAMIC) && mrb->symtbl[i] != NULL) { + mrb_free(mrb, (void*)mrb->symtbl[i]); + } + } + } + + /* Free symbol string pool chunks (static symbols) */ struct sym_pool_chunk *chunk = (struct sym_pool_chunk*)mrb->sym_pool; while (chunk) { struct sym_pool_chunk *next = chunk->next; @@ -634,6 +885,7 @@ mrb_free_symtbl(mrb_state *mrb) mrb->sym_pool = NULL; mrb_free(mrb, (void*)mrb->symtbl); + mrb_free(mrb, mrb->sym_flags); /* Free hash table if allocated */ if (mrb->symhash) { diff --git a/src/variable.c b/src/variable.c index 45e4e02ea6..401692b19c 100644 --- a/src/variable.c +++ b/src/variable.c @@ -665,7 +665,8 @@ assign_class_name(mrb_state *mrb, struct RObject *obj, mrb_sym sym, mrb_value v) { if (namespace_p(mrb_type(v))) { struct RObject *c = mrb_obj_ptr(v); - if (obj != c && ISUPPER(mrb_sym_name_len(mrb, sym, NULL)[0])) { + const char *name = mrb_sym_name_len(mrb, sym, NULL); + if (obj != c && name && ISUPPER(name[0])) { mrb_sym id_classname = MRB_SYM(__classname__); mrb_value o = mrb_obj_iv_get(mrb, c, id_classname); @@ -1423,6 +1424,18 @@ mrb_vm_const_get(mrb_state *mrb, mrb_sym sym) * Raises: * E_TYPE_ERROR: If `mod` is not a class or module. */ +#ifndef MRB_NO_CONST_CACHE +void +mrb_const_cache_clear(mrb_state *mrb) +{ + struct mrb_const_cache_entry *cc = mrb->const_cache; + + for (int i=0; iirep = NULL; + } +} +#endif + MRB_API void mrb_const_set(mrb_state *mrb, mrb_value mod, mrb_sym sym, mrb_value v) { @@ -1431,6 +1444,7 @@ mrb_const_set(mrb_state *mrb, mrb_value mod, mrb_sym sym, mrb_value v) mrb_class_name_class(mrb, mrb_class_ptr(mod), mrb_class_ptr(v), sym); } mrb_obj_iv_set(mrb, mrb_obj_ptr(mod), sym, v); + mrb_const_cache_clear(mrb); if (!mrb->bootstrapping) { mrb_value name = mrb_symbol_value(sym); @@ -1457,6 +1471,7 @@ mrb_const_remove(mrb_state *mrb, mrb_value mod, mrb_sym sym) { mod_const_check(mrb, mod); mrb_iv_remove(mrb, mod, sym); + mrb_const_cache_clear(mrb); } /* @@ -1474,6 +1489,7 @@ MRB_API void mrb_define_const_id(mrb_state *mrb, struct RClass *mod, mrb_sym name, mrb_value v) { mrb_obj_iv_set(mrb, (struct RObject*)mod, name, v); + mrb_const_cache_clear(mrb); } /* diff --git a/src/vm.c b/src/vm.c index 96d9266d8f..0a84798b0d 100644 --- a/src/vm.c +++ b/src/vm.c @@ -271,8 +271,13 @@ top_proc(mrb_state *mrb, const struct RProc *proc, const struct REnv **envp) #define CI_PROC_SET(ci, p) do {\ ci->proc = p;\ - mrb_assert(!p || !MRB_PROC_ALIAS_P(p));\ - ci->pc = (p && !MRB_PROC_CFUNC_P(p) && p->body.irep) ? p->body.irep->iseq : NULL;\ + if (p) {\ + mrb_assert(!MRB_PROC_ALIAS_P(p));\ + ci->pc = (!MRB_PROC_CFUNC_P(p) && p->body.irep) ? p->body.irep->iseq : NULL;\ + }\ + else {\ + ci->pc = NULL;\ + }\ } while (0) void @@ -490,8 +495,14 @@ cipop(mrb_state *mrb) { struct mrb_context *c = mrb->c; mrb_callinfo *ci = c->ci; - struct REnv *env = CI_ENV(ci); + /* Fast path: no env and no blk (most common for simple method calls) */ + if (mrb_likely((!ci->u.env || ci->u.env->tt != MRB_TT_ENV) && !ci->blk)) { + c->ci--; + return c->ci; + } + + struct REnv *env = CI_ENV(ci); ci_env_set(ci, NULL); // make possible to free env by GC if not needed struct RProc *b = ci->blk; if (b && !MRB_PROC_STRICT_P(b) && MRB_PROC_ENV(b) == CI_ENV(&ci[-1])) { @@ -911,7 +922,11 @@ exec_irep(mrb_state *mrb, mrb_value self, const struct RProc *p) MRB_PROC_RESOLVE_ALIAS(ci, p); CI_PROC_SET(ci, p); if (MRB_PROC_CFUNC_P(p)) { - if (MRB_PROC_NOARG_P(p) && (ci->n > 0 || ci->nk > 0)) { + uint32_t caspec_bits = p->flags & MRB_PROC_CASPEC_MASK; + if (caspec_bits != 0) { + check_argument_count(mrb, ci, mrb_proc_decompress_caspec(caspec_bits)); + } + else if (MRB_PROC_NOARG_P(p) && (ci->n > 0 || ci->nk > 0)) { check_argument_count(mrb, ci, 0); } return MRB_PROC_CFUNC(p)(mrb, self); @@ -1062,7 +1077,11 @@ send_method(mrb_state *mrb, mrb_value self, mrb_bool pub) MRB_PROC_RESOLVE_ALIAS(ci, p); CI_PROC_SET(ci, p); if (MRB_PROC_CFUNC_P(p)) { - if (MRB_PROC_NOARG_P(p) && (ci->n > 0 || ci->nk > 0)) { + uint32_t caspec_bits = p->flags & MRB_PROC_CASPEC_MASK; + if (caspec_bits != 0) { + check_argument_count(mrb, ci, mrb_proc_decompress_caspec(caspec_bits)); + } + else if (MRB_PROC_NOARG_P(p) && (ci->n > 0 || ci->nk > 0)) { check_argument_count(mrb, ci, 0); } return MRB_PROC_CFUNC(p)(mrb, self); @@ -1432,6 +1451,19 @@ catch_handler_find(const mrb_irep *irep, const mrb_code *pc, uint32_t filter) #define RAISE_LIT(mrb, c, str) RAISE_EXC(mrb, mrb_exc_new_lit(mrb, c, str)) #define RAISE_FORMAT(mrb, c, fmt, ...) RAISE_EXC(mrb, mrb_exc_new_str(mrb, c, mrb_format(mrb, fmt, __VA_ARGS__))) +/* return codes for extracted opcode handlers */ +#define VM_NEXT 0 /* continue to next instruction */ +#define VM_RAISE 1 /* exception: goto L_RAISE */ +#define VM_SEND_SYM 2 /* fallback send: goto L_SEND_SYM */ +#define VM_SENDB_SYM 3 /* fallback sendb: goto L_SENDB_SYM */ +#define VM_RETURN_NIL 4 /* nil irep: return nil via L_OP_RETURN */ + +#if defined(__GNUC__) || defined(__clang__) +#define MRB_FLATTEN __attribute__((flatten)) +#else +#define MRB_FLATTEN +#endif + static void argnum_error(mrb_state *mrb, mrb_int num) { @@ -1556,17 +1588,58 @@ prepare_tagged_break(mrb_state *mrb, uint32_t tag, const mrb_callinfo *return_ci #define CALL_CODE_HOOKS() do { insn = BYTECODE_DECODER(*ci->pc); CODE_FETCH_HOOK(mrb, irep, ci->pc, regs); } while (0) #ifdef MRB_USE_TASK_SCHEDULER +/* TRUE when the current context is executing across a C call boundary, i.e. + a C function on the stack re-entered the VM (mrb_funcall / mrb_yield / + mrb_vm_run). A task cannot be suspended at such a point: the C stack + frame between the scheduler's mrb_vm_exec and the current frame cannot + be saved or restored, and returning early from the inner mrb_vm_exec + would leave the call-info stack drifted, tripping the assertion in + mrb_vm_run (issues #6864, #6868). The scheduler defers the switch until + execution unwinds back to a frame with no C boundary. This mirrors the + cooperative guard in Task.pass, which raises rather than defers. + cibase is excluded: it is the entry frame of this mrb_vm_exec. */ +static mrb_bool +task_across_c_boundary(mrb_state *mrb) +{ + for (mrb_callinfo *ci = mrb->c->ci; ci > mrb->c->cibase; ci--) { + if (ci->cci > 0) return TRUE; + } + return FALSE; +} + +/* Defer task switches while a C-level ObjectSpace walk holds gc.iterating + true. The walk runs callbacks (which may call back into mrb_vm_exec via + mrb_yield); returning early from an inner exec while the outer C + iteration is still active drifts the call-info stack and eventually + crashes (issue #6862). Switches resume at the next OP boundary after + the walk releases gc.iterating. A pending switch is also deferred while + executing across a C call boundary (see task_across_c_boundary). A + pending MRB_TASK_STOPPED is not deferred, since the task is going away. + + mrb->jmp is restored to prev_jmp before returning, exactly as the + normal return paths below do. mrb_vm_exec set mrb->jmp to its own + stack-local c_jmp on entry; leaving it dangling after this early return + means a later raise longjmps into a freed frame (issue #6863). + + This macro must only be expanded where prev_jmp is in scope, i.e. + inside mrb_vm_exec (via NEXT / END_DISPATCH). */ #define RETURN_IF_TASK_STOPPED(mrb) do { \ - if ((mrb)->task.switching || (mrb)->c->status == MRB_TASK_STOPPED) \ + if (((mrb)->task.switching && !(mrb)->gc.iterating && \ + !task_across_c_boundary(mrb)) || \ + (mrb)->c->status == MRB_TASK_STOPPED) { \ + (mrb)->jmp = prev_jmp; \ return mrb_nil_value(); \ + } \ } while (0) #define TASK_STOP(mrb) do { \ if (mrb->c->status != MRB_TASK_STOPPED) \ mrb->c->status = MRB_TASK_STOPPED; \ } while (0) +#define TASK_RETURN_EXCEPTION_AS_VALUE(mrb) ((mrb)->task.exception_as_result) #else #define RETURN_IF_TASK_STOPPED(mrb) #define TASK_STOP(mrb) +#define TASK_RETURN_EXCEPTION_AS_VALUE(mrb) FALSE #endif /** @@ -1652,6 +1725,505 @@ hash_new_from_regs(mrb_state *mrb, mrb_int argc, mrb_int idx) #define ary_new_from_regs(mrb, argc, idx) mrb_ary_new_from_values(mrb, (argc), ®s[idx]); +/* type pair for arithmetic/comparison dispatch */ +#define TYPES2(a,b) ((((uint16_t)(a))<<8)|(((uint16_t)(b))&0xff)) + +/* + * Extracted opcode handlers. + * These are static functions force-inlined back into mrb_vm_exec via + * __attribute__((flatten)). The source stays clean while the compiled + * output is identical to having the code inline. + * + * Return VM_NEXT to continue, VM_RAISE when an exception has been set. + * VM_SEND_SYM/VM_SENDB_SYM for method fallback (mid set via out-param). + */ + +static int +vm_op_blkpush(mrb_state *mrb, uint32_t a, uint16_t b) +{ + mrb_callinfo *ci = mrb->c->ci; + int m1 = (b>>11)&0x3f; + int r = (b>>10)&0x1; + int m2 = (b>>5)&0x1f; + int kd = (b>>4)&0x1; + int lv = (b>>0)&0xf; + int offset = m1+r+m2+kd; + mrb_value *stack; + + if (lv == 0) stack = regs + 1; + else { + struct REnv *e = uvenv(mrb, lv-1); + if (!e || (!MRB_ENV_ONSTACK_P(e) && e->mid == 0) || + MRB_ENV_LEN(e) <= offset+1) { + mrb_exc_set(mrb, mrb_exc_new_lit(mrb, E_LOCALJUMP_ERROR, "unexpected yield")); + return VM_RAISE; + } + stack = e->stack + 1; + } + if (mrb_nil_p(stack[offset])) { + mrb_exc_set(mrb, mrb_exc_new_lit(mrb, E_LOCALJUMP_ERROR, "unexpected yield")); + return VM_RAISE; + } + regs[a] = stack[offset]; + return VM_NEXT; +} + +static int +vm_op_argary(mrb_state *mrb, uint32_t a, uint16_t b) +{ + mrb_callinfo *ci = mrb->c->ci; + mrb_int m1 = (b>>11)&0x3f; + mrb_int r = (b>>10)&0x1; + mrb_int m2 = (b>>5)&0x1f; + mrb_int kd = (b>>4)&0x1; + mrb_int lv = (b>>0)&0xf; + mrb_value *stack; + + if (ci->mid == 0 || CI_TARGET_CLASS(ci) == NULL) { + L_NOSUPER: + mrb_exc_set(mrb, mrb_exc_new_lit(mrb, E_NOMETHOD_ERROR, "super called outside of method")); + return VM_RAISE; + } + if (lv == 0) stack = regs + 1; + else { + struct REnv *e = uvenv(mrb, lv-1); + if (!e) goto L_NOSUPER; + if (MRB_ENV_LEN(e) <= m1+r+m2+1) + goto L_NOSUPER; + stack = e->stack + 1; + } + if (r == 0) { + regs[a] = mrb_ary_new_from_values(mrb, m1+m2, stack); + } + else { + mrb_value *pp = NULL; + struct RArray *rest; + mrb_int len = 0; + + if (mrb_array_p(stack[m1])) { + struct RArray *ary = mrb_ary_ptr(stack[m1]); + + pp = ARY_PTR(ary); + len = ARY_LEN(ary); + } + regs[a] = mrb_ary_new_capa(mrb, m1+len+m2); + rest = mrb_ary_ptr(regs[a]); + if (m1 > 0) { + stack_copy(ARY_PTR(rest), stack, m1); + } + if (len > 0) { + stack_copy(ARY_PTR(rest)+m1, pp, len); + } + if (m2 > 0) { + stack_copy(ARY_PTR(rest)+m1+len, stack+m1+1, m2); + } + ARY_SET_LEN(rest, m1+len+m2); + } + if (kd) { + regs[a+1] = stack[m1+r+m2]; + regs[a+2] = stack[m1+r+m2+1]; + } + else { + regs[a+1] = stack[m1+r+m2]; + } + return VM_NEXT; +} + +static int +vm_op_enter(mrb_state *mrb, uint32_t a) +{ + mrb_callinfo *ci = mrb->c->ci; + const mrb_irep *irep = ci->proc->body.irep; + mrb_int argc = ci->n; + mrb_value *argv = regs+1; + + mrb_int m1 = MRB_ASPEC_REQ(a); + + /* no other args */ + if ((a & ~0x7c0001) == 0 && argc < 15 && MRB_PROC_STRICT_P(ci->proc)) { + if (mrb_unlikely(argc+(ci->nk==15) != m1)) { /* count kdict too */ + argnum_error(mrb, m1); + return VM_RAISE; + } + /* clear local (but non-argument) variables */ + mrb_int pos = m1+2; /* self+m1+blk */ + if (irep->nlocals-pos > 0) { + stack_clear(®s[pos], irep->nlocals-pos); + } + return VM_NEXT; + } + + mrb_int o = MRB_ASPEC_OPT(a); + mrb_int r = MRB_ASPEC_REST(a); + mrb_int m2 = MRB_ASPEC_POST(a); + mrb_int kd = (MRB_ASPEC_KEY(a) > 0 || MRB_ASPEC_KDICT(a))? 1 : 0; + /* unused + int b = MRB_ASPEC_BLOCK(a); + */ + mrb_int const len = m1 + o + r + m2; + + mrb_value * const argv0 = argv; + mrb_value blk = regs[ci_bidx(ci)]; + + /* &nil: reject block */ + if (MRB_ASPEC_NOBLOCK(a) && !mrb_nil_p(blk)) { + mrb_exc_set(mrb, mrb_exc_new_lit(mrb, E_ARGUMENT_ERROR, "no block accepted")); + return VM_RAISE; + } + + mrb_value kdict = mrb_nil_value(); + + /* keyword arguments */ + if (ci->nk == 15) { + kdict = regs[mrb_ci_kidx(ci)]; + } + if (!kd) { + if (!mrb_nil_p(kdict) && mrb_hash_p(kdict) && mrb_hash_size(mrb, kdict) > 0) { + if (argc < 14) { + ci->n++; + argc++; /* include kdict in normal arguments */ + } + else if (argc == 14) { + /* pack arguments and kdict */ + regs[1] = mrb_ary_new_from_values(mrb, argc+1, ®s[1]); + argc = ci->n = 15; + } + else {/* argc == 15 */ + /* push kdict to packed arguments */ + mrb_ary_push(mrb, regs[1], kdict); + } + } + kdict = mrb_nil_value(); + ci->nk = 0; + } + else if (!mrb_nil_p(kdict)) { + mrb_gc_protect(mrb, kdict); + } + + /* arguments is passed with Array */ + if (argc == 15) { + struct RArray *ary = mrb_ary_ptr(regs[1]); + argv = ARY_PTR(ary); + argc = (int)ARY_LEN(ary); + mrb_gc_protect(mrb, regs[1]); + } + + /* strict argument check */ + if (ci->proc && MRB_PROC_STRICT_P(ci->proc)) { + if (mrb_unlikely(argc < m1 + m2 || (r == 0 && argc > len))) { + argnum_error(mrb, m1+m2); + return VM_RAISE; + } + } + /* extract first argument array to arguments */ + else if (len > 1 && argc == 1 && mrb_array_p(argv[0])) { + mrb_gc_protect(mrb, argv[0]); + argc = (int)RARRAY_LEN(argv[0]); + argv = RARRAY_PTR(argv[0]); + } + + /* rest arguments */ + mrb_value rest; + if (argc < len) { + mrb_int mlen = m2; + if (argc < m1+m2) { + mlen = m1 < argc ? argc - m1 : 0; + } + + /* copy mandatory and optional arguments */ + if (argv0 != argv && argv) { + value_move(®s[1], argv, argc-mlen); /* m1 + o */ + } + if (argc < m1) { + stack_clear(®s[argc+1], m1-argc); + } + /* copy post mandatory arguments */ + if (mlen) { + value_move(®s[len-m2+1], &argv[argc-mlen], mlen); + } + if (mlen < m2) { + stack_clear(®s[len-m2+mlen+1], m2-mlen); + } + /* initialize rest arguments with empty Array */ + if (r) { + rest = mrb_ary_new_capa(mrb, 0); + regs[m1+o+1] = rest; + } + /* skip initializer of passed arguments */ + if (o > 0 && argc > m1+m2) + ci->pc += (argc - m1 - m2)*3; + } + else { + mrb_int rnum = 0; + if (argv0 != argv) { + mrb_gc_protect(mrb, blk); + value_move(®s[1], argv, m1+o); + } + if (r) { + rnum = argc-m1-o-m2; + rest = mrb_ary_new_from_values(mrb, rnum, argv+m1+o); + regs[m1+o+1] = rest; + } + if (m2 > 0 && argc-m2 > m1) { + value_move(®s[m1+o+r+1], &argv[m1+o+rnum], m2); + } + ci->pc += o*3; + } + + /* need to be update blk first to protect blk from GC */ + mrb_int const kw_pos = len + kd; /* where kwhash should be */ + mrb_int const blk_pos = kw_pos + 1; /* where block should be */ + regs[blk_pos] = blk; /* move block */ + if (kd) { + if (mrb_nil_p(kdict)) { + kdict = mrb_hash_new_capa(mrb, 0); + } + regs[kw_pos] = kdict; /* set kwhash */ + ci->nk = 15; + } + + /* format arguments for generated code */ + ci->n = (uint8_t)len; + + /* clear local (but non-argument) variables */ + if (irep->nlocals-blk_pos-1 > 0) { + stack_clear(®s[blk_pos+1], irep->nlocals-blk_pos-1); + } + return VM_NEXT; +} + +static int +vm_op_getidx(mrb_state *mrb, uint32_t a, mrb_sym *midp) +{ + mrb_callinfo *ci = mrb->c->ci; + mrb_value va = regs[a], vb = regs[a+1]; + enum mrb_vtype tt = mrb_type(va); + + /* Array case is most common - check first with branch hint */ + if (mrb_likely(tt == MRB_TT_ARRAY)) { + struct RArray *ary = mrb_ary_ptr(va); + /* optimize only for Array class; subclasses/singleton may override [] */ + if (mrb_unlikely(ary->c != mrb->array_class)) goto getidx_fallback; + if (mrb_likely(mrb_integer_p(vb))) { + mrb_int idx = mrb_integer(vb); + mrb_int len; + mrb_value *ptr; + + /* Single ARY_EMBED_P check instead of two */ +#ifndef MRB_ARY_NO_EMBED + if (ARY_EMBED_P(ary)) { + len = ARY_EMBED_LEN(ary); + ptr = ary->as.ary; + } + else +#endif + { + len = ary->as.heap.len; + ptr = ary->as.heap.ptr; + } + + /* Unsigned comparison: handles negative idx as large positive */ + if (mrb_likely((mrb_uint)idx < (mrb_uint)len)) { + regs[a] = ptr[idx]; + } + else { + regs[a] = mrb_ary_entry(va, idx); + } + return VM_NEXT; + } + goto getidx_fallback; + } + else if (tt == MRB_TT_HASH) { + /* optimize only for Hash class; subclasses/singleton may override [] */ + if (mrb_obj_ptr(va)->c != mrb->hash_class) goto getidx_fallback; + va = mrb_hash_get(mrb, va, vb); + ci = mrb->c->ci; + regs[a] = va; + return VM_NEXT; + } + else if (tt == MRB_TT_STRING) { + /* optimize only for String class; subclasses/singleton may override [] */ + if (mrb_obj_ptr(va)->c != mrb->string_class) goto getidx_fallback; + switch (mrb_type(vb)) { + case MRB_TT_INTEGER: + case MRB_TT_STRING: + case MRB_TT_RANGE: + va = mrb_str_aref(mrb, va, vb, mrb_undef_value()); + regs[a] = va; + return VM_NEXT; + default: + break; + } + } +getidx_fallback: + *midp = MRB_OPSYM(aref); + return VM_SEND_SYM; +} + +static int +vm_op_getidx0(mrb_state *mrb, uint32_t a, uint16_t b, mrb_sym *midp) +{ + mrb_callinfo *ci = mrb->c->ci; + mrb_value recv = regs[b]; + enum mrb_vtype tt = mrb_type(recv); + + if (mrb_likely(tt == MRB_TT_ARRAY)) { + struct RArray *ary = mrb_ary_ptr(recv); + if (mrb_unlikely(ary->c != mrb->array_class)) goto getidx0_fallback; +#ifndef MRB_ARY_NO_EMBED + if (ARY_EMBED_P(ary)) { + regs[a] = ARY_EMBED_LEN(ary) > 0 ? ary->as.ary[0] : mrb_nil_value(); + } + else +#endif + { + regs[a] = ary->as.heap.len > 0 ? ary->as.heap.ptr[0] : mrb_nil_value(); + } + return VM_NEXT; + } + else if (tt == MRB_TT_HASH) { + if (mrb_obj_ptr(recv)->c != mrb->hash_class) goto getidx0_fallback; + regs[a] = mrb_hash_get(mrb, recv, mrb_fixnum_value(0)); + return VM_NEXT; + } +getidx0_fallback: + regs[a] = recv; + SET_FIXNUM_VALUE(regs[a+1], 0); + *midp = MRB_OPSYM(aref); + return VM_SEND_SYM; +} + +static int +vm_op_setidx(mrb_state *mrb, uint32_t a, mrb_sym *midp) +{ + mrb_callinfo *ci = mrb->c->ci; + mrb_value va = regs[a], vb = regs[a+1], vc = regs[a+2]; + switch (mrb_type(va)) { + case MRB_TT_ARRAY: + /* optimize only for Array class; subclasses/singleton may override []= */ + if (mrb_obj_ptr(va)->c != mrb->array_class) goto setidx_fallback; + if (!mrb_integer_p(vb)) goto setidx_fallback; + mrb_ary_set(mrb, va, mrb_integer(vb), vc); + ci = mrb->c->ci; + regs[a] = vc; + return VM_NEXT; + case MRB_TT_HASH: + /* optimize only for Hash class; subclasses/singleton may override []= */ + if (mrb_obj_ptr(va)->c != mrb->hash_class) goto setidx_fallback; + mrb_hash_set(mrb, va, vb, vc); + ci = mrb->c->ci; + regs[a] = vc; + return VM_NEXT; + default: + setidx_fallback: + SET_NIL_VALUE(regs[a+3]); + *midp = MRB_OPSYM(aset); + return VM_SENDB_SYM; + } +} + +static int +vm_op_div(mrb_state *mrb, uint32_t a, mrb_sym *midp) +{ + mrb_callinfo *ci = mrb->c->ci; +#ifndef MRB_NO_FLOAT + mrb_float x, y, f; +#endif + + /* need to check if op is overridden */ + switch (TYPES2(mrb_type(regs[a]),mrb_type(regs[a+1]))) { + case TYPES2(MRB_TT_INTEGER,MRB_TT_INTEGER): + { + mrb_int x = mrb_integer(regs[a]); + mrb_int y = mrb_integer(regs[a+1]); + regs[a] = mrb_div_int_value(mrb, x, y); + } + return VM_NEXT; +#ifndef MRB_NO_FLOAT + case TYPES2(MRB_TT_INTEGER,MRB_TT_FLOAT): + x = (mrb_float)mrb_integer(regs[a]); + y = mrb_float(regs[a+1]); + break; + case TYPES2(MRB_TT_FLOAT,MRB_TT_INTEGER): + x = mrb_float(regs[a]); + y = (mrb_float)mrb_integer(regs[a+1]); + break; + case TYPES2(MRB_TT_FLOAT,MRB_TT_FLOAT): + x = mrb_float(regs[a]); + y = mrb_float(regs[a+1]); + break; +#endif + default: + *midp = MRB_OPSYM(div); + return VM_SEND_SYM; + } + +#ifndef MRB_NO_FLOAT + f = mrb_div_float(x, y); + SET_FLOAT_VALUE(mrb, regs[a], f); +#endif + return VM_NEXT; +} + +static mrb_sym +vm_define_method(mrb_state *mrb, struct RClass *tc, const mrb_irep *irep, uint16_t b, uint16_t c) +{ + struct RProc *p = mrb_proc_new(mrb, irep->reps[c]); + mrb_sym mid = irep->syms[b]; + mrb_method_t m; + + p->flags |= MRB_PROC_SCOPE | MRB_PROC_STRICT; + MRB_METHOD_FROM_PROC(m, p); + MRB_METHOD_SET_VISIBILITY(m, MRB_METHOD_VDEFAULT_FL); + mrb_define_method_raw(mrb, tc, mid, m); + mrb_method_added(mrb, tc, mid); + return mid; +} + +/* Common proc dispatch for OP_CALL and OP_BLKCALL. + Returns VM_NEXT, VM_RAISE, or VM_RETURN_NIL. */ +static int +vm_call_proc(mrb_state *mrb, const struct RProc *p, mrb_int nargs, + const mrb_irep **irepp, int ai) +{ + mrb_callinfo *ci = mrb->c->ci; + mrb_value recv = ci->stack[0]; + + /* handle alias */ + MRB_PROC_RESOLVE_ALIAS(ci, p); + if (MRB_PROC_ENV_P(p)) { + ci->mid = MRB_PROC_ENV(p)->mid; + } + ci->u.target_class = MRB_PROC_TARGET_CLASS(p); + CI_PROC_SET(ci, p); + + if (MRB_PROC_CFUNC_P(p)) { + recv = MRB_PROC_CFUNC(p)(mrb, recv); + mrb_gc_arena_shrink(mrb, ai); + if (mrb_unlikely(mrb->exc)) return VM_RAISE; + ci = cipop(mrb); + ci[1].stack[0] = recv; + *irepp = ci->proc->body.irep; + } + else { + const mrb_irep *irep = p->body.irep; + if (!irep) { + ci->stack[0] = mrb_nil_value(); + return VM_RETURN_NIL; + } + if (nargs < irep->nregs) { + stack_extend(mrb, irep->nregs); + stack_clear(ci->stack+nargs, irep->nregs-nargs); + } + if (MRB_PROC_ENV_P(p)) { + ci->stack[0] = MRB_PROC_ENV(p)->stack[0]; + } + ci->pc = irep->iseq; + *irepp = irep; + } + return VM_NEXT; +} + /** * @brief Executes a sequence of mruby bytecode instructions. * @@ -1679,7 +2251,7 @@ hash_new_from_regs(mrb_state *mrb, mrb_int argc, mrb_int idx) * when not using switch-based dispatch. It also manages the callinfo * stack (`ci`) for tracking method/block calls. */ -MRB_API mrb_value +MRB_FLATTEN MRB_API mrb_value mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq) { /* mrb_assert(MRB_PROC_CFUNC_P(begin_proc)) */ @@ -1898,131 +2470,46 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq } CASE(OP_GETIDX, B) { - mrb_value va = regs[a], vb = regs[a+1]; - enum mrb_vtype tt = mrb_type(va); - - /* Array case is most common - check first with branch hint */ - if (mrb_likely(tt == MRB_TT_ARRAY)) { - struct RArray *ary = mrb_ary_ptr(va); - /* optimize only for Array class; subclasses/singleton may override [] */ - if (mrb_unlikely(ary->c != mrb->array_class)) goto getidx_fallback; - if (mrb_likely(mrb_integer_p(vb))) { - mrb_int idx = mrb_integer(vb); - mrb_int len; - mrb_value *ptr; - - /* Single ARY_EMBED_P check instead of two */ -#ifndef MRB_ARY_NO_EMBED - if (ARY_EMBED_P(ary)) { - len = ARY_EMBED_LEN(ary); - ptr = ary->as.ary; - } - else -#endif - { - len = ary->as.heap.len; - ptr = ary->as.heap.ptr; - } - - /* Unsigned comparison: handles negative idx as large positive */ - if (mrb_likely((mrb_uint)idx < (mrb_uint)len)) { - regs[a] = ptr[idx]; - } - else { - regs[a] = mrb_ary_entry(va, idx); - } - NEXT; - } - goto getidx_fallback; - } - else if (tt == MRB_TT_HASH) { - /* optimize only for Hash class; subclasses/singleton may override [] */ - if (mrb_obj_ptr(va)->c != mrb->hash_class) goto getidx_fallback; - va = mrb_hash_get(mrb, va, vb); - ci = mrb->c->ci; - regs[a] = va; - NEXT; - } - else if (tt == MRB_TT_STRING) { - /* optimize only for String class; subclasses/singleton may override [] */ - if (mrb_obj_ptr(va)->c != mrb->string_class) goto getidx_fallback; - switch (mrb_type(vb)) { - case MRB_TT_INTEGER: - case MRB_TT_STRING: - case MRB_TT_RANGE: - va = mrb_str_aref(mrb, va, vb, mrb_undef_value()); - regs[a] = va; - NEXT; - default: - break; - } - } - getidx_fallback: - mid = MRB_OPSYM(aref); - goto L_SEND_SYM; + int r = vm_op_getidx(mrb, a, &mid); + ci = mrb->c->ci; + if (r == VM_SEND_SYM) goto L_SEND_SYM; + NEXT; } CASE(OP_GETIDX0, BB) { - mrb_value recv = regs[b]; - enum mrb_vtype tt = mrb_type(recv); - - if (mrb_likely(tt == MRB_TT_ARRAY)) { - struct RArray *ary = mrb_ary_ptr(recv); - if (mrb_unlikely(ary->c != mrb->array_class)) goto getidx0_fallback; -#ifndef MRB_ARY_NO_EMBED - if (ARY_EMBED_P(ary)) { - regs[a] = ARY_EMBED_LEN(ary) > 0 ? ary->as.ary[0] : mrb_nil_value(); - } - else -#endif - { - regs[a] = ary->as.heap.len > 0 ? ary->as.heap.ptr[0] : mrb_nil_value(); - } - NEXT; - } - else if (tt == MRB_TT_HASH) { - if (mrb_obj_ptr(recv)->c != mrb->hash_class) goto getidx0_fallback; - regs[a] = mrb_hash_get(mrb, recv, mrb_fixnum_value(0)); - NEXT; - } - getidx0_fallback: - regs[a] = recv; - SET_FIXNUM_VALUE(regs[a+1], 0); - mid = MRB_OPSYM(aref); - goto L_SEND_SYM; + int r = vm_op_getidx0(mrb, a, b, &mid); + ci = mrb->c->ci; + if (r == VM_SEND_SYM) goto L_SEND_SYM; + NEXT; } CASE(OP_SETIDX, B) { - mrb_value va = regs[a], vb = regs[a+1], vc = regs[a+2]; - switch (mrb_type(va)) { - case MRB_TT_ARRAY: - /* optimize only for Array class; subclasses/singleton may override []= */ - if (mrb_obj_ptr(va)->c != mrb->array_class) goto setidx_fallback; - if (!mrb_integer_p(vb)) goto setidx_fallback; - mrb_ary_set(mrb, va, mrb_integer(vb), vc); - ci = mrb->c->ci; - regs[a] = vc; - NEXT; - case MRB_TT_HASH: - /* optimize only for Hash class; subclasses/singleton may override []= */ - if (mrb_obj_ptr(va)->c != mrb->hash_class) goto setidx_fallback; - mrb_hash_set(mrb, va, vb, vc); - ci = mrb->c->ci; - regs[a] = vc; - NEXT; - default: - setidx_fallback: - c = 2; - mid = MRB_OPSYM(aset); - SET_NIL_VALUE(regs[a+3]); - goto L_SENDB_SYM; - } + int r = vm_op_setidx(mrb, a, &mid); + ci = mrb->c->ci; + if (r == VM_SENDB_SYM) { c = 2; goto L_SENDB_SYM; } + NEXT; } CASE(OP_GETCONST, BB) { - mrb_value v = mrb_vm_const_get(mrb, irep->syms[b]); - ci = mrb->c->ci; - regs[a] = v; +#ifndef MRB_NO_CONST_CACHE + mrb_sym sym = irep->syms[b]; + uint32_t h = mrb_int_hash_func(mrb, ((intptr_t)irep) ^ sym) & (MRB_CONST_CACHE_SIZE-1); + struct mrb_const_cache_entry *cc = &mrb->const_cache[h]; + if (cc->irep == irep && cc->sym == sym) { + regs[a] = cc->value; + NEXT; + } +#endif + { + mrb_value v = mrb_vm_const_get(mrb, irep->syms[b]); + ci = mrb->c->ci; + regs[a] = v; +#ifndef MRB_NO_CONST_CACHE + cc->irep = irep; + cc->sym = sym; + cc->value = v; +#endif + } NEXT; } @@ -2031,6 +2518,7 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq struct RClass *c = MRB_PROC_TARGET_CLASS(ci->proc); if (!c) c = mrb->object_class; mrb_const_set(mrb, mrb_obj_value(c), irep->syms[b], regs[a]); + ci = mrb->c->ci; NEXT; } @@ -2205,6 +2693,7 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq fiber_terminate(mrb, c, ci); if (mrb_unlikely(!c->vmexec)) goto L_RAISE; mrb->jmp = prev_jmp; + if (TASK_RETURN_EXCEPTION_AS_VALUE(mrb)) return mrb_obj_value(mrb->exc); if (!prev_jmp) return mrb_obj_value(mrb->exc); MRB_THROW(prev_jmp); } @@ -2373,97 +2862,26 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq } CASE(OP_CALL, Z) { - mrb_value recv = ci->stack[0]; - const struct RProc *p = mrb_proc_ptr(recv); - - /* handle alias */ - MRB_PROC_RESOLVE_ALIAS(ci, p); - if (MRB_PROC_ENV_P(p)) { - ci->mid = MRB_PROC_ENV(p)->mid; - } - /* replace callinfo */ - ci->u.target_class = MRB_PROC_TARGET_CLASS(p); - CI_PROC_SET(ci, p); - - /* prepare stack */ - if (MRB_PROC_CFUNC_P(p)) { - recv = MRB_PROC_CFUNC(p)(mrb, recv); - mrb_gc_arena_shrink(mrb, ai); - if (mrb_unlikely(mrb->exc)) goto L_RAISE; - /* pop stackpos */ - ci = cipop(mrb); - ci[1].stack[0] = recv; - irep = ci->proc->body.irep; - } - else { - /* setup environment for calling method */ - irep = p->body.irep; - if (!irep) { - ci->stack[0] = mrb_nil_value(); - a = 0; - goto L_OP_RETURN_BODY; - } - mrb_int nargs = ci_bidx(ci)+1; - if (nargs < irep->nregs) { - stack_extend(mrb, irep->nregs); - stack_clear(regs+nargs, irep->nregs-nargs); - } - if (MRB_PROC_ENV_P(p)) { - regs[0] = MRB_PROC_ENV(p)->stack[0]; - } - ci->pc = irep->iseq; - } + const struct RProc *p = mrb_proc_ptr(ci->stack[0]); + int r = vm_call_proc(mrb, p, ci_bidx(ci)+1, &irep, ai); + ci = mrb->c->ci; + if (r == VM_RAISE) goto L_RAISE; + if (r == VM_RETURN_NIL) { a = 0; goto L_OP_RETURN_BODY; } JUMP; } CASE(OP_BLKCALL, BB) { /* Direct block call: R[a] = R[a].call(R[a+1],...,R[a+b]) */ - /* Skip method dispatch - directly invoke the proc */ - mrb_value recv = regs[a]; - const struct RProc *p; - - if (mrb_unlikely(!mrb_proc_p(recv))) { - mrb_raisef(mrb, E_TYPE_ERROR, "wrong type %T (expected Proc)", recv); + if (mrb_unlikely(!mrb_proc_p(regs[a]))) { + mrb_raisef(mrb, E_TYPE_ERROR, "wrong type %T (expected Proc)", regs[a]); } - p = mrb_proc_ptr(recv); - - /* push callinfo */ + const struct RProc *p = mrb_proc_ptr(regs[a]); ci = cipush(mrb, a, CINFO_DIRECT, NULL, NULL, NULL, 0, b); ci->cci = CINFO_NONE; /* mark as VM-to-VM call for proper break handling */ - - /* handle alias */ - MRB_PROC_RESOLVE_ALIAS(ci, p); - if (MRB_PROC_ENV_P(p)) { - ci->mid = MRB_PROC_ENV(p)->mid; - } - ci->u.target_class = MRB_PROC_TARGET_CLASS(p); - CI_PROC_SET(ci, p); - - if (MRB_PROC_CFUNC_P(p)) { - recv = MRB_PROC_CFUNC(p)(mrb, recv); - mrb_gc_arena_shrink(mrb, ai); - if (mrb_unlikely(mrb->exc)) goto L_RAISE; - ci = cipop(mrb); - ci[1].stack[0] = recv; - irep = ci->proc->body.irep; - } - else { - irep = p->body.irep; - if (!irep) { - ci->stack[0] = mrb_nil_value(); - a = 0; - goto L_OP_RETURN_BODY; - } - mrb_int nargs = b + 1; /* args + self */ - if (nargs < irep->nregs) { - stack_extend(mrb, irep->nregs); - stack_clear(regs+nargs, irep->nregs-nargs); - } - if (MRB_PROC_ENV_P(p)) { - regs[0] = MRB_PROC_ENV(p)->stack[0]; - } - ci->pc = irep->iseq; - } + int r = vm_call_proc(mrb, p, b+1, &irep, ai); + ci = mrb->c->ci; + if (r == VM_RAISE) goto L_RAISE; + if (r == VM_RETURN_NIL) { a = 0; goto L_OP_RETURN_BODY; } JUMP; } @@ -2490,218 +2908,15 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq } CASE(OP_ARGARY, BS) { - mrb_int m1 = (b>>11)&0x3f; - mrb_int r = (b>>10)&0x1; - mrb_int m2 = (b>>5)&0x1f; - mrb_int kd = (b>>4)&0x1; - mrb_int lv = (b>>0)&0xf; - mrb_value *stack; - - if (ci->mid == 0 || CI_TARGET_CLASS(ci) == NULL) { - L_NOSUPER: - RAISE_LIT(mrb, E_NOMETHOD_ERROR, "super called outside of method"); - } - if (lv == 0) stack = regs + 1; - else { - struct REnv *e = uvenv(mrb, lv-1); - if (!e) goto L_NOSUPER; - if (MRB_ENV_LEN(e) <= m1+r+m2+1) - goto L_NOSUPER; - stack = e->stack + 1; - } - if (r == 0) { - regs[a] = mrb_ary_new_from_values(mrb, m1+m2, stack); - } - else { - mrb_value *pp = NULL; - struct RArray *rest; - mrb_int len = 0; - - if (mrb_array_p(stack[m1])) { - struct RArray *ary = mrb_ary_ptr(stack[m1]); - - pp = ARY_PTR(ary); - len = ARY_LEN(ary); - } - regs[a] = mrb_ary_new_capa(mrb, m1+len+m2); - rest = mrb_ary_ptr(regs[a]); - if (m1 > 0) { - stack_copy(ARY_PTR(rest), stack, m1); - } - if (len > 0) { - stack_copy(ARY_PTR(rest)+m1, pp, len); - } - if (m2 > 0) { - stack_copy(ARY_PTR(rest)+m1+len, stack+m1+1, m2); - } - ARY_SET_LEN(rest, m1+len+m2); - } - if (kd) { - regs[a+1] = stack[m1+r+m2]; - regs[a+2] = stack[m1+r+m2+1]; - } - else { - regs[a+1] = stack[m1+r+m2]; - } + if (vm_op_argary(mrb, a, b) == VM_RAISE) goto L_RAISE; mrb_gc_arena_restore(mrb, ai); NEXT; } CASE(OP_ENTER, W) { - mrb_int argc = ci->n; - mrb_value *argv = regs+1; - - mrb_int m1 = MRB_ASPEC_REQ(a); - - /* no other args */ - if ((a & ~0x7c0001) == 0 && argc < 15 && MRB_PROC_STRICT_P(ci->proc)) { - if (mrb_unlikely(argc+(ci->nk==15) != m1)) { /* count kdict too */ - argnum_error(mrb, m1); - goto L_RAISE; - } - /* clear local (but non-argument) variables */ - mrb_int pos = m1+2; /* self+m1+blk */ - if (irep->nlocals-pos > 0) { - stack_clear(®s[pos], irep->nlocals-pos); - } - NEXT; - } - - mrb_int o = MRB_ASPEC_OPT(a); - mrb_int r = MRB_ASPEC_REST(a); - mrb_int m2 = MRB_ASPEC_POST(a); - mrb_int kd = (MRB_ASPEC_KEY(a) > 0 || MRB_ASPEC_KDICT(a))? 1 : 0; - /* unused - int b = MRB_ASPEC_BLOCK(a); - */ - mrb_int const len = m1 + o + r + m2; - - mrb_value * const argv0 = argv; - mrb_value blk = regs[ci_bidx(ci)]; - - /* &nil: reject block */ - if (MRB_ASPEC_NOBLOCK(a) && !mrb_nil_p(blk)) { - RAISE_LIT(mrb, E_ARGUMENT_ERROR, "no block accepted"); - } - - mrb_value kdict = mrb_nil_value(); - - /* keyword arguments */ - if (ci->nk == 15) { - kdict = regs[mrb_ci_kidx(ci)]; - } - if (!kd) { - if (!mrb_nil_p(kdict) && mrb_hash_p(kdict) && mrb_hash_size(mrb, kdict) > 0) { - if (argc < 14) { - ci->n++; - argc++; /* include kdict in normal arguments */ - } - else if (argc == 14) { - /* pack arguments and kdict */ - regs[1] = ary_new_from_regs(mrb, argc+1, 1); - argc = ci->n = 15; - } - else {/* argc == 15 */ - /* push kdict to packed arguments */ - mrb_ary_push(mrb, regs[1], kdict); - } - } - kdict = mrb_nil_value(); - ci->nk = 0; - } - else if (!mrb_nil_p(kdict)) { - mrb_gc_protect(mrb, kdict); - } - - /* arguments is passed with Array */ - if (argc == 15) { - struct RArray *ary = mrb_ary_ptr(regs[1]); - argv = ARY_PTR(ary); - argc = (int)ARY_LEN(ary); - mrb_gc_protect(mrb, regs[1]); - } - - /* strict argument check */ - if (ci->proc && MRB_PROC_STRICT_P(ci->proc)) { - if (mrb_unlikely(argc < m1 + m2 || (r == 0 && argc > len))) { - argnum_error(mrb, m1+m2); - goto L_RAISE; - } - } - /* extract first argument array to arguments */ - else if (len > 1 && argc == 1 && mrb_array_p(argv[0])) { - mrb_gc_protect(mrb, argv[0]); - argc = (int)RARRAY_LEN(argv[0]); - argv = RARRAY_PTR(argv[0]); - } - - /* rest arguments */ - mrb_value rest; - if (argc < len) { - mrb_int mlen = m2; - if (argc < m1+m2) { - mlen = m1 < argc ? argc - m1 : 0; - } - - /* copy mandatory and optional arguments */ - if (argv0 != argv && argv) { - value_move(®s[1], argv, argc-mlen); /* m1 + o */ - } - if (argc < m1) { - stack_clear(®s[argc+1], m1-argc); - } - /* copy post mandatory arguments */ - if (mlen) { - value_move(®s[len-m2+1], &argv[argc-mlen], mlen); - } - if (mlen < m2) { - stack_clear(®s[len-m2+mlen+1], m2-mlen); - } - /* initialize rest arguments with empty Array */ - if (r) { - rest = mrb_ary_new_capa(mrb, 0); - regs[m1+o+1] = rest; - } - /* skip initializer of passed arguments */ - if (o > 0 && argc > m1+m2) - ci->pc += (argc - m1 - m2)*3; - } - else { - mrb_int rnum = 0; - if (argv0 != argv) { - mrb_gc_protect(mrb, blk); - value_move(®s[1], argv, m1+o); - } - if (r) { - rnum = argc-m1-o-m2; - rest = mrb_ary_new_from_values(mrb, rnum, argv+m1+o); - regs[m1+o+1] = rest; - } - if (m2 > 0 && argc-m2 > m1) { - value_move(®s[m1+o+r+1], &argv[m1+o+rnum], m2); - } - ci->pc += o*3; - } - - /* need to be update blk first to protect blk from GC */ - mrb_int const kw_pos = len + kd; /* where kwhash should be */ - mrb_int const blk_pos = kw_pos + 1; /* where block should be */ - regs[blk_pos] = blk; /* move block */ - if (kd) { - if (mrb_nil_p(kdict)) { - kdict = mrb_hash_new_capa(mrb, 0); - } - regs[kw_pos] = kdict; /* set kwhash */ - ci->nk = 15; - } - - /* format arguments for generated code */ - ci->n = (uint8_t)len; - - /* clear local (but non-argument) variables */ - if (irep->nlocals-blk_pos-1 > 0) { - stack_clear(®s[blk_pos+1], irep->nlocals-blk_pos-1); - } + if (vm_op_enter(mrb, a) == VM_RAISE) goto L_RAISE; + ci = mrb->c->ci; + irep = ci->proc->body.irep; JUMP; } @@ -2898,27 +3113,7 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq } CASE(OP_BLKPUSH, BS) { - int m1 = (b>>11)&0x3f; - int r = (b>>10)&0x1; - int m2 = (b>>5)&0x1f; - int kd = (b>>4)&0x1; - int lv = (b>>0)&0xf; - int offset = m1+r+m2+kd; - mrb_value *stack; - - if (lv == 0) stack = regs + 1; - else { - struct REnv *e = uvenv(mrb, lv-1); - if (!e || (!MRB_ENV_ONSTACK_P(e) && e->mid == 0) || - MRB_ENV_LEN(e) <= offset+1) { - RAISE_LIT(mrb, E_LOCALJUMP_ERROR, "unexpected yield"); - } - stack = e->stack + 1; - } - if (mrb_nil_p(stack[offset])) { - RAISE_LIT(mrb, E_LOCALJUMP_ERROR, "unexpected yield"); - } - regs[a] = stack[offset]; + if (vm_op_blkpush(mrb, a, b) == VM_RAISE) goto L_RAISE; NEXT; } @@ -2927,7 +3122,6 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq RAISE_LIT(mrb, E_RANGE_ERROR, "integer overflow"); #endif -#define TYPES2(a,b) ((((uint16_t)(a))<<8)|(((uint16_t)(b))&0xff)) #define OP_MATH(op_name) do { \ /* need to check if op is overridden */ \ uint16_t tt = TYPES2(mrb_type(regs[a]),mrb_type(regs[a+1])); \ @@ -3003,42 +3197,9 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq } CASE(OP_DIV, B) { -#ifndef MRB_NO_FLOAT - mrb_float x, y, f; -#endif - - /* need to check if op is overridden */ - switch (TYPES2(mrb_type(regs[a]),mrb_type(regs[a+1]))) { - case TYPES2(MRB_TT_INTEGER,MRB_TT_INTEGER): - { - mrb_int x = mrb_integer(regs[a]); - mrb_int y = mrb_integer(regs[a+1]); - regs[a] = mrb_div_int_value(mrb, x, y); - } - NEXT; -#ifndef MRB_NO_FLOAT - case TYPES2(MRB_TT_INTEGER,MRB_TT_FLOAT): - x = (mrb_float)mrb_integer(regs[a]); - y = mrb_float(regs[a+1]); - break; - case TYPES2(MRB_TT_FLOAT,MRB_TT_INTEGER): - x = mrb_float(regs[a]); - y = (mrb_float)mrb_integer(regs[a+1]); - break; - case TYPES2(MRB_TT_FLOAT,MRB_TT_FLOAT): - x = mrb_float(regs[a]); - y = mrb_float(regs[a+1]); - break; -#endif - default: - mid = MRB_OPSYM(div); - goto L_SEND_SYM; - } - -#ifndef MRB_NO_FLOAT - f = mrb_div_float(x, y); - SET_FLOAT_VALUE(mrb, regs[a], f); -#endif + int r = vm_op_div(mrb, a, &mid); + ci = mrb->c->ci; + if (r == VM_SEND_SYM) goto L_SEND_SYM; NEXT; } @@ -3119,15 +3280,9 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq break; \ OP_MATHILV_CASE_FLOAT(op_name); \ default: \ - { \ - mrb_value arg = mrb_int_value(mrb, c); \ - mrb_sym mid = MRB_OPSYM(op_name); \ - mrb_value v = mrb_funcall_argv(mrb, regs[a], mid, 1, &arg); \ - ci = mrb->c->ci; \ - regs[a] = v; \ - mrb_gc_arena_restore(mrb, ai); \ - } \ - break; \ + SET_INT_VALUE(mrb,regs[a+1], c); \ + mid = MRB_OPSYM(op_name); \ + goto L_SEND_SYM; \ } \ NEXT @@ -3529,17 +3684,8 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq CASE(OP_TDEF, BBB) { struct RClass *tc = check_target_class(mrb); - struct RProc *p; - mrb_method_t m; - if (mrb_unlikely(!tc)) goto L_RAISE; - p = mrb_proc_new(mrb, irep->reps[c]); - mid = irep->syms[b]; - p->flags |= MRB_PROC_SCOPE | MRB_PROC_STRICT; - MRB_METHOD_FROM_PROC(m, p); - MRB_METHOD_SET_VISIBILITY(m, MRB_METHOD_VDEFAULT_FL); - mrb_define_method_raw(mrb, tc, mid, m); - mrb_method_added(mrb, tc, mid); + mid = vm_define_method(mrb, tc, irep, b, c); ci = mrb->c->ci; mrb_gc_arena_restore(mrb, ai); regs[a] = mrb_symbol_value(mid); @@ -3548,15 +3694,7 @@ mrb_vm_exec(mrb_state *mrb, const struct RProc *begin_proc, const mrb_code *iseq CASE(OP_SDEF, BBB) { struct RClass *tc = mrb_class_ptr(mrb_singleton_class(mrb, regs[a])); - struct RProc *p = mrb_proc_new(mrb, irep->reps[c]); - mrb_method_t m; - - mid = irep->syms[b]; - p->flags |= MRB_PROC_SCOPE | MRB_PROC_STRICT; - MRB_METHOD_FROM_PROC(m, p); - MRB_METHOD_SET_VISIBILITY(m, MRB_METHOD_VDEFAULT_FL); - mrb_define_method_raw(mrb, tc, mid, m); - mrb_method_added(mrb, tc, mid); + mid = vm_define_method(mrb, tc, irep, b, c); ci = mrb->c->ci; mrb_gc_arena_restore(mrb, ai); regs[a] = mrb_symbol_value(mid); diff --git a/test/bintest.rb b/test/bintest.rb index 94bdf10cab..e5ec309ff0 100644 --- a/test/bintest.rb +++ b/test/bintest.rb @@ -1,4 +1,5 @@ $:.unshift File.dirname(File.dirname(File.expand_path(__FILE__))) +require 'shellwords' require 'test/assert.rb' GEMNAME = "" @@ -16,7 +17,7 @@ def cmd_list(s) path_list = [cmd_bin(s)] emu = ENV['EMULATOR'] - path_list.unshift emu if emu && !emu.empty? + path_list.unshift(*Shellwords.split(emu)) if emu && !emu.empty? path_list end diff --git a/test/t/array.rb b/test/t/array.rb index ab34264f0e..bccdbb8159 100644 --- a/test/t/array.rb +++ b/test/t/array.rb @@ -107,6 +107,17 @@ class SubArray < Array a = [1,2,3] a[-1,0] = a assert_equal([1,2,1,2,3,3], a) + + # passing self with length above ARY_REPLACE_SHARED_MIN (=20). + # ary_dup -> ary_replace converts the source to shared as a + # copy-on-write optimization; without re-modifying `a` afterwards, + # ARY_CAPA(a) reads from aux.shared's pointer bits and the + # expand-capa check silently mis-sizes -> heap-buffer-overflow in + # value_move. Reported via clusterfuzz mruby_fuzzer. + a = (0..30).to_a + a[3, 2] = a + assert_equal(60, a.length) + assert_equal([0, 1, 2] + (0..30).to_a + (5..30).to_a, a) end assert('Array#clear', '15.2.12.5.6') do diff --git a/test/t/class.rb b/test/t/class.rb index 65eb5edd4d..fcdb7a88b0 100644 --- a/test/t/class.rb +++ b/test/t/class.rb @@ -503,3 +503,39 @@ class BarBeingExtended assert_true BarBeingExtended.respond_to?(:answer) assert_equal 42, BarBeingExtended.answer end + +assert("inherited hook runs before block body") do + class A + def self.values + @values ||= [] + end + + def self.inherited(mod) + mod.values << 1 + end + end + + klass = Class.new(A) do + self.values << 2 + end + + assert_equal [1, 2], klass.values +end + +assert("inherited hook runs before class body") do + class A + def self.values + @values ||= [] + end + + def self.inherited(mod) + mod.values << 1 + end + end + + class B < A + self.values << 2 + end + + assert_equal [1, 2], B.values +end diff --git a/test/t/codegen.rb b/test/t/codegen.rb index c4e031bd39..1e4d375594 100644 --- a/test/t/codegen.rb +++ b/test/t/codegen.rb @@ -194,3 +194,29 @@ class << Object.new end end end + +assert('bare `nil?` in if/unless uses self as receiver (#6874)') do + klass = Class.new do + def unless_form + reached = false + unless nil? + reached = true + end + reached + end + + def if_form + if nil? + :yes + else + :no + end + end + end + + assert_true klass.new.unless_form + assert_equal :no, klass.new.if_form + # Sanity: explicit literal nil receiver still optimized correctly. + result = if nil.nil? then :yes else :no end + assert_equal :yes, result +end diff --git a/test/t/float.rb b/test/t/float.rb index 72945d250d..73ca7cf030 100644 --- a/test/t/float.rb +++ b/test/t/float.rb @@ -293,4 +293,13 @@ def check_floats(exp, act) assert_equal(0.0, f.abs) end +assert('Float literal underflow') do + # Regression: float literals with exponents below POW10_MIN used to + # index pow10_tab out of bounds in mrb_read_float. They must round + # cleanly to 0.0. + assert_equal 0.0, 1.0e-400 + assert_equal 0.0, 9.99e-344 + assert_equal(-0.0, -92170141183460469231731687303715884105729e-383) +end + end # const_defined?(:Float) diff --git a/test/t/gc.rb b/test/t/gc.rb index 4b800e945a..3ea6b7ef8c 100644 --- a/test/t/gc.rb +++ b/test/t/gc.rb @@ -33,6 +33,56 @@ end end +assert('GC.step_limit=') do + origin = GC.step_limit + begin + assert_equal 0, origin # default: unlimited + assert_equal 512, (GC.step_limit = 512) + assert_equal 512, GC.step_limit + assert_equal 0, (GC.step_limit = 0) # back to unlimited + ensure + GC.step_limit = origin + end +end + +assert('GC.step_limit - GC completes with small limit') do + origin = GC.step_limit + begin + GC.step_limit = 64 + # GC should still complete even with a small step limit + GC.start + assert_true GC.stat[:live] > 0 + ensure + GC.step_limit = origin + end +end + +assert('GC.malloc_threshold=') do + origin = GC.malloc_threshold + begin + assert_equal 0, origin # default: disabled + assert_equal 65536, (GC.malloc_threshold = 65536) + assert_equal 65536, GC.malloc_threshold + assert_equal 0, (GC.malloc_threshold = 0) # back to disabled + ensure + GC.malloc_threshold = origin + end +end + +assert('GC.malloc_threshold - triggers GC on large allocations') do + origin = GC.malloc_threshold + begin + GC.malloc_threshold = 4096 + GC.start # reset malloc_increase + # allocate large strings to exceed threshold + 100.times { "x" * 1024 } + stat = GC.stat + assert_true stat[:malloc_increase] >= 0 + ensure + GC.malloc_threshold = origin + end +end + assert('GC.generational_mode=') do origin = GC.generational_mode begin diff --git a/test/t/integer.rb b/test/t/integer.rb index 1767f06daa..b51826829a 100644 --- a/test/t/integer.rb +++ b/test/t/integer.rb @@ -129,6 +129,18 @@ assert_equal 6, 5 ^ 3 end +assert('Integer bitwise ops reject non-Integer operands') do + # A non-Integer operand has no bit pattern to combine, so &, |, ^ raise + # TypeError instead of silently reading garbage (Float used to return a + # bogus value via an unchecked union access). + assert_raise(TypeError) { 5 & 5.0 } + assert_raise(TypeError) { 5 | 5.0 } + assert_raise(TypeError) { 5 ^ 5.0 } + assert_raise(TypeError) { 5 | "3" } + assert_raise(TypeError) { 5 & nil } + assert_raise(TypeError) { 5 ^ :sym } +end + assert('Integer#<<', '15.2.8.3.12') do # Left Shift by one # 00010111 (23) diff --git a/test/t/literals.rb b/test/t/literals.rb index 03f0cb699b..de30e816ba 100644 --- a/test/t/literals.rb +++ b/test/t/literals.rb @@ -383,4 +383,20 @@ assert_equal :'{foo bar}', h end +assert('operator override with negative integer literal', '#2557') do + cls = Class.new { + def +(x); ['add', x]; end + def -(x); ['sub', x]; end + } + q = cls.new + assert_equal ['add', 5], q + 5 + assert_equal ['add', -5], q + -5 + assert_equal ['sub', 5], q - 5 + assert_equal ['sub', -5], q - -5 + assert_equal ['add', 500], q + 500 + assert_equal ['add', -500], q + -500 + assert_equal ['sub', 500], q - 500 + assert_equal ['sub', -500], q - -500 +end + # Not Implemented ATM assert('Literals Regular expression', '8.7.6.5') do diff --git a/test/t/proc.rb b/test/t/proc.rb index 417213dc29..f9c4b4be1d 100644 --- a/test/t/proc.rb +++ b/test/t/proc.rb @@ -179,6 +179,18 @@ def m(&b) b end end end +assert('#6345: dup of a block from method is treated as orphan') do + def m(&b) b.dup end + + # The dup is orphan, so calling it raises LocalJumpError on break. + assert_raise LocalJumpError do + m { break 1 }.call + end + + # A dup of a block without break still returns normally. + assert_equal 42, m { 42 }.call +end + assert('identity check for proc object') do b = [] t = 2 diff --git a/test/t/syntax.rb b/test/t/syntax.rb index d051bc789c..5f43a1bfa2 100644 --- a/test/t/syntax.rb +++ b/test/t/syntax.rb @@ -981,6 +981,32 @@ def self.cs3(x) = s3 x + 1 assert_equal :other, result end +assert('pattern matching - string literal patterns') do + result = case "hello" + in "hello" then :match + end + assert_equal :match, result + + # double-quoted vs single-quoted should both work + result = case 'world' + in "world" then :double + end + assert_equal :double, result + + # alternation with strings + result = case "ab" + in "ab" | "cd" then :alt + end + assert_equal :alt, result + + # string interpolation in pattern + expected = "lo" + result = case "hello" + in "hel#{expected}" then :interp + end + assert_equal :interp, result +end + assert('pattern matching - array patterns') do # simple array pattern case [1, 2, 3] @@ -1017,6 +1043,21 @@ def self.cs3(x) = s3 x + 1 x end assert_equal 3, result + + # array literal with splat as case value (#6854): + # the array-literal-length optimization must bail out for splat, + # since the runtime length is unknown statically. + a = [1, 2] + result = case [*a] + in [1, 2] then :match + else :nomatch + end + assert_equal :match, result + + # same bug in one-line `in` pattern + assert_true ([*a] in [1, 2]) + assert_false ([*a] in [1, 2, 3]) + assert_true ([1, *a, 4] in [1, 1, 2, 4]) end assert('pattern matching - find patterns') do diff --git a/test/t/vformat.rb b/test/t/vformat.rb index 956870e025..06a30a9694 100644 --- a/test/t/vformat.rb +++ b/test/t/vformat.rb @@ -42,7 +42,7 @@ class << v assert_equal '`S`: {a: 1, "b" => "c"}', vf.v('`S`: %S', {a: 1, "b" => ?c}) assert_equal 'percent: %', vf.z('percent: %%') assert_equal '"I": inspect char', vf.c('%!c: inspect char', ?I) - assert_equal '709: inspect mrb_int', vf.i('%!d: inspect mrb_int', 709) + assert_equal '709: inspect mrb_int', vf.i('%!i: inspect mrb_int', 709) assert_equal '"a\x00b\xff"', vf.l('%!l', "a\000b\xFFc\000d", 4) assert_equal ':"&.": inspect symbol', vf.n('%!n: inspect symbol', :'&.') assert_equal 'inspect "String"', vf.v('inspect %!v', 'String') diff --git a/tools/gen_pow10_tab.rb b/tools/gen_pow10_tab.rb new file mode 100755 index 0000000000..07cec1885d --- /dev/null +++ b/tools/gen_pow10_tab.rb @@ -0,0 +1,68 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Generate pow10 table for mruby unrounded scaling (fp_uscale.c) +# +# Uses exact integer arithmetic only. +# For each p in [-343, 341], computes (hi, lo) such that: +# 10^p ~= (hi * 2^64 - lo) * 2^pe +# where pe = floor(p * log2(10)) - 127 +# +# The 128-bit value pm = hi * 2^64 - lo is in [2^127, 2^128). + +POW10_MIN = -343 +POW10_MAX = 341 + +def generate_entry(p) + if p >= 0 + val = 10**p + bit_len = val.bit_length + pe = bit_len - 128 + if pe >= 0 + mask = (1 << pe) - 1 + pm = (val >> pe) + ((val & mask) != 0 ? 1 : 0) + else + pm = val << (-pe) + end + else + abs_p = -p + denom = 10**abs_p + # pe = floor(p * log2(10)) - 127 + # Ruby's integer division of negative numbers does floor division + pe_est = (p * 108853 >> 15) - 127 + + numerator = 1 << (-pe_est) + pm = (numerator + denom - 1) / denom + + # Adjust pe if pm is out of range [2^127, 2^128) + while pm >= (1 << 128) + pe_est += 1 + numerator = 1 << (-pe_est) + pm = (numerator + denom - 1) / denom + end + while pm < (1 << 127) + pe_est -= 1 + numerator = 1 << (-pe_est) + pm = (numerator + denom - 1) / denom + end + end + + raise "pm out of range for p=#{p}: #{pm.bit_length}" unless pm.bit_length == 128 + + hi = (pm >> 64) + ((pm & ((1 << 64) - 1)) != 0 ? 1 : 0) + lo = (hi << 64) - pm + + raise "hi out of range for p=#{p}" unless hi >= (1 << 63) && hi < (1 << 64) + + { hi: hi, lo: lo } +end + +def main + entries = (POW10_MIN..POW10_MAX).map { |p| [p, generate_entry(p)] } + + entries.each do |p, e| + printf(" {0x%016xULL, 0x%016xULL},\n", e[:hi], e[:lo]) + end +end + +main