diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index deac7ac12..198276e73 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,7 @@ jobs: LD: ${{ matrix.cc }} steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Ruby version run: ruby -v - name: Compiler version @@ -46,7 +46,7 @@ jobs: MRUBY_CONFIG: ci/msvc steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Ruby version run: ruby -v - name: Build and test diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml deleted file mode 100644 index 2c3a718f8..000000000 --- a/.github/workflows/claude-code-review.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Claude Code Review - -on: - pull_request: - types: [opened, synchronize] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" - -jobs: - claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code Review - id: claude-review - uses: anthropics/claude-code-action@beta - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) - # model: "claude-opus-4-20250514" - - # Direct prompt for automated review (no @claude mention needed) - direct_prompt: | - Please review this pull request and provide feedback on: - - Code quality and best practices - - Potential bugs or issues - - Performance considerations - - Security concerns - - Test coverage - - Be constructive and helpful in your feedback. - - # Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR - # use_sticky_comment: true - - # Optional: Customize review based on file types - # direct_prompt: | - # Review this PR focusing on: - # - For TypeScript files: Type safety and proper interface usage - # - For API endpoints: Security, input validation, and error handling - # - For React components: Performance, accessibility, and best practices - # - For tests: Coverage, edge cases, and test quality - - # Optional: Different prompts for different authors - # direct_prompt: | - # ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' && - # 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' || - # 'Please provide a thorough code review focusing on our coding standards and best practices.' }} - - # Optional: Add specific tools for running tests or linting - # allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)" - - # Optional: Skip review for certain conditions - # if: | - # !contains(github.event.pull_request.title, '[skip-review]') && - # !contains(github.event.pull_request.title, '[WIP]') diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index fab4b58bc..000000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@beta - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # This is an optional setting that allows Claude to read CI results on PRs - additional_permissions: | - actions: read - - # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) - # model: "claude-opus-4-20250514" - - # Optional: Customize the trigger phrase (default: @claude) - # trigger_phrase: "/claude" - - # Optional: Trigger when specific user is assigned to an issue - # assignee_trigger: "claude-bot" - - # Optional: Allow Claude to run specific commands - # allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)" - - # Optional: Add custom instructions for Claude to customize its behavior for your project - # custom_instructions: | - # Follow our coding standards - # Ensure all new code has tests - # Use TypeScript for new files - - # Optional: Custom environment variables for Claude - # claude_env: | - # NODE_ENV: test diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 890b4bebb..4b9d2e09b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -18,14 +18,14 @@ jobs: language: ["actions"] steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "Security" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8d392fe17..9512a8fa0 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -16,7 +16,7 @@ jobs: LDFLAGS: --coverage steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Ruby version run: ruby -v - name: Compiler version @@ -34,7 +34,7 @@ jobs: echo \`\`\` } > "$GITHUB_STEP_SUMMARY" - name: Upload coverage report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: coverage-${{ github.sha }} path: coverage/ diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 4d1493689..0e2e4e653 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@v5 + - uses: actions/labeler@v6 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" sync-labels: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f3b4f1d68..059efac4d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install run: | python -m pip install --upgrade pip @@ -31,7 +31,7 @@ jobs: name: Run ls-lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: ls-lint/action@v2.3.1 with: config: .github/linters/.ls-lint.yml diff --git a/.github/workflows/oss-fuzz.yml b/.github/workflows/oss-fuzz.yml index c4e0b3d10..5506ef054 100644 --- a/.github/workflows/oss-fuzz.yml +++ b/.github/workflows/oss-fuzz.yml @@ -20,7 +20,7 @@ jobs: fuzz-seconds: 600 dry-run: false - name: Upload Crash - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: failure() with: name: artifacts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ab16cdfc..c9e22856a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: fail-fast: false steps: - name: "Checkout ${{ github.ref_name }} ( ${{ github.sha }} )" - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Builds id: builds run: | diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index fe1cc550c..95646bd3b 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -15,12 +15,12 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # Full git history is needed to get a proper list of changed files within `super-linter` fetch-depth: 0 - name: Lint Code Base - uses: super-linter/super-linter/slim@v8.0.0 + uses: super-linter/super-linter/slim@v8.2.1 env: # VALIDATE_BASH_EXEC: true VALIDATE_DOCKERFILE_HADOLINT: true diff --git a/.gitignore b/.gitignore index f8cecb9c4..76861bb04 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ benchmark/**/*.dat benchmark/*.pdf benchmark/*.png bin -build +/build doc/api doc/capi compile_commands.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f33dfcdaa..386d651cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,10 +2,8 @@ # https://pre-commit.com/ default_stages: [pre-commit, pre-push] default_language_version: - # force all unspecified Python hooks to run python3 python: python3 - # force all unspecified Node hooks to run Node.js v22.17.0 LTS - node: 22.17.0 + node: 22.19.0 minimum_pre_commit_version: "3.2.0" exclude: "^tools/lrama/" repos: @@ -28,12 +26,12 @@ repos: additional_dependencies: ["prettier@3.6.2"] stages: [manual] - repo: https://github.com/gitleaks/gitleaks - rev: v8.27.2 + rev: v8.28.0 hooks: - id: gitleaks name: run gitleaks description: detect hardcoded secrets with gitleaks - - repo: https://github.com/shssoichiro/oxipng + - repo: https://github.com/oxipng/oxipng rev: v9.1.5 hooks: - id: oxipng @@ -42,7 +40,7 @@ repos: args: ["--fix", "-o", "4", "--strip", "safe", "--alpha"] stages: [manual] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -112,7 +110,7 @@ repos: types: [markdown] files: \.md$ - repo: https://github.com/rubocop/rubocop - rev: v1.78.0 + rev: v1.80.2 hooks: - id: rubocop name: run rubocop @@ -120,7 +118,7 @@ repos: exclude: ^test/t/syntax\.rb$ args: [--config=.github/linters/.rubocop.yml] - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.10.0.1 + rev: v0.11.0.1 hooks: - id: shellcheck name: run shellcheck diff --git a/AUTHORS b/AUTHORS index 41fe3f198..2024d2d90 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,14 +1,14 @@ # Authors of mruby (mruby developers) -## The List of Contributors sorted by number of commits (as of 2025-06-03 a3e7280) +## The List of Contributors sorted by number of commits (as of 2025-08-04 1562f10) - 6428 Yukihiro "Matz" Matsumoto (@matz)* - 683 dearblue (@dearblue)* + 6640 Yukihiro "Matz" Matsumoto (@matz)* + 690 dearblue (@dearblue)* 587 KOBAYASHI Shuji (@shuujii) 353 Daniel Bovensiepen (@bovi)* 345 Takeshi Watanabe (@take-cheeze)* 333 Masaki Muranaka (@monaka) - 238 John Bampton (@jbampton) + 242 John Bampton (@jbampton) 234 Jun Hiroe (@suzukaze) 228 Tomoyuki Sahara (@tsahara)* 220 Cremno (@cremno)* @@ -129,6 +129,7 @@ 4 Marcus Stollsteimer (@stomar) 4 Mark Delk (@jethrodaniel) 4 NARUSE, Yui (@nurse) + 4 Paweł Świątkowski (@katafrakt) 4 Ravil Bayramgalin (@brainopia)*+ 4 Satoshi Odawara (@SatoshiOdawara) 4 Yuhei Okazaki (@Yuuhei-Okazaki)* @@ -202,6 +203,7 @@ 2 buty4649 (@buty4649) 2 icm7216 (@icm7216) 1 A-Sat (@asatou)+ + 1 AN Long (@aisk) 1 Abinoam Praxedes Marques Junior (@abinoam) 1 Alex Wang (@nanamiwang)+ 1 AlexDenisov (@AlexDenisov) @@ -267,7 +269,6 @@ 1 Patrick Ellis (@pje) 1 Patrick Pokatilo (@SHyx0rmZ) 1 Pavel Evstigneev (@Paxa)+ - 1 Paweł Świątkowski (@katafrakt) 1 Pete Kinnecom (@petekinnecom) 1 Prayag Verma (@pra85) 1 Ranmocy (@ranmocy) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed5a36958..e99708f54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,15 @@ things in mind before submitting your pull request: - Use mrbgem to provide non ISO features (classes, modules and methods) unless you have a special reason to implement them in the core +## Security Issues + +If you discover a security vulnerability: + +- **High priority security vulnerabilities** (RCE): Report via email to +- **VM crashes from valid Ruby code**: Please report as regular bug reports on our issue tracker + +For detailed guidance on what qualifies as a security issue and what doesn't, see [SECURITY.md](SECURITY.md). + ## pre-commit A framework for managing and maintaining multi-language `pre-commit` hooks. diff --git a/Gemfile.lock b/Gemfile.lock index 21a968db2..d387508b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,7 @@ GEM remote: https://rubygems.org/ specs: coderay (1.1.3) - rake (13.3.0) + rake (13.3.1) yard (0.9.37) yard-coderay (0.1.0) coderay diff --git a/Makefile b/Makefile index d60ec5ea7..20dbf3874 100644 --- a/Makefile +++ b/Makefile @@ -11,46 +11,53 @@ define check_command } endef -all : check_rake +# For colors +ifneq ($(shell tty -s),) + CYAN := $(shell tput setaf 6) + RESET := $(shell tput sgr0) +else + CYAN := + RESET := +endif + +.PHONY: all test clean check checkinstall checkupdate composecheck composetest check_rake check_docker_compose check_pre_commit help +.DEFAULT_GOAL := all + +all: check_rake ## build all targets, install (locally) in-repo $(RAKE) -.PHONY : all -test : check_rake all +test: check_rake all ## build and run all mruby tests $(RAKE) test -.PHONY : test -clean : check_rake +clean: check_rake ## clean all built and in-repo installed artifacts $(RAKE) clean -.PHONY : clean -check : check_pre_commit +check: check_pre_commit ## run all pre-commit hooks against all files $(PRE_COMMIT) run --all-files -.PHONY : check -checkinstall : check_pre_commit +checkinstall: check_pre_commit ## install the pre-commit hooks $(PRE_COMMIT) install -.PHONY : checkinstall -checkupdate : check_pre_commit +checkupdate: check_pre_commit ## check the pre-commit hooks for updates $(PRE_COMMIT) autoupdate -.PHONY : checkupdate -composecheck : check_docker_compose check_pre_commit +composecheck: check_docker_compose check_pre_commit ## run all pre-commit hooks against all files with docker-compose $(DOCKER_COMPOSE) -p mruby run test $(PRE_COMMIT) run --all-files -.PHONY : composecheck -composetest : check_docker_compose +composetest: check_docker_compose ## build and run all mruby tests with docker-compose $(DOCKER_COMPOSE) -p mruby run test -.PHONY : composetest -check_rake: +check_rake: ## check if Rake is installed $(call check_command, $(RAKE)) -.PHONY : check_rake -check_docker_compose: +check_docker_compose: ## check if docker-compose is installed $(call check_command, $(DOCKER_COMPOSE)) -.PHONY : check_docker_compose -check_pre_commit: +check_pre_commit: ## check if pre-commit is installed $(call check_command, $(PRE_COMMIT)) -.PHONY : check_pre_commit + +help: ## display this help message + @echo "Usage: make " + @echo + @echo "Available targets:" + @grep -E '^[a-z_-]+:.*##' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## *"}; {printf " $(CYAN)%-20s$(RESET) %s\n", $$1, $$2}' diff --git a/Rakefile b/Rakefile index 79ec84f67..c7488bc65 100644 --- a/Rakefile +++ b/Rakefile @@ -16,6 +16,11 @@ require "mruby/build" MRUBY_CONFIG = MRuby::Build.mruby_config_path load MRUBY_CONFIG +# set up all gems +MRuby.each_target do + gems.setup(self) if enable_gems? +end + # load basic rules MRuby.each_target do |build| build.define_rules diff --git a/SECURITY.md b/SECURITY.md index bd80d2dd9..574749e40 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,19 +2,53 @@ ## Reporting a Vulnerability -If you have any security concern, contact . +To report a security vulnerability, please email the mruby team at . We appreciate your efforts to disclose your findings responsibly. ## Scope -We consider the following issues as vulnerabilities: +mruby is an embeddable Ruby implementation. Its security model is designed for integration into a host application, which is responsible for sandboxing and resource management. This policy defines what we consider a security vulnerability within the mruby interpreter itself. -- Remote code execution -- Crash caused by a valid Ruby script +### High Priority Security Vulnerabilities -We _don't_ consider the following issues as vulnerabilities: +We consider the following issues to be **high priority security vulnerabilities**: -- Runtime C undefined behavior (including integer overflow) -- Crash caused by misused API -- Crash caused by modified compiled binary -- ASAN/Valgrind warning for too big memory allocation - mruby assumes `malloc(3)` returns `NULL` for too big allocations +- **Remote Code Execution (RCE)**: The ability to execute arbitrary machine code or shell commands from within a Ruby script, beyond the intended execution scope of the script itself. + +### Lower Priority: Crashes (Preferably Report as Bugs) + +We **accept but deprioritize** the following issues. We recommend reporting them as **bug reports** on our issue tracker rather than security reports: + +- **VM Crash on Valid Ruby Code**: Segmentation faults, assertion failures, or other interpreter crashes triggered by syntactically and semantically valid Ruby scripts. + - _Recommendation_: Please report these as bugs on our issue tracker. + - _Rationale_: While we will fix these issues, they typically only result in denial of service (DoS), not arbitrary code execution. They are lower priority than RCE vulnerabilities. + - _Note_: This does not include standard Ruby exceptions like `TypeError` or `ZeroDivisionError`, which are expected behavior. + - _Example_: A segmentation fault when running `[1, 2, 3].map { |x| x * 2 }` is best reported as a bug. + +### Out of Scope: Not Considered Security Vulnerabilities + +We do **not** consider the following issues to be security vulnerabilities: + +- **Resource Exhaustion**: Infinite loops, excessive memory allocation, or high CPU usage originating from a Ruby script. + - _Rationale_: The host application is responsible for implementing resource limits, sandboxing, and execution timeouts. mruby provides the execution engine; the host provides the constraints. + - _Example_: `loop {}` or `"a" * (2**30)` are not vulnerabilities, even if they lead to memory or CPU exhaustion. + +- **Crashes from Malformed Bytecode**: Crashes resulting from loading or executing corrupted or intentionally malformed `.mrb` files. + - _Rationale_: mruby's bytecode format is not a security boundary. Applications should only execute bytecode from trusted sources. + - _Example_: A crash discovered by fuzzing `.mrb` files is not considered a vulnerability. + +- **Crashes from C API Misuse**: Crashes caused by incorrect usage of mruby's C API from the embedding application. + - _Rationale_: The C API is a trusted interface for developers. The caller is responsible for adhering to the API contract (e.g., not passing `NULL` pointers, managing object lifetimes correctly). + - _Example_: Calling `mrb_funcall()` with an invalid `mrb_state*` pointer is not a vulnerability. + +- **Theoretical Undefined Behavior (UB)**: Issues reported by tools like ASAN, UBSan, or Valgrind that do not lead to a demonstrable crash or exploitable behavior in practice. + - _Rationale_: While we strive for clean, well-defined code, our focus is on practical security impact. We prioritize fixing UB that is exploitable over issues that are purely theoretical. + - _Example_: An integer overflow in an intermediate calculation that gets handled correctly before affecting program output or control flow. + +- **Warnings on Large Memory Allocations**: Tooling warnings related to large memory allocations that do not result in a crash. + - _Rationale_: mruby is designed to handle `malloc(3)` returning `NULL` on large allocation requests. This is considered graceful error handling, not a vulnerability. + +### Summary + +- **High Priority Security Reports**: Remote code execution vulnerabilities. +- **Accepted (but preferably as bug reports)**: VM crashes from valid Ruby code. +- **Not Accepted as Security Issues**: Resource exhaustion, malformed bytecode, C API misuse, theoretical undefined behavior, or allocation warnings. diff --git a/build_config/dreamcast_shelf.rb b/build_config/dreamcast_shelf.rb index 3427f83ac..e65465f48 100644 --- a/build_config/dreamcast_shelf.rb +++ b/build_config/dreamcast_shelf.rb @@ -5,12 +5,16 @@ # http://gamedev.allusion.net/softprj/kos/ # # This configuration has been improved to be used as KallistiOS Port (kos-ports) -# Updated: 2023-12-24 +# Updated: 2025-07-31 # # Tested on GNU/Linux, macOS and Windows (MinGW-w64/MSYS2, Cygwin, DreamSDK) -# DreamSDK is based on MinGW/MSYS: https://dreamsdk.org/ +# DreamSDK is based on both MinGW/MSYS and MinGW-w64/MSYS2: https://dreamsdk.org/ # # Install mruby for Sega Dreamcast using the "mruby" kos-port. +# See: https://github.com/kallistios/kallistios +# +# If you want to see examples, check the /examples/dreamcast/mruby directory +# in the KallistiOS repository. # MRuby::CrossBuild.new("dreamcast") do |conf| toolchain :gcc @@ -19,42 +23,37 @@ MRuby::CrossBuild.new("dreamcast") do |conf| KOS_BASE = ENV["KOS_BASE"] KOS_CC_BASE = ENV["KOS_CC_BASE"] - if (KOS_BASE.nil? || KOS_BASE.empty? || KOS_CC_BASE.nil? || KOS_CC_BASE.empty?) - raise "Error: KallistiOS is required; KOS_BASE/KOS_CC_BASE needs to be declared; Stop." + # Check environment variables + if KOS_BASE.to_s.empty? + raise "Error: KallistiOS is required; KOS_BASE need to be declared; Stop." end + # Root directory for KallistiOS wrappers + # This will handle specific DreamSDK wrappers if needed + KOS_WRAPPERS_BASE = if ENV["ENVIRONMENT_NAME"] == "DreamSDK" && ENV["RAKE_AVAILABLE"] == "0" + "#{KOS_CC_BASE}/bin" + else + "#{KOS_BASE}/utils/build_wrappers" + end + # C compiler - # All flags and settings below were extracted from KallistiOS environment files conf.cc do |cc| - cc.command = "#{KOS_CC_BASE}/bin/sh-elf-gcc" - cc.include_paths << ["#{KOS_BASE}/include", "#{KOS_BASE}/kernel/arch/dreamcast/include", "#{KOS_BASE}/addons/include", "#{KOS_BASE}/../kos-ports/include"] - cc.flags << ["-O2", "-fomit-frame-pointer", "-fno-builtin", "-ml", "-m4-single-only", "-ffunction-sections", "-fdata-sections", "-matomic-model=soft-imask", "-ftls-model=local-exec", "-Wall", "-g"] - cc.compile_options = %Q[%{flags} -o "%{outfile}" -c "%{infile}"] - cc.defines << %w(_arch_dreamcast) - cc.defines << %w(_arch_sub_pristine) + cc.command = "#{KOS_WRAPPERS_BASE}/kos-cc" end # C++ compiler conf.cxx do |cxx| - cxx.command = conf.cc.command.dup - cxx.include_paths = conf.cc.include_paths.dup - cxx.flags = conf.cc.flags.dup - cxx.flags << %w(-fno-operator-names) - cxx.defines = conf.cc.defines.dup - cxx.compile_options = conf.cc.compile_options.dup + cxx.command = "#{KOS_WRAPPERS_BASE}/kos-c++" end # Linker conf.linker do |linker| - linker.command = "#{KOS_CC_BASE}/bin/sh-elf-gcc" - linker.flags << ["-Wl,-Ttext=0x8c010000", "-Wl,--gc-sections", "-T#{KOS_BASE}/utils/ldscripts/shlelf.xc", "-nodefaultlibs", "-Wl,--start-group -lkallisti -lc -lgcc -Wl,--end-group"] - linker.library_paths << ["#{KOS_BASE}/lib/dreamcast", "#{KOS_BASE}/addons/lib/dreamcast", "#{KOS_BASE}/../kos-ports/lib"] + linker.command = "#{KOS_WRAPPERS_BASE}/kos-ld" end # Archiver conf.archiver do |archiver| - archiver.command = "#{KOS_CC_BASE}/bin/sh-elf-ar" - archiver.archive_options = 'rcs "%{outfile}" %{objs}' + archiver.command = "#{KOS_WRAPPERS_BASE}/kos-ar" end # No executables needed for KallistiOS diff --git a/doc/guides/mrbgems.md b/doc/guides/mrbgems.md index 7087965e7..d7104821e 100644 --- a/doc/guides/mrbgems.md +++ b/doc/guides/mrbgems.md @@ -256,7 +256,7 @@ When more than one version requirements is passed, the dependency must satisfy a You can have default gem to use as dependency when it's not defined in your build configuration. When the last argument of `add_dependency` call is `Hash`, it will be treated as default gem information. -Its format is same as argument of method `MRuby::Build#gem`, expect that it can't be treated as path gem location. +Its format is same as argument of method `MRuby::Build#gem`, except that it can't be treated as path gem location. When a special version of dependency is required, use `MRuby::Build#gem` in the build configuration to override default gem. @@ -304,12 +304,34 @@ Your GEM can export include paths to another GEMs that depends on your GEM. By default, `/...absolute path.../{GEM_NAME}/include` will be exported. So it is recommended not to put GEM's local header files on include/. -These exports are retroactive. +These exports are transitive. For example: when B depends on C and A depends on B, A will get include paths exported by C. -Exported include_paths are automatically appended to GEM local include_paths by rake. +Exported `include_paths` are automatically appended to GEM local `include_paths` by rake. You can use `spec.export_include_paths` accessor if you want more complex build. +### Settings for GEM build commands/tasks + +When the block argument passed to `MRuby::Gem::Specification.new` is executed, +the GEM build commands/tasks for the `MRuby::Build` instance may not yet be finalized. +In most cases, modifying the GEM build commands/tasks within the block passed to +`MRuby::Gem::Specification.new` is not a problem. + +However, you may need to perform GEM build commands/tasks after the GEM build +commands/tasks for the `MRuby::Build` instance have been finalized. +In such cases, you can achieve this by passing a block argument to +`MRuby::Gem::Specification#build_settings` within the block passed to +`MRuby::Gem::Specification.new`. + +```ruby +spec.build_settings do + spec.cc.flags << "-any_flags" +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. + ## C Extension mruby can be extended with C. This is possible by using the C API to diff --git a/include/mrbconf.h b/include/mrbconf.h index 869c24cc2..915b5001f 100644 --- a/include/mrbconf.h +++ b/include/mrbconf.h @@ -153,6 +153,12 @@ //#define MRB_USE_DEBUG_HOOK /* hooks for debugger */ //#define MRB_USE_ALL_SYMBOLS /* Symbol.all_symbols */ +/* Symbol table configuration */ +/* Threshold for switching from linear search to hash table */ +#ifndef MRB_SYMBOL_LINEAR_THRESHOLD +#define MRB_SYMBOL_LINEAR_THRESHOLD 256 +#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 9d90869cb..a0fb07442 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -155,6 +155,7 @@ typedef uint8_t mrb_code; typedef uint32_t mrb_aspec; typedef struct mrb_irep mrb_irep; + struct mrb_state; #ifndef MRB_FIXED_STATE_ATEXIT_STACK_SIZE @@ -188,6 +189,10 @@ enum mrb_fiber_state { MRB_FIBER_TERMINATED, }; +/* Task context status aliases */ +#define MRB_TASK_CREATED MRB_FIBER_CREATED +#define MRB_TASK_STOPPED MRB_FIBER_TERMINATED + struct mrb_context { struct mrb_context *prev; @@ -242,6 +247,18 @@ struct mrb_jmpbuf; typedef void (*mrb_atexit_func)(struct mrb_state*); +#ifdef MRB_USE_TASK_SCHEDULER +struct mrb_task; + +typedef struct mrb_task_state { + struct mrb_task *queues[4]; /* Task queues (dormant, ready, waiting, suspended) */ + volatile uint32_t tick; /* Current tick count */ + volatile uint32_t wakeup_tick; /* Next wakeup tick */ + volatile mrb_bool switching; /* Context switch pending flag */ + struct mrb_task *main_task; /* Main task wrapper for root context */ +} mrb_task_state; +#endif + typedef struct mrb_state { struct mrb_jmpbuf *jmp; @@ -281,10 +298,8 @@ typedef struct mrb_state { mrb_sym symidx; const char **symtbl; - uint8_t *symlink; - uint8_t *symflags; - mrb_sym symhash[256]; size_t symcapa; + struct mrb_sym_hash_table *symhash; #ifndef MRB_USE_ALL_SYMBOLS char symbuf[8]; /* buffer for small symbol names */ #endif @@ -314,6 +329,10 @@ typedef struct mrb_state { mrb_atexit_func *atexit_stack; #endif uint16_t atexit_stack_len; + +#ifdef MRB_USE_TASK_SCHEDULER + mrb_task_state task; /* Task scheduler state */ +#endif } mrb_state; /** @@ -935,7 +954,7 @@ MRB_API struct RClass* mrb_define_module_under_id(mrb_state *mrb, struct RClass * | `I` | inline struct | void *, struct RClass | `I!` gives `NULL` for `nil` | * | `&` | block | {mrb_value} | &! raises exception if no block given. | * | `*` | rest arguments | const {mrb_value} *, {mrb_int} | Receive the rest of arguments as an array; `*!` avoid copy of the stack. | - * | \| | optional | | After this spec following specs would be optional. | + * | `\|` | optional | | After this spec following specs would be optional. | * | `?` | optional given | {mrb_bool} | `TRUE` if preceding argument is given. Used to check optional argument is given. | * | `:` | keyword args | {mrb_kwargs} const | Get keyword arguments. @see mrb_kwargs | * @@ -1251,6 +1270,39 @@ MRB_API mrb_state* mrb_open_core(void); MRB_API void mrb_close(mrb_state *mrb); MRB_API void mrb_method_cache_clear(mrb_state *mrb); +/** + * Check if mrb_open() failed + * + * @param mrb + * Pointer returned from mrb_open() or mrb_open_core(). + * @return + * Non-zero if initialization failed, 0 if succeeded. + * @note + * mrb_open() may return non-NULL even on failure (with mrb->exc set). + * Use this macro to check for failure: + * @code + * mrb_state *mrb = mrb_open(); + * if (MRB_OPEN_FAILURE(mrb)) { + * if (mrb) { + * // Inspect mrb->exc for error details + * mrb_close(mrb); + * } + * return EXIT_FAILURE; + * } + * @endcode + */ +#define MRB_OPEN_FAILURE(mrb) (!(mrb) || (mrb)->exc) + +/** + * Check if mrb_open() succeeded + * + * @param mrb + * Pointer returned from mrb_open() or mrb_open_core(). + * @return + * Non-zero if initialization succeeded, 0 if failed. + */ +#define MRB_OPEN_SUCCESS(mrb) (!MRB_OPEN_FAILURE(mrb)) + /** * The memory allocation function. You can redefine this function for your own allocator. * @@ -1308,6 +1360,7 @@ MRB_API mrb_int mrb_cmp(mrb_state *mrb, mrb_value obj1, mrb_value obj2); /* recursion detection */ MRB_API mrb_bool mrb_recursive_method_p(mrb_state *mrb, mrb_sym mid, mrb_value obj1, mrb_value obj2); +MRB_API mrb_bool mrb_recursive_func_p(mrb_state *mrb, mrb_sym mid, mrb_value obj1, mrb_value obj2); #define MRB_RECURSIVE_P(mrb, mid, obj1, obj2) \ mrb_recursive_method_p(mrb, mid, obj1, obj2) @@ -1318,6 +1371,12 @@ MRB_API mrb_bool mrb_recursive_method_p(mrb_state *mrb, mrb_sym mid, mrb_value o #define MRB_RECURSIVE_BINARY_P(mrb, mid, obj1, obj2) \ mrb_recursive_method_p(mrb, mid, obj1, obj2) +#define MRB_RECURSIVE_FUNC_P(mrb, mid, obj) \ + mrb_recursive_func_p(mrb, mid, obj, mrb_nil_value()) + +#define MRB_RECURSIVE_BINARY_FUNC_P(mrb, mid, obj1, obj2) \ + mrb_recursive_func_p(mrb, mid, obj1, obj2) + #define mrb_gc_arena_save(mrb) ((mrb)->gc.arena_idx) #define mrb_gc_arena_restore(mrb, idx) ((mrb)->gc.arena_idx = (idx)) diff --git a/include/mruby/array.h b/include/mruby/array.h index ab5232f4b..1ebba93b8 100644 --- a/include/mruby/array.h +++ b/include/mruby/array.h @@ -87,6 +87,7 @@ struct RArray { MRB_API void mrb_ary_modify(mrb_state*, struct RArray*); MRB_API mrb_value mrb_ary_dup(mrb_state*, mrb_value ary); +MRB_API mrb_value mrb_ary_make_shared_copy(mrb_state*, mrb_value ary); MRB_API mrb_value mrb_ary_new_capa(mrb_state*, mrb_int); /* diff --git a/include/mruby/compile.h b/include/mruby/compile.h index 40ee8139c..538a96e06 100644 --- a/include/mruby/compile.h +++ b/include/mruby/compile.h @@ -53,10 +53,7 @@ MRB_API void mrb_ccontext_cleanup_local_variables(mrb_ccontext *c); #define mrbc_cleanup_local_variables mrb_ccontext_cleanup_local_variables /* AST node structure */ -typedef struct mrb_ast_node { - struct mrb_ast_node *car, *cdr; - uint16_t lineno, filename_index; -} mrb_ast_node; +typedef struct mrb_ast_node mrb_ast_node; /* lexer states */ enum mrb_lex_state_enum { @@ -81,42 +78,6 @@ struct mrb_parser_message { char* message; }; -#define STR_FUNC_PARSING 0x01 -#define STR_FUNC_EXPAND 0x02 -#define STR_FUNC_REGEXP 0x04 -#define STR_FUNC_WORD 0x08 -#define STR_FUNC_SYMBOL 0x10 -#define STR_FUNC_ARRAY 0x20 -#define STR_FUNC_HEREDOC 0x40 -#define STR_FUNC_XQUOTE 0x80 - -enum mrb_string_type { - str_not_parsing = (0), - str_squote = (STR_FUNC_PARSING), - str_dquote = (STR_FUNC_PARSING|STR_FUNC_EXPAND), - str_regexp = (STR_FUNC_PARSING|STR_FUNC_REGEXP|STR_FUNC_EXPAND), - str_sword = (STR_FUNC_PARSING|STR_FUNC_WORD|STR_FUNC_ARRAY), - str_dword = (STR_FUNC_PARSING|STR_FUNC_WORD|STR_FUNC_ARRAY|STR_FUNC_EXPAND), - str_ssym = (STR_FUNC_PARSING|STR_FUNC_SYMBOL), - str_ssymbols = (STR_FUNC_PARSING|STR_FUNC_SYMBOL|STR_FUNC_ARRAY), - str_dsymbols = (STR_FUNC_PARSING|STR_FUNC_SYMBOL|STR_FUNC_ARRAY|STR_FUNC_EXPAND), - str_heredoc = (STR_FUNC_PARSING|STR_FUNC_HEREDOC), - str_xquote = (STR_FUNC_PARSING|STR_FUNC_XQUOTE|STR_FUNC_EXPAND), -}; - -/* heredoc structure */ -struct mrb_parser_heredoc_info { - mrb_bool allow_indent:1; - mrb_bool remove_indent:1; - mrb_bool line_head:1; - size_t indent; - mrb_ast_node *indented; - enum mrb_string_type type; - const char *term; - int term_len; - mrb_ast_node *doc; -}; - #define MRB_PARSER_TOKBUF_MAX (UINT16_MAX-1) #define MRB_PARSER_TOKBUF_SIZE 256 @@ -172,6 +133,7 @@ struct mrb_parser_state { uint16_t filename_table_length; uint16_t current_filename_index; + /* Variable-sized node management */ mrb_ast_node *nvars; }; diff --git a/include/mruby/internal.h b/include/mruby/internal.h index a9e43848f..3ec83fa3a 100644 --- a/include/mruby/internal.h +++ b/include/mruby/internal.h @@ -258,6 +258,17 @@ void mrb_bint_copy(mrb_state *mrb, mrb_value x, mrb_value y); size_t mrb_bint_memsize(mrb_value x); mrb_value mrb_bint_hash(mrb_state *mrb, mrb_value x); mrb_value mrb_bint_sqrt(mrb_state *mrb, mrb_value x); +mrb_int mrb_bint_size(mrb_state *mrb, mrb_value bint); +mrb_value mrb_bint_from_bytes(mrb_state *mrb, const uint8_t *bytes, mrb_int len); +mrb_int mrb_bint_sign(mrb_state *mrb, mrb_value bint); +mrb_value mrb_bint_gcd(mrb_state *mrb, mrb_value x, mrb_value y); +mrb_value mrb_bint_lcm(mrb_state *mrb, mrb_value x, mrb_value y); +mrb_value mrb_bint_abs(mrb_state *mrb, mrb_value x); +#endif + +#ifdef MRB_USE_TASK_SCHEDULER +/* GC marking for task scheduler */ +void mrb_task_mark_all(mrb_state *mrb); #endif #endif /* MRUBY_INTERNAL_H */ diff --git a/include/mruby/khash.h b/include/mruby/khash.h index 46cd91d9a..4b846177d 100644 --- a/include/mruby/khash.h +++ b/include/mruby/khash.h @@ -20,12 +20,13 @@ MRB_BEGIN_DECL typedef uint32_t khint_t; typedef khint_t khiter_t; -#ifndef KHASH_DEFAULT_SIZE -# define KHASH_DEFAULT_SIZE 8 +#ifndef KHASH_INITIAL_SIZE +# define KHASH_INITIAL_SIZE 8 #endif #define KHASH_MIN_SIZE 8 +#define KHASH_SMALL_LIMIT 4 -#define UPPER_BOUND(x) ((x)>>2|(x)>>1) +#define KH_UPPER_BOUND(x) ((x) - ((x)>>3)) /* 87.5% load factor */ /* extern uint8_t __m[]; */ @@ -48,7 +49,21 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0}; v++;\ } while (0) #define khash_mask(h) ((h)->n_buckets-1) -#define khash_upper_bound(h) (UPPER_BOUND((h)->n_buckets)) +#define khash_upper_bound(h) (KH_UPPER_BOUND((h)->n_buckets)) + +/* BREAKING CHANGE: khash structure optimized for 50% memory reduction + * + * The structure now uses a single data pointer instead of separate keys, + * vals, and ed_flags pointers, reducing size from 32 to 16 bytes. + * + * MIGRATION REQUIRED for field access macros: + * - OLD: kh_key(h, x) NEW: kh_key(typename, h, x) + * - OLD: kh_val(h, x) NEW: kh_val(typename, h, x) + * - OLD: kh_exist(h, x) NEW: kh_exist(typename, h, x) + * - OLD: KHASH_FOREACH() NEW: KHASH_FOREACH(typename, ...) + * + * Function-style macros (kh_get, kh_put, etc.) remain unchanged. + */ /* declare struct kh_xxx and kh_xxx_funcs @@ -59,13 +74,22 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0}; */ #define KHASH_DECLARE(name, khkey_t, khval_t, kh_is_map) \ typedef struct kh_##name { \ - khint_t n_buckets; \ - khint_t size; \ - uint8_t *ed_flags; \ - khkey_t *keys; \ - khval_t *vals; \ + void *data; /* Single allocation: [keys][vals][flags] */ \ + khint_t n_buckets; /* Number of buckets (power of 2) */ \ + khint_t size; /* Number of elements */ \ } kh_##name##_t; \ - void kh_alloc_##name(mrb_state *mrb, kh_##name##_t *h); \ + /* Address calculation functions for optimized memory layout */ \ + static inline khkey_t* kh_keys_##name(const kh_##name##_t *h) { \ + return (khkey_t*)(h)->data; \ + } \ + static inline khval_t* kh_vals_##name(const kh_##name##_t *h) { \ + return kh_is_map ? \ + (khval_t*)((uint8_t*)(h)->data + sizeof(khkey_t) * (h)->n_buckets) : NULL; \ + } \ + static inline uint8_t* kh_flags_##name(const kh_##name##_t *h) { \ + return (uint8_t*)(h)->data + sizeof(khkey_t) * (h)->n_buckets + \ + (kh_is_map ? sizeof(khval_t) * (h)->n_buckets : 0); \ + } \ kh_##name##_t *kh_init_##name##_size(mrb_state *mrb, khint_t size); \ kh_##name##_t *kh_init_##name(mrb_state *mrb); \ void kh_destroy_##name(mrb_state *mrb, kh_##name##_t *h); \ @@ -74,15 +98,10 @@ static const uint8_t __m_either[] = {0x03, 0x0c, 0x30, 0xc0}; khint_t kh_put_##name(mrb_state *mrb, kh_##name##_t *h, khkey_t key, int *ret); \ void kh_resize_##name(mrb_state *mrb, kh_##name##_t *h, khint_t new_n_buckets); \ void kh_del_##name(mrb_state *mrb, kh_##name##_t *h, khint_t x); \ - kh_##name##_t *kh_copy_##name(mrb_state *mrb, kh_##name##_t *h); - -static inline void -kh_fill_flags(uint8_t *p, uint8_t c, size_t len) -{ - while (len-- > 0) { - *p++ = c; - } -} + kh_##name##_t *kh_copy_##name(mrb_state *mrb, kh_##name##_t *h); \ + void kh_init_data_##name(mrb_state *mrb, kh_##name##_t *h, khint_t size); \ + void kh_destroy_data_##name(mrb_state *mrb, kh_##name##_t *h); \ + void kh_replace_##name(mrb_state *mrb, kh_##name##_t *dst, const kh_##name##_t *src); /* define kh_xxx_funcs @@ -95,64 +114,136 @@ kh_fill_flags(uint8_t *p, uint8_t c, size_t len) */ #define KHASH_DEFINE(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ mrb_noreturn void mrb_raise_nomemory(mrb_state *mrb); \ - int kh_alloc_simple_##name(mrb_state *mrb, kh_##name##_t *h) \ - { \ - khint_t sz = h->n_buckets; \ - size_t len = sizeof(khkey_t) + (kh_is_map ? sizeof(khval_t) : 0); \ - uint8_t *p = (uint8_t*)mrb_malloc_simple(mrb, sizeof(uint8_t)*sz/4+len*sz); \ - if (!p) { return 1; } \ - h->size = 0; \ - h->keys = (khkey_t*)p; \ - h->vals = kh_is_map ? (khval_t*)(p+sizeof(khkey_t)*sz) : NULL; \ - h->ed_flags = p+len*sz; \ - kh_fill_flags(h->ed_flags, 0xaa, sz/4); \ - return 0; \ + /* Internal helper functions */ \ + static inline size_t kh__kv_size_##name(khint_t count) { \ + return sizeof(khkey_t) * count + \ + (kh_is_map ? sizeof(khval_t) * count : 0); \ } \ - void kh_alloc_##name(mrb_state *mrb, kh_##name##_t *h) \ - { \ - if (kh_alloc_simple_##name(mrb, h)) { \ - mrb_raise_nomemory(mrb); \ + static inline size_t kh__htable_size_##name(khint_t n_buckets) { \ + return kh__kv_size_##name(n_buckets) + n_buckets / 4; \ + } \ + static inline void kh__mark_occupied_##name(kh_##name##_t *h, khint_t i) { \ + uint8_t *flags = kh_flags_##name(h); \ + flags[i/4] &= ~__m_either[i%4]; /* Clear both empty and deleted bits */ \ + } \ + static inline void kh__mark_deleted_##name(kh_##name##_t *h, khint_t i) { \ + uint8_t *flags = kh_flags_##name(h); \ + flags[i/4] |= __m_del[i%4]; /* Set deleted bit */ \ + } \ + static inline khint_t kh__key_idx_##name(mrb_state *mrb, khkey_t key, kh_##name##_t *h) { \ + return __hash_func(mrb, key) & khash_mask(h); \ + } \ + static inline khint_t kh__next_probe_##name(khint_t k, khint_t *step, kh_##name##_t *h) { \ + return (k+(++(*step))) & khash_mask(h); \ + } \ + static inline khint_t kh__insert_key_##name(kh_##name##_t *h, khint_t index, khkey_t key) { \ + khkey_t *keys = kh_keys_##name(h); \ + keys[index] = key; \ + kh__mark_occupied_##name(h, index); \ + h->size++; \ + return index; \ + } \ + static inline void kh__clear_flags_##name(kh_##name##_t *h, khint_t n_buckets) { \ + memset(kh_flags_##name(h), 0xaa, n_buckets/4); \ + } \ + static inline void kh__alloc_##name(mrb_state *mrb, kh_##name##_t *h) { \ + khint_t sz = h->n_buckets; \ + uint8_t *p = (uint8_t*)mrb_malloc(mrb, kh__htable_size_##name(sz)); \ + h->size = 0; \ + h->data = p; /* Single data pointer for optimized layout */ \ + kh__clear_flags_##name(h, sz); \ + } \ + /* Small table optimization functions */ \ + static inline int kh__is_small_##name(const kh_##name##_t *h) { \ + return h->n_buckets == 0; /* Small table marker */ \ + } \ + static inline khint_t kh__get_small_##name(mrb_state *mrb, kh_##name##_t *h, khkey_t key) { \ + khkey_t *keys = kh_keys_##name(h); \ + for (khint_t i = 0; i < h->size; i++) { \ + if (__hash_equal(mrb, keys[i], key)) return i; \ } \ + return h->size; /* Not found - return end position */ \ + } \ + static inline void kh__rebuild_##name(mrb_state *mrb, kh_##name##_t *h, khint_t new_n_buckets) { \ + kh_##name##_t hh; \ + hh.data = NULL; \ + hh.size = 0; \ + kh_init_data_##name(mrb, &hh, new_n_buckets); \ + /* Rehash from old 'h' to 'hh' */ \ + khkey_t *old_keys = kh_keys_##name(h); \ + khval_t *old_vals = kh_vals_##name(h); \ + uint8_t *old_flags = kh__is_small_##name(h) ? NULL : kh_flags_##name(h); \ + khint_t limit = old_flags ? h->n_buckets : h->size; \ + for (khint_t i = 0; i < limit; i++) { \ + if (old_flags && __ac_iseither(old_flags, i)) continue; \ + khint_t k = kh_put_##name(mrb, &hh, old_keys[i], NULL); \ + if (kh_is_map) { \ + kh_val(name, &hh, k) = old_vals[i]; \ + } \ + } \ + /* Final Swap */ \ + mrb_free(mrb, h->data); \ + h->data = hh.data; \ + h->n_buckets = hh.n_buckets; \ + h->size = hh.size; \ + } \ + static inline khint_t kh__put_small_##name(mrb_state *mrb, kh_##name##_t *h, khkey_t key, int *ret) { \ + /* First check if key exists */ \ + khint_t pos = kh__get_small_##name(mrb, h, key); \ + if (pos < h->size) { \ + if (ret) *ret = 0; /* Key exists */ \ + return pos; \ + } \ + /* Check if we need to convert to hash table */ \ + if (h->size >= KHASH_SMALL_LIMIT) { \ + /* Convert from small table to hash table */ \ + kh__rebuild_##name(mrb, h, KHASH_MIN_SIZE); \ + /* Now add the new key using regular hash table */ \ + return kh_put_##name(mrb, h, key, ret); \ + } \ + /* Add new element to small table */ \ + khkey_t *keys = kh_keys_##name(h); \ + keys[h->size] = key; \ + h->size++; \ + if (ret) *ret = 1; /* New key */ \ + return h->size - 1; \ } \ kh_##name##_t *kh_init_##name##_size(mrb_state *mrb, khint_t size) { \ kh_##name##_t *h = (kh_##name##_t*)mrb_calloc(mrb, 1, sizeof(kh_##name##_t)); \ - if (size < KHASH_MIN_SIZE) \ - size = KHASH_MIN_SIZE; \ - khash_power2(size); \ - h->n_buckets = size; \ - if (kh_alloc_simple_##name(mrb, h)) { \ - mrb_free(mrb, h); \ - mrb_raise_nomemory(mrb); \ - } \ + kh_init_data_##name(mrb, h, size); \ return h; \ } \ kh_##name##_t *kh_init_##name(mrb_state *mrb) { \ - return kh_init_##name##_size(mrb, KHASH_DEFAULT_SIZE); \ + return kh_init_##name##_size(mrb, KHASH_INITIAL_SIZE); \ } \ void kh_destroy_##name(mrb_state *mrb, kh_##name##_t *h) \ { \ - if (h) { \ - mrb_free(mrb, h->keys); \ - mrb_free(mrb, h); \ - } \ + kh_destroy_data_##name(mrb, h); \ + mrb_free(mrb, h); \ } \ void kh_clear_##name(mrb_state *mrb, kh_##name##_t *h) \ { \ (void)mrb; \ - if (h && h->ed_flags) { \ - kh_fill_flags(h->ed_flags, 0xaa, h->n_buckets/4); \ + if (h && h->data) { \ + kh__clear_flags_##name(h, h->n_buckets); \ h->size = 0; \ } \ } \ khint_t kh_get_##name(mrb_state *mrb, kh_##name##_t *h, khkey_t key) \ { \ - khint_t k = __hash_func(mrb,key) & khash_mask(h), step = 0; \ + if (kh__is_small_##name(h)) { \ + return kh__get_small_##name(mrb, h, key); \ + } \ + /* Cache calculated pointers for performance */ \ + khkey_t *keys = kh_keys_##name(h); \ + uint8_t *ed_flags = kh_flags_##name(h); \ + khint_t k = kh__key_idx_##name(mrb, key, h), step = 0; \ (void)mrb; \ - while (!__ac_isempty(h->ed_flags, k)) { \ - if (!__ac_isdel(h->ed_flags, k)) { \ - if (__hash_equal(mrb,h->keys[k], key)) return k; \ + while (!__ac_isempty(ed_flags, k)) { \ + if (!__ac_isdel(ed_flags, k)) { \ + if (__hash_equal(mrb, keys[k], key)) return k; \ } \ - k = (k+(++step)) & khash_mask(h); \ + k = kh__next_probe_##name(k, &step, h); \ } \ return kh_end(h); \ } \ @@ -161,38 +252,25 @@ kh_fill_flags(uint8_t *p, uint8_t c, size_t len) if (new_n_buckets < KHASH_MIN_SIZE) \ new_n_buckets = KHASH_MIN_SIZE; \ khash_power2(new_n_buckets); \ - { \ - kh_##name##_t hh; \ - uint8_t *old_ed_flags = h->ed_flags; \ - khkey_t *old_keys = h->keys; \ - khval_t *old_vals = h->vals; \ - khint_t old_n_buckets = h->n_buckets; \ - khint_t i; \ - hh.n_buckets = new_n_buckets; \ - kh_alloc_##name(mrb, &hh); \ - /* relocate */ \ - for (i=0; isize >= khash_upper_bound(h)) { \ kh_resize_##name(mrb, h, h->n_buckets*2); \ } \ - k = __hash_func(mrb,key) & khash_mask(h); \ + /* Cache calculated pointers for performance */ \ + khkey_t *keys = kh_keys_##name(h); \ + uint8_t *ed_flags = kh_flags_##name(h); \ + k = kh__key_idx_##name(mrb, key, h); \ del_k = kh_end(h); \ - while (!__ac_isempty(h->ed_flags, k)) { \ - if (!__ac_isdel(h->ed_flags, k)) { \ - if (__hash_equal(mrb,h->keys[k], key)) { \ + while (!__ac_isempty(ed_flags, k)) { \ + if (!__ac_isdel(ed_flags, k)) { \ + if (__hash_equal(mrb, keys[k], key)) { \ if (ret) *ret = 0; \ return k; \ } \ @@ -200,21 +278,17 @@ kh_fill_flags(uint8_t *p, uint8_t c, size_t len) else if (del_k == kh_end(h)) { \ del_k = k; \ } \ - k = (k+(++step)) & khash_mask(h); \ + k = kh__next_probe_##name(k, &step, h); \ } \ if (del_k != kh_end(h)) { \ /* put at del */ \ - h->keys[del_k] = key; \ - h->ed_flags[del_k/4] &= ~__m_del[del_k%4]; \ - h->size++; \ + kh__insert_key_##name(h, del_k, key); \ if (ret) *ret = 2; \ return del_k; \ } \ else { \ /* put at empty */ \ - h->keys[k] = key; \ - h->ed_flags[k/4] &= ~__m_empty[k%4]; \ - h->size++; \ + kh__insert_key_##name(h, k, key); \ if (ret) *ret = 1; \ return k; \ } \ @@ -222,23 +296,81 @@ kh_fill_flags(uint8_t *p, uint8_t c, size_t len) void kh_del_##name(mrb_state *mrb, kh_##name##_t *h, khint_t x) \ { \ (void)mrb; \ - mrb_assert(x != h->n_buckets && !__ac_iseither(h->ed_flags, x)); \ - h->ed_flags[x/4] |= __m_del[x%4]; \ - h->size--; \ + if (kh__is_small_##name(h)) { \ + /* Small table deletion: shift elements down */ \ + mrb_assert(x < h->size); \ + khkey_t *keys = kh_keys_##name(h); \ + khval_t *vals = kh_vals_##name(h); \ + for (khint_t i = x; i < h->size - 1; i++) { \ + keys[i] = keys[i + 1]; \ + if (kh_is_map) vals[i] = vals[i + 1]; \ + } \ + h->size--; \ + } \ + else { \ + /* Regular hash table deletion */ \ + mrb_assert(x != h->n_buckets && !__ac_iseither(kh_flags_##name(h), x)); \ + kh__mark_deleted_##name(h, x); \ + h->size--; \ + } \ } \ kh_##name##_t *kh_copy_##name(mrb_state *mrb, kh_##name##_t *h) \ { \ - kh_##name##_t *h2; \ - khiter_t k, k2; \ - \ - h2 = kh_init_##name(mrb); \ - for (k = kh_begin(h); k != kh_end(h); k++) { \ - if (kh_exist(h, k)) { \ - k2 = kh_put_##name(mrb, h2, kh_key(h, k), NULL); \ - if (kh_is_map) kh_value(h2, k2) = kh_value(h, k); \ - } \ - } \ + kh_##name##_t *h2 = (kh_##name##_t*)mrb_calloc(mrb, 1, sizeof(kh_##name##_t)); \ + kh_replace_##name(mrb, h2, h); \ return h2; \ + } \ + void kh_init_data_##name(mrb_state *mrb, kh_##name##_t *h, khint_t size) { \ + if (size <= KHASH_SMALL_LIMIT) { \ + /* Start as small table */ \ + h->n_buckets = 0; /* Small table marker */ \ + h->data = mrb_malloc(mrb, kh__kv_size_##name(KHASH_SMALL_LIMIT)); \ + h->size = 0; \ + } \ + else { \ + /* Start as regular hash table */ \ + if (size < KHASH_MIN_SIZE) \ + size = KHASH_MIN_SIZE; \ + khash_power2(size); \ + h->n_buckets = size; \ + kh__alloc_##name(mrb, h); \ + } \ + } \ + void kh_destroy_data_##name(mrb_state *mrb, kh_##name##_t *h) \ + { \ + if (h && h->data) { \ + mrb_free(mrb, h->data); /* Free only the data allocation */ \ + h->data = NULL; \ + } \ + } \ + void kh_replace_##name(mrb_state *mrb, kh_##name##_t *dst, const kh_##name##_t *src) \ + { \ + if (!src || (src->n_buckets == 0 && src->size == 0)) { \ + /* Empty source */ \ + kh_destroy_data_##name(mrb, dst); \ + dst->data = NULL; \ + dst->n_buckets = 0; \ + dst->size = 0; \ + } \ + else if (src->n_buckets == 0) { \ + /* Small table case */ \ + size_t data_size = kh__kv_size_##name(KHASH_SMALL_LIMIT); \ + dst->data = mrb_realloc(mrb, dst->data, data_size); \ + dst->size = src->size; \ + dst->n_buckets = 0; \ + /* Copy only the used portion of keys and values */ \ + size_t copy_size = kh__kv_size_##name(src->size); \ + memcpy(dst->data, src->data, copy_size); \ + } \ + else { \ + /* Regular hash table case */ \ + size_t data_size = kh__htable_size_##name(src->n_buckets); \ + dst->data = mrb_realloc(mrb, dst->data, data_size); \ + dst->size = src->size; \ + dst->n_buckets = src->n_buckets; \ + /* Copy the entire data block: [keys][vals][flags] */ \ + memcpy(dst->data, src->data, data_size); \ + } \ } @@ -254,13 +386,28 @@ kh_fill_flags(uint8_t *p, uint8_t c, size_t len) #define kh_get(name, mrb, h, k) kh_get_##name(mrb, h, k) #define kh_del(name, mrb, h, k) kh_del_##name(mrb, h, k) #define kh_copy(name, mrb, h) kh_copy_##name(mrb, h) +#define kh_init_data(name, mrb, h, size) kh_init_data_##name(mrb, h, size) +#define kh_destroy_data(name, mrb, h) kh_destroy_data_##name(mrb, h) +#define kh_replace(name, mrb, dst, src) kh_replace_##name(mrb, dst, src) -#define kh_exist(h, x) (!__ac_iseither((h)->ed_flags, (x))) -#define kh_key(h, x) ((h)->keys[x]) -#define kh_val(h, x) ((h)->vals[x]) -#define kh_value(h, x) ((h)->vals[x]) +/* BREAKING CHANGE: Field access macros now require type name as first parameter + * The macros keep their familiar names but now need the hash type name. + * + * MIGRATION: Add type name as first parameter: + * kh_key(h, x) -> kh_key(typename, h, x) + * kh_val(h, x) -> kh_val(typename, h, x) + * kh_exist(h, x) -> kh_exist(typename, h, x) + * kh_value(h, x) -> kh_value(typename, h, x) + */ + +/* Type-aware access macros - same names, now with type parameter */ +#define kh_exist(name, h, x) ((h)->n_buckets == 0 ? ((x) < (h)->size) : (!__ac_iseither(kh_flags_##name(h), (x)))) +#define kh_key(name, h, x) (kh_keys_##name(h)[x]) +#define kh_val(name, h, x) (kh_vals_##name(h)[x]) +#define kh_value(name, h, x) (kh_vals_##name(h)[x]) #define kh_begin(h) (khint_t)(0) -#define kh_end(h) ((h)->n_buckets) +#define kh_end(h) ((h)->n_buckets == 0 ? (h)->size : (h)->n_buckets) +#define kh_is_end(h, i) ((i) >= kh_end(h)) #define kh_size(h) ((h)->size) #define kh_n_buckets(h) ((h)->n_buckets) @@ -285,20 +432,24 @@ MRB_END_DECL * Macro for iterating over all elements in a khash. * * Usage: - * KHASH_FOREACH(mrb, kh, k) { + * KHASH_FOREACH(typename, kh, k) { * // k is the khiter_t iterator - * // Access the key with kh_key(kh, k) - * // Access the value with kh_val(kh, k) if applicable + * // Access the key with kh_key(typename, kh, k) + * // Access the value with kh_val(typename, kh, k) if applicable * // Your code here * } * - * @param mrb The mrb_state - * @param kh The khash to iterate over - * @param k The name to use for the khiter_t iterator variable + * @param name The hash type name + * @param kh The khash to iterate over + * @param k The name to use for the khiter_t iterator variable */ -#define KHASH_FOREACH(mrb, kh, k) \ +/* BREAKING CHANGE: KHASH_FOREACH now requires type name as first parameter + * OLD: KHASH_FOREACH(mrb, kh, k) + * NEW: KHASH_FOREACH(name, kh, k) + */ +#define KHASH_FOREACH(name, kh, k) \ if (kh) \ - for (khiter_t k = kh_begin(kh); k != kh_end(kh); k++) \ - if (kh_exist(kh, k)) + for (khiter_t k = kh_begin(kh); !kh_is_end(kh, k); k++) \ + if (kh_exist(name, kh, k)) #endif /* MRUBY_KHASH_H */ diff --git a/include/mruby/numeric.h b/include/mruby/numeric.h index 48adea3d1..bd64a2bb6 100644 --- a/include/mruby/numeric.h +++ b/include/mruby/numeric.h @@ -119,12 +119,12 @@ mrb_int_mul_overflow(mrb_int a, mrb_int b, mrb_int *c) *c = (mrb_int)n; return n > MRB_INT_MAX || n < MRB_INT_MIN; #else /* MRB_INT64 */ + *c = a * b; if (a > 0 && b > 0 && a > MRB_INT_MAX / b) return TRUE; if (a < 0 && b > 0 && a < MRB_INT_MIN / b) return TRUE; if (a > 0 && b < 0 && b < MRB_INT_MIN / a) return TRUE; if (a < 0 && b < 0 && (a <= MRB_INT_MIN || b <= MRB_INT_MIN || -a > MRB_INT_MAX / -b)) return TRUE; - *c = a * b; return FALSE; #endif } diff --git a/include/mruby/throw.h b/include/mruby/throw.h index f2984de69..ac8ee6fe3 100644 --- a/include/mruby/throw.h +++ b/include/mruby/throw.h @@ -33,7 +33,7 @@ typedef void *mrb_jmpbuf_impl; #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #define MRB_SETJMP _setjmp #define MRB_LONGJMP _longjmp -#elif defined(__MINGW64__) && defined(__GNUC__) && __GNUC__ >= 4 +#elif defined(__MINGW64__) && !defined(_M_ARM64) && defined(__GNUC__) && __GNUC__ >= 4 #define MRB_SETJMP __builtin_setjmp #define MRB_LONGJMP __builtin_longjmp #else diff --git a/lib/mruby/build.rb b/lib/mruby/build.rb index 9cd36aae6..ed4610840 100644 --- a/lib/mruby/build.rb +++ b/lib/mruby/build.rb @@ -60,7 +60,11 @@ module MRuby def mruby_config_path path = ENV['MRUBY_CONFIG'] || ENV['CONFIG'] if path.nil? || path.empty? - path = File.file?("./build_config.rb") ? "./build_config.rb" : "#{MRUBY_ROOT}/build_config/default.rb" + path = if Dir.pwd != MRUBY_ROOT && File.file?("./build_config.rb") + "./build_config.rb" + else + "#{MRUBY_ROOT}/build_config/default.rb" + end elsif !File.file?(path) && !Pathname.new(path).absolute? f = "#{MRUBY_ROOT}/build_config/#{path}.rb" path = File.exist?(f) ? f : File.extname(path).empty? ? f : path diff --git a/lib/mruby/build/command.rb b/lib/mruby/build/command.rb index 773166a4f..458c166b7 100644 --- a/lib/mruby/build/command.rb +++ b/lib/mruby/build/command.rb @@ -144,13 +144,13 @@ module MRuby # # === Example of +.d+ file # - # ==== Without -MP compiler flag + # ==== Without `-MP` compiler flag # # /build/host/src/array.o: /src/array.c \ # /include/mruby/common.h /include/mruby/value.h \ # /src/value_array.h # - # ==== With -MP compiler flag + # ==== With `-MP` compiler flag # # /build/host/src/array.o: /src/array.c \ # /include/mruby/common.h /include/mruby/value.h \ diff --git a/lib/mruby/build/load_gems.rb b/lib/mruby/build/load_gems.rb index e237116d8..8198abca5 100644 --- a/lib/mruby/build/load_gems.rb +++ b/lib/mruby/build/load_gems.rb @@ -32,14 +32,21 @@ module MRuby gemrake = File.join(checkout.full_gemdir, "mrbgem.rake") fail "Can't find #{gemrake}" unless File.exist?(gemrake) + current_build = MRuby::Build.current + build = self.is_a?(MRuby::Build) ? self : MRuby::Build.current + MRuby::Build.current = build Gem.current = nil - load gemrake + begin + load gemrake + ensure + MRuby::Build.current = current_build + end return nil unless Gem.current current = Gem.current # Add it to gems current.dir = checkout.full_gemdir - current.build = self.is_a?(MRuby::Build) ? self : MRuby::Build.current + current.build = build current.build_config_initializer = block gems << current diff --git a/lib/mruby/gem.rb b/lib/mruby/gem.rb index 619621e7b..1e9a3d919 100644 --- a/lib/mruby/gem.rb +++ b/lib/mruby/gem.rb @@ -53,10 +53,8 @@ module MRuby return if defined?(@bins) # return if already set up MRuby::Gem.current = self - MRuby::Build::COMMANDS.each do |command| - instance_variable_set("@#{command}", @build.send(command).clone) - end - @linker.run_attrs.each(&:clear) + reset_commands # for backward compatibility, reset the commands from the beginning. + @build_settings = nil @rbfiles = Dir.glob("#{@dir}/mrblib/**/*.rb").sort @objs = srcs_to_objs("src") @@ -192,6 +190,19 @@ module MRuby end end + def build_settings(&blk) + @build_settings = blk + end + + def setup_build + if @build_settings + # by this point, build.cc or other commands may have been modified. + # therefore, reset the commands again before calling build_settings. + reset_commands + @build_settings.call(self) + end + end + def define_gem_init_builder file "#{build_dir}/gem_init.c" => [build.mrbcfile, __FILE__] + [rbfiles].flatten do |t| mkdir_p build_dir @@ -303,6 +314,13 @@ module MRuby self end + + private def reset_commands + MRuby::Build::COMMANDS.each do |command| + instance_variable_set("@#{command}", @build.send(command).clone) + end + @linker.run_attrs.each(&:clear) + end end # Specification class Version @@ -401,7 +419,21 @@ module MRuby end end - def generate_gem_table build + def setup(build) + gemset = nil + begin + gemset_prev = gemset + self.each(&:setup) + gemset = self.setup_dependencies(build).keys.sort + end until gemset == gemset_prev + end + + def setup_build + each(&:setup_build) + self + end + + def setup_dependencies(build) gem_table = each_with_object({}) { |spec, h| h[spec.name] = spec } default_gems = {} @@ -424,6 +456,12 @@ module MRuby end end + gem_table + end + + def generate_gem_table(build) + gem_table = setup_dependencies(build) + each do |g| g.dependencies.each do |dep| name = dep[:gem] diff --git a/mrbgems/full-core.gembox b/mrbgems/full-core.gembox index fdee3268e..759c13215 100644 --- a/mrbgems/full-core.gembox +++ b/mrbgems/full-core.gembox @@ -1,6 +1,6 @@ MRuby::GemBox.new do |conf| Dir.glob("#{root}/mrbgems/mruby-*/mrbgem.rake") do |x| g = File.basename(File.dirname(x)) - conf.gem :core => g unless g =~ /^mruby-(?:bin-debugger|test)$/ + conf.gem :core => g unless g =~ /^mruby-(?:bin-debugger|test|sleep)$/ end end diff --git a/mrbgems/hal-posix-dir/mrbgem.rake b/mrbgems/hal-posix-dir/mrbgem.rake new file mode 100644 index 000000000..877b110cb --- /dev/null +++ b/mrbgems/hal-posix-dir/mrbgem.rake @@ -0,0 +1,7 @@ +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-dir/src/dir_hal.c b/mrbgems/hal-posix-dir/src/dir_hal.c new file mode 100644 index 000000000..2ad1c33c7 --- /dev/null +++ b/mrbgems/hal-posix-dir/src/dir_hal.c @@ -0,0 +1,193 @@ +/* +** dir_hal.c - POSIX HAL implementation for mruby-dir +** +** See Copyright Notice in mruby.h +** +** POSIX implementation for directory operations using standard POSIX APIs. +** Supported platforms: Linux, macOS, BSD, Unix +*/ + +#include +#include "dir_hal.h" + +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +/* On POSIX, mrb_dir_handle wraps DIR */ +struct mrb_dir_handle { + DIR *dir; +}; + +/* + * Directory Operations + */ + +mrb_dir_handle* +mrb_hal_dir_open(mrb_state *mrb, const char *path) +{ + DIR *dir = opendir(path); + if (dir == NULL) { + return NULL; + } + + mrb_dir_handle *handle = (mrb_dir_handle*)mrb_malloc(mrb, sizeof(mrb_dir_handle)); + handle->dir = dir; + return handle; +} + +int +mrb_hal_dir_close(mrb_state *mrb, mrb_dir_handle *handle) +{ + int result = closedir(handle->dir); + mrb_free(mrb, handle); + return result; +} + +const char* +mrb_hal_dir_read(mrb_state *mrb, mrb_dir_handle *handle) +{ + (void)mrb; + struct dirent *dp = readdir(handle->dir); + return dp ? dp->d_name : NULL; +} + +void +mrb_hal_dir_rewind(mrb_state *mrb, mrb_dir_handle *handle) +{ + (void)mrb; + rewinddir(handle->dir); +} + +/* + * Optional Operations + */ + +int +mrb_hal_dir_seek(mrb_state *mrb, mrb_dir_handle *handle, long pos) +{ +#if defined(__ANDROID__) + /* Android doesn't have reliable seekdir */ + (void)mrb; (void)handle; (void)pos; + errno = ENOSYS; + return -1; +#else + (void)mrb; + seekdir(handle->dir, pos); + return 0; +#endif +} + +long +mrb_hal_dir_tell(mrb_state *mrb, mrb_dir_handle *handle) +{ +#if defined(__ANDROID__) + /* Android doesn't have reliable telldir */ + (void)mrb; (void)handle; + errno = ENOSYS; + return -1; +#else + (void)mrb; + return telldir(handle->dir); +#endif +} + +/* + * Filesystem Operations + */ + +int +mrb_hal_dir_mkdir(mrb_state *mrb, const char *path, int mode) +{ + (void)mrb; + return mkdir(path, (mode_t)mode); +} + +int +mrb_hal_dir_rmdir(mrb_state *mrb, const char *path) +{ + (void)mrb; + return rmdir(path); +} + +int +mrb_hal_dir_chdir(mrb_state *mrb, const char *path) +{ + (void)mrb; + return chdir(path); +} + +int +mrb_hal_dir_getcwd(mrb_state *mrb, char *buf, size_t size) +{ + (void)mrb; + return getcwd(buf, size) ? 0 : -1; +} + +int +mrb_hal_dir_chroot(mrb_state *mrb, const char *path) +{ +#if defined(__ANDROID__) || defined(__MSDOS__) + /* Not available on these platforms */ + (void)mrb; (void)path; + errno = ENOSYS; + return -1; +#else + (void)mrb; + return chroot(path); +#endif +} + +int +mrb_hal_dir_is_directory(mrb_state *mrb, const char *path) +{ + struct stat sb; + (void)mrb; + + if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + return 1; + } + return 0; +} + +/* + * HAL Initialization/Finalization + */ + +void +mrb_hal_dir_init(mrb_state *mrb) +{ + (void)mrb; + /* No initialization needed for POSIX */ +} + +void +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-posix-io/mrbgem.rake b/mrbgems/hal-posix-io/mrbgem.rake new file mode 100644 index 000000000..bdd4913d1 --- /dev/null +++ b/mrbgems/hal-posix-io/mrbgem.rake @@ -0,0 +1,8 @@ +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-io/src/io_hal.c b/mrbgems/hal-posix-io/src/io_hal.c new file mode 100644 index 000000000..8120a00dc --- /dev/null +++ b/mrbgems/hal-posix-io/src/io_hal.c @@ -0,0 +1,578 @@ +/* +** io_hal.c - POSIX HAL implementation for mruby-io +** +** See Copyright Notice in mruby.h +** +** POSIX implementation for I/O operations using standard POSIX APIs. +** Supported platforms: Linux, macOS, BSD, Unix +*/ + +#include +#include "io_hal.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifndef __DJGPP__ +#include +#endif + +/* Maximum path length */ +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +/* + * Helper Functions + */ + +/* Convert POSIX struct stat to mrb_io_stat */ +static void +convert_stat(const struct stat *src, mrb_io_stat *dst) +{ + /* Save time values to avoid macro expansion issues */ + time_t atime = src->st_atime; + time_t mtime = src->st_mtime; + time_t ctime = src->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; + + /* Assign time values after undefinining macros */ +#undef st_atime +#undef st_mtime +#undef st_ctime + dst->st_atime = (int64_t)atime; + dst->st_mtime = (int64_t)mtime; + dst->st_ctime = (int64_t)ctime; + +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE + dst->st_blksize = (int64_t)src->st_blksize; +#else + dst->st_blksize = 512; +#endif +#ifdef HAVE_STRUCT_STAT_ST_BLOCKS + dst->st_blocks = (int64_t)src->st_blocks; +#else + dst->st_blocks = (dst->st_size + 511) / 512; +#endif +} + +/* + * File Operations + */ + +int +mrb_hal_io_stat(mrb_state *mrb, const char *path, mrb_io_stat *st) +{ + struct stat s; + (void)mrb; + + if (stat(path, &s) == -1) { + return -1; + } + convert_stat(&s, st); + return 0; +} + +int +mrb_hal_io_fstat(mrb_state *mrb, int fd, mrb_io_stat *st) +{ + struct stat s; + (void)mrb; + + if (fstat(fd, &s) == -1) { + return -1; + } + convert_stat(&s, st); + return 0; +} + +int +mrb_hal_io_lstat(mrb_state *mrb, const char *path, mrb_io_stat *st) +{ + struct stat s; + (void)mrb; + + if (lstat(path, &s) == -1) { + return -1; + } + convert_stat(&s, st); + return 0; +} + +int +mrb_hal_io_chmod(mrb_state *mrb, const char *path, uint32_t mode) +{ + (void)mrb; + return chmod(path, (mode_t)mode); +} + +uint32_t +mrb_hal_io_umask(mrb_state *mrb, int32_t mask) +{ + mode_t old; + (void)mrb; + + if (mask < 0) { + /* Just query current value */ + old = umask(0); + umask(old); + } + else { + old = umask((mode_t)mask); + } + return (uint32_t)old; +} + +int +mrb_hal_io_ftruncate(mrb_state *mrb, int fd, int64_t length) +{ + (void)mrb; + return ftruncate(fd, (off_t)length); +} + +int +mrb_hal_io_flock(mrb_state *mrb, int fd, int operation) +{ + (void)mrb; + + while (flock(fd, operation) == -1) { + if (errno == EINTR) { + continue; /* Retry on interrupt */ + } + return -1; + } + return 0; +} + +int +mrb_hal_io_unlink(mrb_state *mrb, const char *path) +{ + (void)mrb; + return unlink(path); +} + +int +mrb_hal_io_rename(mrb_state *mrb, const char *oldpath, const char *newpath) +{ + (void)mrb; + return rename(oldpath, newpath); +} + +int +mrb_hal_io_symlink(mrb_state *mrb, const char *target, const char *linkpath) +{ + (void)mrb; + return symlink(target, linkpath); +} + +int64_t +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; +} + +char* +mrb_hal_io_realpath(mrb_state *mrb, const char *path, char *resolved) +{ + (void)mrb; + return realpath(path, resolved); +} + +char* +mrb_hal_io_getcwd(mrb_state *mrb, char *buf, size_t size) +{ + (void)mrb; + return getcwd(buf, size); +} + +const char* +mrb_hal_io_getenv(mrb_state *mrb, const char *name) +{ + (void)mrb; + return getenv(name); +} + +const char* +mrb_hal_io_gethome(mrb_state *mrb, const char *username) +{ + const char *home; + + if (username == NULL || *username == '\0') { + /* Get current user's home */ + home = getenv("HOME"); + if (home == NULL) { + errno = ENOENT; + return NULL; + } + } + else { + /* Get specified user's home */ + struct passwd *pwd = getpwnam(username); + if (pwd == NULL) { + errno = ENOENT; + return NULL; + } + home = pwd->pw_dir; + } + + return home; +} + +/* + * Core I/O Operations + */ + +int +mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, uint32_t mode) +{ + int fd; + (void)mrb; + + fd = open(path, flags, (mode_t)mode); + if (fd == -1) { + return -1; + } + + /* Set close-on-exec for non-standard descriptors */ +#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) + if (fd > 2) { + int fd_flags = fcntl(fd, F_GETFD); + if (fd_flags != -1) { + fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC); + } + } +#endif + + return fd; +} + +int +mrb_hal_io_close(mrb_state *mrb, int fd) +{ + (void)mrb; + return close(fd); +} + +int64_t +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; +} + +int64_t +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; +} + +int64_t +mrb_hal_io_lseek(mrb_state *mrb, int fd, int64_t offset, int whence) +{ + off_t pos; + int posix_whence; + (void)mrb; + + /* Convert MRB_IO_SEEK_* to POSIX SEEK_* */ + switch (whence) { + case MRB_IO_SEEK_SET: posix_whence = SEEK_SET; break; + case MRB_IO_SEEK_CUR: posix_whence = SEEK_CUR; break; + case MRB_IO_SEEK_END: posix_whence = SEEK_END; break; + default: + errno = EINVAL; + return -1; + } + + pos = lseek(fd, (off_t)offset, posix_whence); + return (int64_t)pos; +} + +int +mrb_hal_io_dup(mrb_state *mrb, int fd) +{ + int new_fd; + (void)mrb; + + new_fd = dup(fd); + if (new_fd == -1) { + return -1; + } + + /* Set close-on-exec */ +#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) + if (new_fd > 2) { + int fd_flags = fcntl(new_fd, F_GETFD); + if (fd_flags != -1) { + fcntl(new_fd, F_SETFD, fd_flags | FD_CLOEXEC); + } + } +#endif + + return new_fd; +} + +int +mrb_hal_io_fcntl(mrb_state *mrb, int fd, int cmd, int arg) +{ + (void)mrb; + return fcntl(fd, cmd, arg); +} + +int +mrb_hal_io_isatty(mrb_state *mrb, int fd) +{ + (void)mrb; + return isatty(fd) ? 1 : 0; +} + +int +mrb_hal_io_pipe(mrb_state *mrb, int fds[2]) +{ + int ret; + (void)mrb; + + ret = pipe(fds); + if (ret == -1) { + return -1; + } + + /* Set close-on-exec on both ends */ +#if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) + for (int i = 0; i < 2; i++) { + int fd_flags = fcntl(fds[i], F_GETFD); + if (fd_flags != -1) { + fcntl(fds[i], F_SETFD, fd_flags | FD_CLOEXEC); + } + } +#endif + + return 0; +} + +/* + * Process Operations + */ + +int +mrb_hal_io_spawn_process(mrb_state *mrb, const char *cmd, + int stdin_fd, int stdout_fd, int stderr_fd, + int *pid) +{ + pid_t child_pid; + (void)mrb; + + /* Skip leading whitespace */ + while (*cmd == ' ' || *cmd == '\t' || *cmd == '\n') { + cmd++; + } + + if (!*cmd) { + errno = ENOENT; + return -1; + } + + child_pid = fork(); + if (child_pid == -1) { + /* Fork failed */ + return -1; + } + + if (child_pid == 0) { + /* Child process */ + + /* Redirect stdin */ + if (stdin_fd != -1) { + dup2(stdin_fd, STDIN_FILENO); + if (stdin_fd > 2) close(stdin_fd); + } + + /* Redirect stdout */ + if (stdout_fd != -1) { + dup2(stdout_fd, STDOUT_FILENO); + if (stdout_fd > 2) close(stdout_fd); + } + + /* Redirect stderr */ + if (stderr_fd != -1) { + dup2(stderr_fd, STDERR_FILENO); + if (stderr_fd > 2) close(stderr_fd); + } + + /* Close all other file descriptors */ + int max_fd = sysconf(_SC_OPEN_MAX); + if (max_fd == -1) max_fd = 1024; + for (int i = 3; i < max_fd; i++) { + close(i); + } + + /* Execute command via shell */ + execl("/bin/sh", "sh", "-c", cmd, (char*)NULL); + + /* If execl returns, it failed */ + _exit(127); + } + + /* Parent process */ + *pid = (int)child_pid; + return 0; +} + +int +mrb_hal_io_waitpid(mrb_state *mrb, int pid, int *status, int options) +{ + pid_t result; + int stat; + (void)mrb; + + result = waitpid((pid_t)pid, &stat, options); + if (result == -1) { + return -1; + } + + if (status != NULL) { + *status = stat; + } + + return (int)result; +} + +/* + * I/O Multiplexing + */ + +struct mrb_io_fdset { + fd_set fds; +}; + +mrb_io_fdset* +mrb_hal_io_fdset_alloc(mrb_state *mrb) +{ + mrb_io_fdset *fdset = (mrb_io_fdset*)mrb_malloc(mrb, sizeof(mrb_io_fdset)); + FD_ZERO(&fdset->fds); + return fdset; +} + +void +mrb_hal_io_fdset_free(mrb_state *mrb, mrb_io_fdset *fdset) +{ + if (fdset) { + mrb_free(mrb, fdset); + } +} + +void +mrb_hal_io_fdset_zero(mrb_state *mrb, mrb_io_fdset *fdset) +{ + (void)mrb; + if (fdset) { + FD_ZERO(&fdset->fds); + } +} + +void +mrb_hal_io_fdset_set(mrb_state *mrb, int fd, mrb_io_fdset *fdset) +{ + (void)mrb; + if (fdset) { + FD_SET(fd, &fdset->fds); + } +} + +int +mrb_hal_io_fdset_isset(mrb_state *mrb, int fd, mrb_io_fdset *fdset) +{ + (void)mrb; + if (fdset) { + return FD_ISSET(fd, &fdset->fds); + } + return 0; +} + +int +mrb_hal_io_select(mrb_state *mrb, int nfds, + mrb_io_fdset *readfds, + mrb_io_fdset *writefds, + mrb_io_fdset *errorfds, + mrb_io_timeval *timeout) +{ + fd_set *r = readfds ? &readfds->fds : NULL; + fd_set *w = writefds ? &writefds->fds : NULL; + fd_set *e = errorfds ? &errorfds->fds : NULL; + struct timeval *tv = NULL; + struct timeval tv_storage; + (void)mrb; + + if (timeout) { + tv_storage.tv_sec = (time_t)timeout->tv_sec; + tv_storage.tv_usec = (suseconds_t)timeout->tv_usec; + tv = &tv_storage; + } + + return select(nfds, r, w, e, tv); +} + +/* + * HAL Initialization/Finalization + */ + +void +mrb_hal_io_init(mrb_state *mrb) +{ + (void)mrb; + /* No special initialization needed for POSIX */ +} + +void +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-posix-socket/mrbgem.rake b/mrbgems/hal-posix-socket/mrbgem.rake new file mode 100644 index 000000000..14dca040a --- /dev/null +++ b/mrbgems/hal-posix-socket/mrbgem.rake @@ -0,0 +1,8 @@ +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-socket/src/socket_hal.c b/mrbgems/hal-posix-socket/src/socket_hal.c new file mode 100644 index 000000000..624dd4b7e --- /dev/null +++ b/mrbgems/hal-posix-socket/src/socket_hal.c @@ -0,0 +1,158 @@ +/* +** socket_hal.c - POSIX HAL implementation for mruby-socket +** +** See Copyright Notice in mruby.h +** +** POSIX implementation for socket operations using standard POSIX APIs. +** Supported platforms: Linux, macOS, BSD, Unix +*/ + +#include +#include +#include +#include +#include +#include "socket_hal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Socket HAL Initialization/Finalization + */ + +void +mrb_hal_socket_init(mrb_state *mrb) +{ + (void)mrb; + /* No initialization needed for POSIX sockets */ +} + +void +mrb_hal_socket_final(mrb_state *mrb) +{ + (void)mrb; + /* No cleanup needed for POSIX sockets */ +} + +/* + * Socket Control Operations + */ + +int +mrb_hal_socket_set_nonblock(mrb_state *mrb, int fd, int nonblock) +{ + (void)mrb; + + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + return -1; + } + + if (nonblock) { + flags |= O_NONBLOCK; + } + else { + flags &= ~O_NONBLOCK; + } + + if (fcntl(fd, F_SETFL, flags) == -1) { + return -1; + } + + return 0; +} + +/* + * Address Conversion Functions + */ + +const char* +mrb_hal_socket_inet_ntop(int af, const void *src, char *dst, size_t size) +{ + return inet_ntop(af, src, dst, (socklen_t)size); +} + +int +mrb_hal_socket_inet_pton(int af, const char *src, void *dst) +{ + return inet_pton(af, src, dst); +} + +/* + * Platform-Specific Socket Features + */ + +mrb_value +mrb_hal_socket_sockaddr_un(mrb_state *mrb, const char *path, size_t pathlen) +{ + struct sockaddr_un *sunp; + + if (pathlen > sizeof(sunp->sun_path) - 1) { + mrb_raisef(mrb, mrb_class_get_id(mrb, MRB_SYM(ArgumentError)), + "too long unix socket path (max: %d bytes)", + (int)sizeof(sunp->sun_path) - 1); + } + + mrb_value s = mrb_str_new_capa(mrb, sizeof(struct sockaddr_un)); + sunp = (struct sockaddr_un*)RSTRING_PTR(s); + +#ifdef HAVE_SA_LEN + sunp->sun_len = sizeof(struct sockaddr_un); +#endif + + sunp->sun_family = AF_UNIX; + memcpy(sunp->sun_path, path, pathlen); + sunp->sun_path[pathlen] = '\0'; + mrb_str_resize(mrb, s, sizeof(struct sockaddr_un)); + + return s; +} + +int +mrb_hal_socket_socketpair(mrb_state *mrb, int domain, int type, int protocol, int sv[2]) +{ + (void)mrb; + return socketpair(domain, type, protocol, sv); +} + +mrb_value +mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen) +{ + const struct sockaddr *sa = (const struct sockaddr*)sockaddr; + + if (sa->sa_family != AF_UNIX) { + mrb_raise(mrb, mrb_class_get_id(mrb, MRB_SYM(SocketError)), "need AF_UNIX address"); + } + + if (socklen < offsetof(struct sockaddr_un, sun_path) + 1) { + return mrb_str_new(mrb, "", 0); + } + + 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) +{ + (void)mrb; + /* Cleanup handled by mrb_hal_socket_final called from mruby-socket */ +} diff --git a/mrbgems/hal-posix-task/README.md b/mrbgems/hal-posix-task/README.md new file mode 100644 index 000000000..157ebdba2 --- /dev/null +++ b/mrbgems/hal-posix-task/README.md @@ -0,0 +1,102 @@ +# 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 new file mode 100644 index 000000000..570161c77 --- /dev/null +++ b/mrbgems/hal-posix-task/mrbgem.rake @@ -0,0 +1,8 @@ +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-posix-task/src/task_hal.c b/mrbgems/hal-posix-task/src/task_hal.c new file mode 100644 index 000000000..81be9361c --- /dev/null +++ b/mrbgems/hal-posix-task/src/task_hal.c @@ -0,0 +1,231 @@ +/* +** task_hal.c - POSIX HAL implementation for mruby-task +** +** See Copyright Notice in mruby.h +** +** POSIX implementation using SIGALRM and setitimer() for timer, +** and sigprocmask() for interrupt protection. +** +** Supported platforms: Linux, macOS, BSD, Unix +*/ + +#include +#include "task_hal.h" +#include +#include +#include +#include +#include + +/* Time conversion constants */ +#define NSEC_PER_MSEC 1000000ULL +#define NSEC_PER_SEC 1000000000ULL +#define USEC_PER_MSEC 1000ULL + +/* Multi-VM support */ +static mrb_state *vm_list[MRB_TASK_MAX_VMS]; +static volatile sig_atomic_t vm_count = 0; +static sigset_t alarm_mask; + +/* SIGALRM signal handler - ticks all registered VMs */ +static void +sigalrm_handler(int sig) +{ + int i; + (void)sig; + /* Tick all registered VMs */ + for (i = 0; i < vm_count; i++) { + if (vm_list[i]) { + mrb_tick(vm_list[i]); + } + } +} + +/* + * HAL Interface Implementation + */ + +void +mrb_hal_task_init(mrb_state *mrb) +{ + struct sigaction sa; + struct itimerval timer; + int i; + int vm_index = -1; + + /* Initialize task state */ + for (i = 0; i < 4; i++) { + mrb->task.queues[i] = NULL; + } + mrb->task.tick = 0; + mrb->task.wakeup_tick = UINT32_MAX; + mrb->task.switching = FALSE; + + /* Block SIGALRM during registration to avoid race */ + sigemptyset(&alarm_mask); + sigaddset(&alarm_mask, SIGALRM); + sigprocmask(SIG_BLOCK, &alarm_mask, NULL); + + /* Check if this VM is already registered */ + for (i = 0; i < vm_count; i++) { + if (vm_list[i] == mrb) { + vm_index = i; + break; + } + } + + /* Register new VM if not already present */ + if (vm_index < 0) { + if (vm_count >= MRB_TASK_MAX_VMS) { + sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL); + mrb_raisef(mrb, E_RUNTIME_ERROR, + "too many mrb_states with task scheduler (max: %d)", + MRB_TASK_MAX_VMS); + } + vm_list[vm_count] = mrb; + vm_count++; + } + + /* Set up signal handler and timer only for first VM */ + if (vm_count == 1) { + /* Set up signal handler - SA_RESTART to avoid breaking IO operations */ + sa.sa_handler = sigalrm_handler; + sa.sa_flags = SA_RESTART; + sigemptyset(&sa.sa_mask); + sigaction(SIGALRM, &sa, NULL); + + /* Start timer */ + timer.it_value.tv_sec = 0; + timer.it_value.tv_usec = MRB_TICK_UNIT * 1000; + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_usec = MRB_TICK_UNIT * 1000; + setitimer(ITIMER_REAL, &timer, NULL); + } + + /* Unblock SIGALRM */ + sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL); +} + +void +mrb_task_enable_irq(void) +{ + sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL); +} + +void +mrb_task_disable_irq(void) +{ + sigprocmask(SIG_BLOCK, &alarm_mask, NULL); +} + +void +mrb_hal_task_idle_cpu(mrb_state *mrb) +{ + (void)mrb; + /* On POSIX, just pause briefly */ + usleep(MRB_TICK_UNIT * 1000); +} + +void +mrb_hal_task_sleep_us(mrb_state *mrb, mrb_int usec) +{ + struct timespec start, now, sleep_time; + int ret; + + (void)mrb; + + /* Validate input to prevent overflow */ + if (usec < 0) { + return; + } + + ret = clock_gettime(CLOCK_MONOTONIC, &start); + if (ret != 0) { + /* Fallback to simple usleep if clock_gettime fails */ + usleep(usec); + return; + } + + uint64_t target_ns = (uint64_t)usec * USEC_PER_MSEC; + + /* Loop until enough real time has elapsed */ + while (1) { + ret = clock_gettime(CLOCK_MONOTONIC, &now); + if (ret != 0) { + break; /* Clock failure - exit loop */ + } + + uint64_t elapsed_ns = (uint64_t)(now.tv_sec - start.tv_sec) * NSEC_PER_SEC + + (uint64_t)(now.tv_nsec - start.tv_nsec); + + if (elapsed_ns >= target_ns) { + break; + } + + /* Sleep for remaining time, but at least 1ms to allow timer interrupts */ + uint64_t remaining_ns = target_ns - elapsed_ns; + if (remaining_ns > NSEC_PER_MSEC) { + sleep_time.tv_sec = remaining_ns / NSEC_PER_SEC; + sleep_time.tv_nsec = remaining_ns % NSEC_PER_SEC; + } + else { + sleep_time.tv_sec = 0; + sleep_time.tv_nsec = NSEC_PER_MSEC; + } + + nanosleep(&sleep_time, NULL); /* Interrupted by signals - that's OK */ + } +} + +void +mrb_hal_task_final(mrb_state *mrb) +{ + struct itimerval timer; + int i, j; + + /* Block SIGALRM during unregistration */ + sigprocmask(SIG_BLOCK, &alarm_mask, NULL); + + /* Find and remove this VM from the list */ + for (i = 0; i < vm_count; i++) { + if (vm_list[i] == mrb) { + /* Shift remaining VMs down */ + for (j = i; j < vm_count - 1; j++) { + vm_list[j] = vm_list[j + 1]; + } + vm_list[vm_count - 1] = NULL; + vm_count--; + break; + } + } + + /* Stop timer if last VM */ + if (vm_count == 0) { + timer.it_value.tv_sec = 0; + timer.it_value.tv_usec = 0; + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_usec = 0; + setitimer(ITIMER_REAL, &timer, NULL); + } + + /* Unblock SIGALRM */ + sigprocmask(SIG_UNBLOCK, &alarm_mask, NULL); +} + +/* + * 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-dir/mrbgem.rake b/mrbgems/hal-win-dir/mrbgem.rake new file mode 100644 index 000000000..426829dbb --- /dev/null +++ b/mrbgems/hal-win-dir/mrbgem.rake @@ -0,0 +1,7 @@ +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-dir/src/dir_hal.c b/mrbgems/hal-win-dir/src/dir_hal.c new file mode 100644 index 000000000..db2123951 --- /dev/null +++ b/mrbgems/hal-win-dir/src/dir_hal.c @@ -0,0 +1,249 @@ +/* +** dir_hal.c - Windows HAL implementation for mruby-dir +** +** See Copyright Notice in mruby.h +** +** Windows implementation for directory operations using _findfirst/_findnext APIs. +** Provides POSIX-compatible interface on Windows. +** +** Based on dirent.c by Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) +** Original implementation: Created March 1997. Updated June 2003 and July 2012. +** See end of file for Kevlin Henney's copyright notice. +*/ + +#include +#include "dir_hal.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Windows directory handle implementation */ +struct mrb_dir_handle { + intptr_t handle; /* _findfirst/_findnext handle */ + struct _finddata_t info; /* Current entry info */ + char *pattern; /* Search pattern with wildcard */ + int first; /* Flag: haven't read first entry yet */ +}; + +/* + * Directory Operations + */ + +mrb_dir_handle* +mrb_hal_dir_open(mrb_state *mrb, const char *path) +{ + mrb_dir_handle *handle; + size_t len = strlen(path); + const char *suffix; + + /* Add wildcard suffix if needed */ + suffix = (len > 0 && (path[len-1] == '/' || path[len-1] == '\\')) ? "*" : "/*"; + + handle = (mrb_dir_handle*)mrb_malloc(mrb, sizeof(mrb_dir_handle)); + handle->pattern = (char*)mrb_malloc(mrb, len + strlen(suffix) + 1); + strcpy(handle->pattern, path); + strcat(handle->pattern, suffix); + + handle->handle = _findfirst(handle->pattern, &handle->info); + if (handle->handle == -1) { + mrb_free(mrb, handle->pattern); + mrb_free(mrb, handle); + return NULL; + } + + handle->first = 1; + return handle; +} + +int +mrb_hal_dir_close(mrb_state *mrb, mrb_dir_handle *handle) +{ + int result = -1; + + if (handle->handle != -1) { + result = _findclose(handle->handle); + } + + mrb_free(mrb, handle->pattern); + mrb_free(mrb, handle); + + if (result == -1) { + /* Map all errors to EBADF */ + errno = EBADF; + } + + return result; +} + +const char* +mrb_hal_dir_read(mrb_state *mrb, mrb_dir_handle *handle) +{ + (void)mrb; + + if (handle->handle == -1) { + errno = EBADF; + return NULL; + } + + /* First call returns the result from _findfirst */ + if (handle->first) { + handle->first = 0; + return handle->info.name; + } + + /* Subsequent calls use _findnext */ + if (_findnext(handle->handle, &handle->info) == -1) { + return NULL; + } + + return handle->info.name; +} + +void +mrb_hal_dir_rewind(mrb_state *mrb, mrb_dir_handle *handle) +{ + (void)mrb; + + if (handle->handle == -1) { + errno = EBADF; + return; + } + + /* Close and reopen to rewind */ + _findclose(handle->handle); + handle->handle = _findfirst(handle->pattern, &handle->info); + handle->first = 1; +} + +/* + * Optional Operations + */ + +int +mrb_hal_dir_seek(mrb_state *mrb, mrb_dir_handle *handle, long pos) +{ + /* Not supported on Windows */ + (void)mrb; (void)handle; (void)pos; + errno = ENOSYS; + return -1; +} + +long +mrb_hal_dir_tell(mrb_state *mrb, mrb_dir_handle *handle) +{ + /* Not supported on Windows */ + (void)mrb; (void)handle; + errno = ENOSYS; + return -1; +} + +/* + * Filesystem Operations + */ + +int +mrb_hal_dir_mkdir(mrb_state *mrb, const char *path, int mode) +{ + /* Windows _mkdir ignores mode parameter */ + (void)mrb; (void)mode; + return _mkdir(path); +} + +int +mrb_hal_dir_rmdir(mrb_state *mrb, const char *path) +{ + (void)mrb; + return _rmdir(path); +} + +int +mrb_hal_dir_chdir(mrb_state *mrb, const char *path) +{ + (void)mrb; + return _chdir(path); +} + +int +mrb_hal_dir_getcwd(mrb_state *mrb, char *buf, size_t size) +{ + (void)mrb; + return _getcwd(buf, (int)size) ? 0 : -1; +} + +int +mrb_hal_dir_chroot(mrb_state *mrb, const char *path) +{ + /* Not available on Windows */ + (void)mrb; (void)path; + errno = ENOSYS; + return -1; +} + +int +mrb_hal_dir_is_directory(mrb_state *mrb, const char *path) +{ + struct _stat sb; + (void)mrb; + + if (_stat(path, &sb) == 0 && (sb.st_mode & _S_IFDIR)) { + return 1; + } + return 0; +} + +/* + * HAL Initialization/Finalization + */ + +void +mrb_hal_dir_init(mrb_state *mrb) +{ + (void)mrb; + /* No initialization needed for Windows */ +} + +void +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/hal-win-io/mrbgem.rake b/mrbgems/hal-win-io/mrbgem.rake new file mode 100644 index 000000000..5bad6576b --- /dev/null +++ b/mrbgems/hal-win-io/mrbgem.rake @@ -0,0 +1,11 @@ +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-io/src/io_hal.c b/mrbgems/hal-win-io/src/io_hal.c new file mode 100644 index 000000000..9cc3583bd --- /dev/null +++ b/mrbgems/hal-win-io/src/io_hal.c @@ -0,0 +1,641 @@ +/* +** io_hal.c - Windows HAL implementation for mruby-io +** +** See Copyright Notice in mruby.h +** +** Windows implementation for I/O operations using Win32 APIs. +** Supported platforms: Windows, MinGW +*/ + +#include +#include "io_hal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Maximum path length */ +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +/* + * Helper Functions + */ + +/* Convert Windows struct _stat64 to mrb_io_stat */ +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_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_blksize = 512; + dst->st_blocks = (dst->st_size + 511) / 512; +} + +/* Convert errno to Windows errno */ +static void +set_errno_from_win_error(DWORD error) +{ + switch (error) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + errno = ENOENT; + break; + case ERROR_ACCESS_DENIED: + errno = EACCES; + break; + case ERROR_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + break; + case ERROR_INVALID_HANDLE: + errno = EBADF; + break; + case ERROR_ALREADY_EXISTS: + errno = EEXIST; + break; + default: + errno = EIO; + break; + } +} + +/* + * File Operations + */ + +int +mrb_hal_io_stat(mrb_state *mrb, const char *path, mrb_io_stat *st) +{ + struct _stat64 s; + (void)mrb; + + if (_stat64(path, &s) == -1) { + return -1; + } + convert_stat(&s, st); + return 0; +} + +int +mrb_hal_io_fstat(mrb_state *mrb, int fd, mrb_io_stat *st) +{ + struct _stat64 s; + (void)mrb; + + if (_fstat64(fd, &s) == -1) { + return -1; + } + convert_stat(&s, st); + return 0; +} + +int +mrb_hal_io_lstat(mrb_state *mrb, const char *path, mrb_io_stat *st) +{ + /* Windows doesn't distinguish lstat from stat */ + return mrb_hal_io_stat(mrb, path, st); +} + +int +mrb_hal_io_chmod(mrb_state *mrb, const char *path, uint32_t mode) +{ + (void)mrb; + return _chmod(path, (int)mode); +} + +uint32_t +mrb_hal_io_umask(mrb_state *mrb, int32_t mask) +{ + int old; + (void)mrb; + + if (mask < 0) { + /* Just query current value */ + old = _umask(0); + _umask(old); + } + else { + old = _umask((int)mask); + } + return (uint32_t)old; +} + +int +mrb_hal_io_ftruncate(mrb_state *mrb, int fd, int64_t length) +{ + (void)mrb; + return _chsize_s(fd, length); +} + +int +mrb_hal_io_flock(mrb_state *mrb, int fd, int operation) +{ + HANDLE h; + OVERLAPPED overlapped; + DWORD flags = 0; + (void)mrb; + + h = (HANDLE)_get_osfhandle(fd); + if (h == INVALID_HANDLE_VALUE) { + errno = EBADF; + return -1; + } + + memset(&overlapped, 0, sizeof(overlapped)); + + if (operation & MRB_IO_LOCK_UN) { + if (!UnlockFileEx(h, 0, MAXDWORD, MAXDWORD, &overlapped)) { + set_errno_from_win_error(GetLastError()); + return -1; + } + return 0; + } + + if (operation & MRB_IO_LOCK_EX) { + flags |= LOCKFILE_EXCLUSIVE_LOCK; + } + if (operation & MRB_IO_LOCK_NB) { + flags |= LOCKFILE_FAIL_IMMEDIATELY; + } + + if (!LockFileEx(h, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) { + set_errno_from_win_error(GetLastError()); + return -1; + } + + return 0; +} + +int +mrb_hal_io_unlink(mrb_state *mrb, const char *path) +{ + (void)mrb; + return _unlink(path); +} + +int +mrb_hal_io_rename(mrb_state *mrb, const char *oldpath, const char *newpath) +{ + (void)mrb; + return rename(oldpath, newpath); +} + +int +mrb_hal_io_symlink(mrb_state *mrb, const char *target, const char *linkpath) +{ + (void)target; + (void)linkpath; + /* Symlinks require special privileges on Windows */ + mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on Windows"); + return -1; /* not reached */ +} + +int64_t +mrb_hal_io_readlink(mrb_state *mrb, const char *path, char *buf, size_t bufsize) +{ + (void)path; + (void)buf; + (void)bufsize; + /* Symlinks require special handling on Windows */ + mrb_raise(mrb, E_NOTIMP_ERROR, "readlink is not supported on Windows"); + return -1; /* not reached */ +} + +char* +mrb_hal_io_realpath(mrb_state *mrb, const char *path, char *resolved) +{ + DWORD ret; + (void)mrb; + + ret = GetFullPathName(path, PATH_MAX, resolved, NULL); + if (ret == 0 || ret >= PATH_MAX) { + set_errno_from_win_error(GetLastError()); + return NULL; + } + return resolved; +} + +char* +mrb_hal_io_getcwd(mrb_state *mrb, char *buf, size_t size) +{ + (void)mrb; + return _getcwd(buf, (int)size); +} + +const char* +mrb_hal_io_getenv(mrb_state *mrb, const char *name) +{ + (void)mrb; + return getenv(name); +} + +const char* +mrb_hal_io_gethome(mrb_state *mrb, const char *username) +{ + const char *home; + (void)mrb; + + if (username != NULL && *username != '\0') { + /* Windows doesn't have a simple way to get other users' home directories */ + errno = ENOSYS; + return NULL; + } + + /* Try USERPROFILE first, then HOMEDRIVE+HOMEPATH */ + home = getenv("USERPROFILE"); + if (home == NULL) { + const char *homedrive = getenv("HOMEDRIVE"); + const char *homepath = getenv("HOMEPATH"); + if (homedrive && homepath) { + static char homebuf[PATH_MAX]; + snprintf(homebuf, PATH_MAX, "%s%s", homedrive, homepath); + return homebuf; + } + errno = ENOENT; + return NULL; + } + + return home; +} + +/* + * Core I/O Operations + */ + +int +mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, uint32_t mode) +{ + int fd; + (void)mrb; + + /* Windows uses _open with slightly different flags */ + fd = _open(path, flags | _O_BINARY, (int)mode); + if (fd == -1) { + return -1; + } + + /* Set close-on-exec for non-standard descriptors */ + if (fd > 2) { + SetHandleInformation((HANDLE)_get_osfhandle(fd), HANDLE_FLAG_INHERIT, 0); + } + + return fd; +} + +int +mrb_hal_io_close(mrb_state *mrb, int fd) +{ + (void)mrb; + return _close(fd); +} + +int64_t +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; +} + +int64_t +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; +} + +int64_t +mrb_hal_io_lseek(mrb_state *mrb, int fd, int64_t offset, int whence) +{ + __int64 pos; + int win_whence; + (void)mrb; + + /* Convert MRB_IO_SEEK_* to Windows SEEK_* */ + switch (whence) { + case MRB_IO_SEEK_SET: win_whence = SEEK_SET; break; + case MRB_IO_SEEK_CUR: win_whence = SEEK_CUR; break; + case MRB_IO_SEEK_END: win_whence = SEEK_END; break; + default: + errno = EINVAL; + return -1; + } + + pos = _lseeki64(fd, (__int64)offset, win_whence); + return (int64_t)pos; +} + +int +mrb_hal_io_dup(mrb_state *mrb, int fd) +{ + int new_fd; + (void)mrb; + + new_fd = _dup(fd); + if (new_fd == -1) { + return -1; + } + + /* Set close-on-exec */ + if (new_fd > 2) { + SetHandleInformation((HANDLE)_get_osfhandle(new_fd), HANDLE_FLAG_INHERIT, 0); + } + + return new_fd; +} + +int +mrb_hal_io_fcntl(mrb_state *mrb, int fd, int cmd, int arg) +{ + /* Windows has limited fcntl support */ + (void)mrb; + (void)fd; + (void)cmd; + (void)arg; + errno = ENOSYS; + return -1; +} + +int +mrb_hal_io_isatty(mrb_state *mrb, int fd) +{ + (void)mrb; + return _isatty(fd) ? 1 : 0; +} + +int +mrb_hal_io_pipe(mrb_state *mrb, int fds[2]) +{ + int ret; + (void)mrb; + + ret = _pipe(fds, 4096, _O_BINARY); + if (ret == -1) { + return -1; + } + + /* Set close-on-exec on both ends */ + SetHandleInformation((HANDLE)_get_osfhandle(fds[0]), HANDLE_FLAG_INHERIT, 0); + SetHandleInformation((HANDLE)_get_osfhandle(fds[1]), HANDLE_FLAG_INHERIT, 0); + + return 0; +} + +/* + * Process Operations + */ + +int +mrb_hal_io_spawn_process(mrb_state *mrb, const char *cmd, + int stdin_fd, int stdout_fd, int stderr_fd, + int *pid) +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + HANDLE h_stdin = INVALID_HANDLE_VALUE; + HANDLE h_stdout = INVALID_HANDLE_VALUE; + HANDLE h_stderr = INVALID_HANDLE_VALUE; + char cmdline[8192]; + BOOL ret; + (void)mrb; + + /* Skip leading whitespace */ + while (*cmd == ' ' || *cmd == '\t' || *cmd == '\n') { + cmd++; + } + + if (!*cmd) { + errno = ENOENT; + return -1; + } + + /* Build command line - use cmd.exe to execute */ + snprintf(cmdline, sizeof(cmdline), "cmd.exe /c %s", cmd); + + /* Setup startup info */ + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES; + + /* Convert file descriptors to handles and make them inheritable */ + if (stdin_fd != -1) { + h_stdin = (HANDLE)_get_osfhandle(stdin_fd); + SetHandleInformation(h_stdin, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + si.hStdInput = h_stdin; + } + else { + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + } + + if (stdout_fd != -1) { + h_stdout = (HANDLE)_get_osfhandle(stdout_fd); + SetHandleInformation(h_stdout, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + si.hStdOutput = h_stdout; + } + else { + si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + } + + if (stderr_fd != -1) { + h_stderr = (HANDLE)_get_osfhandle(stderr_fd); + SetHandleInformation(h_stderr, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + si.hStdError = h_stderr; + } + else { + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + } + + /* Create process */ + memset(&pi, 0, sizeof(pi)); + ret = CreateProcess(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); + + if (!ret) { + set_errno_from_win_error(GetLastError()); + return -1; + } + + /* Close thread handle - we don't need it */ + CloseHandle(pi.hThread); + + /* Store process handle as PID (will be used in waitpid) */ + *pid = (int)(intptr_t)pi.hProcess; + + return 0; +} + +int +mrb_hal_io_waitpid(mrb_state *mrb, int pid, int *status, int options) +{ + HANDLE h = (HANDLE)(intptr_t)pid; + DWORD wait_result; + DWORD exit_code; + DWORD timeout; + (void)mrb; + + /* Convert options to timeout */ + timeout = (options != 0) ? 0 : INFINITE; + + wait_result = WaitForSingleObject(h, timeout); + + if (wait_result == WAIT_TIMEOUT) { + return 0; /* Non-blocking wait, no change */ + } + + if (wait_result != WAIT_OBJECT_0) { + set_errno_from_win_error(GetLastError()); + return -1; + } + + /* Get exit code */ + if (!GetExitCodeProcess(h, &exit_code)) { + set_errno_from_win_error(GetLastError()); + return -1; + } + + if (status != NULL) { + /* Store exit code in status (shifted to match Unix convention) */ + *status = (int)(exit_code << 8); + } + + /* Close process handle */ + CloseHandle(h); + + return pid; +} + +/* + * I/O Multiplexing + */ + +struct mrb_io_fdset { + fd_set fds; +}; + +mrb_io_fdset* +mrb_hal_io_fdset_alloc(mrb_state *mrb) +{ + mrb_io_fdset *fdset = (mrb_io_fdset*)mrb_malloc(mrb, sizeof(mrb_io_fdset)); + FD_ZERO(&fdset->fds); + return fdset; +} + +void +mrb_hal_io_fdset_free(mrb_state *mrb, mrb_io_fdset *fdset) +{ + if (fdset) { + mrb_free(mrb, fdset); + } +} + +void +mrb_hal_io_fdset_zero(mrb_state *mrb, mrb_io_fdset *fdset) +{ + (void)mrb; + if (fdset) { + FD_ZERO(&fdset->fds); + } +} + +void +mrb_hal_io_fdset_set(mrb_state *mrb, int fd, mrb_io_fdset *fdset) +{ + (void)mrb; + if (fdset) { + FD_SET(fd, &fdset->fds); + } +} + +int +mrb_hal_io_fdset_isset(mrb_state *mrb, int fd, mrb_io_fdset *fdset) +{ + (void)mrb; + if (fdset) { + return FD_ISSET(fd, &fdset->fds); + } + return 0; +} + +int +mrb_hal_io_select(mrb_state *mrb, int nfds, + mrb_io_fdset *readfds, + mrb_io_fdset *writefds, + mrb_io_fdset *errorfds, + mrb_io_timeval *timeout) +{ + fd_set *r = readfds ? &readfds->fds : NULL; + fd_set *w = writefds ? &writefds->fds : NULL; + fd_set *e = errorfds ? &errorfds->fds : NULL; + struct timeval *tv = NULL; + struct timeval tv_storage; + (void)mrb; + (void)nfds; /* Windows select() doesn't use nfds */ + + if (timeout) { + tv_storage.tv_sec = (long)timeout->tv_sec; + tv_storage.tv_usec = (long)timeout->tv_usec; + tv = &tv_storage; + } + + return select(0, r, w, e, tv); +} + +/* + * HAL Initialization/Finalization + */ + +void +mrb_hal_io_init(mrb_state *mrb) +{ + (void)mrb; + /* Initialize Winsock for select() support */ + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +} + +void +mrb_hal_io_final(mrb_state *mrb) +{ + (void)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/hal-win-socket/mrbgem.rake b/mrbgems/hal-win-socket/mrbgem.rake new file mode 100644 index 000000000..d959c87e6 --- /dev/null +++ b/mrbgems/hal-win-socket/mrbgem.rake @@ -0,0 +1,12 @@ +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 new file mode 100644 index 000000000..3acda0b5e --- /dev/null +++ b/mrbgems/hal-win-socket/src/socket_hal.c @@ -0,0 +1,177 @@ +/* +** 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 + +/* + * 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 new file mode 100644 index 000000000..b959afdec --- /dev/null +++ b/mrbgems/hal-win-task/README.md @@ -0,0 +1,109 @@ +# 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 new file mode 100644 index 000000000..ea40075e8 --- /dev/null +++ b/mrbgems/hal-win-task/mrbgem.rake @@ -0,0 +1,11 @@ +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/hal-win-task/src/task_hal.c b/mrbgems/hal-win-task/src/task_hal.c new file mode 100644 index 000000000..f6e306f3c --- /dev/null +++ b/mrbgems/hal-win-task/src/task_hal.c @@ -0,0 +1,187 @@ +/* +** task_hal.c - Windows HAL implementation for mruby-task +** +** See Copyright Notice in mruby.h +** +** Windows implementation using multimedia timer (timeSetEvent/timeKillEvent) +** for periodic timer, and CRITICAL_SECTION for interrupt protection. +** +** Supported platforms: Windows (all versions with multimedia timer support) +*/ + +#include +#include "task_hal.h" +#include +#include +#include + +/* Multi-VM support */ +static mrb_state *vm_list[MRB_TASK_MAX_VMS]; +static volatile LONG vm_count = 0; +static CRITICAL_SECTION irq_lock; +static MMRESULT timer_id = 0; + +/* Multimedia timer callback - called periodically by Windows */ +static void CALLBACK +timer_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) +{ + int i; + (void)uID; (void)uMsg; (void)dwUser; (void)dw1; (void)dw2; + + /* Tick all registered VMs */ + EnterCriticalSection(&irq_lock); + for (i = 0; i < vm_count; i++) { + if (vm_list[i]) { + mrb_tick(vm_list[i]); + } + } + LeaveCriticalSection(&irq_lock); +} + +/* + * HAL Interface Implementation + */ + +void +mrb_hal_task_init(mrb_state *mrb) +{ + int i; + LONG idx; + + /* Initialize task state */ + for (i = 0; i < 4; i++) { + mrb->task.queues[i] = NULL; + } + mrb->task.tick = 0; + mrb->task.wakeup_tick = UINT32_MAX; + mrb->task.switching = FALSE; + + /* Initialize critical section on first VM */ + if (vm_count == 0) { + InitializeCriticalSection(&irq_lock); + } + + EnterCriticalSection(&irq_lock); + + /* Check if this VM is already registered */ + idx = -1; + for (i = 0; i < vm_count; i++) { + if (vm_list[i] == mrb) { + idx = i; + break; + } + } + + /* Register new VM if not already present */ + if (idx < 0) { + if (vm_count >= MRB_TASK_MAX_VMS) { + LeaveCriticalSection(&irq_lock); + mrb_raisef(mrb, E_RUNTIME_ERROR, + "too many mrb_states with task scheduler (max: %d)", + MRB_TASK_MAX_VMS); + } + vm_list[vm_count] = mrb; + InterlockedIncrement(&vm_count); + } + + /* Start timer for first VM */ + if (vm_count == 1) { + /* Request 1ms timer resolution */ + timeBeginPeriod(1); + + /* Create periodic timer with MRB_TICK_UNIT interval */ + timer_id = timeSetEvent( + MRB_TICK_UNIT, /* interval in milliseconds */ + 1, /* resolution in milliseconds */ + timer_callback, /* callback function */ + 0, /* user data */ + TIME_PERIODIC | TIME_KILL_SYNCHRONOUS + ); + } + + LeaveCriticalSection(&irq_lock); +} + +void +mrb_task_enable_irq(void) +{ + LeaveCriticalSection(&irq_lock); +} + +void +mrb_task_disable_irq(void) +{ + EnterCriticalSection(&irq_lock); +} + +void +mrb_hal_task_idle_cpu(mrb_state *mrb) +{ + (void)mrb; + /* On Windows, just sleep briefly */ + Sleep(MRB_TICK_UNIT); +} + +void +mrb_hal_task_sleep_us(mrb_state *mrb, mrb_int usec) +{ + (void)mrb; + + /* Windows Sleep() takes milliseconds, convert from microseconds */ + if (usec >= 0) { + Sleep((DWORD)(usec / 1000)); + } +} + +void +mrb_hal_task_final(mrb_state *mrb) +{ + int i, j; + + EnterCriticalSection(&irq_lock); + + /* Find and remove this VM from the list */ + for (i = 0; i < vm_count; i++) { + if (vm_list[i] == mrb) { + /* Shift remaining VMs down */ + for (j = i; j < vm_count - 1; j++) { + vm_list[j] = vm_list[j + 1]; + } + vm_list[vm_count - 1] = NULL; + InterlockedDecrement(&vm_count); + break; + } + } + + /* Stop timer if last VM */ + if (vm_count == 0) { + if (timer_id != 0) { + timeKillEvent(timer_id); + timeEndPeriod(1); + timer_id = 0; + } + LeaveCriticalSection(&irq_lock); + DeleteCriticalSection(&irq_lock); + } + else { + 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-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb index d25c420e9..cdc2e0226 100644 --- a/mrbgems/mruby-array-ext/mrblib/array.rb +++ b/mrbgems/mruby-array-ext/mrblib/array.rb @@ -4,8 +4,8 @@ class Array # ary.uniq! -> ary or nil # ary.uniq! { |item| ... } -> ary or nil # - # Removes duplicate elements from +self+. - # Returns nil if no changes are made (that is, no + # Removes duplicate elements from `self`. + # Returns `nil` if no changes are made (that is, no # duplicates are found). # # a = [ "a", "a", "b", "b", "c" ] @@ -42,7 +42,7 @@ class Array # ary.uniq -> new_ary # ary.uniq { |item| ... } -> new_ary # - # Returns a new array by removing duplicate values in +self+. + # Returns a new array by removing duplicate values in `self`. # # a = [ "a", "a", "b", "b", "c" ] # a.uniq #=> ["a", "b", "c"] @@ -78,15 +78,15 @@ class Array # ary.fetch(index, default) -> obj # ary.fetch(index) { |index| block } -> obj # - # Tries to return the element at position +index+, but throws an IndexError - # exception if the referenced +index+ lies outside of the array bounds. This + # Tries to return the element at position `index`, but throws an IndexError + # exception if the referenced `index` lies outside of the array bounds. This # error can be prevented by supplying a second argument, which will act as a - # +default+ value. + # `default` value. # # Alternatively, if a block is given it will only be executed when an - # invalid +index+ is referenced. + # invalid `index` is referenced. # - # Negative values of +index+ count from the end of the array. + # Negative values of `index` count from the end of the array. # # a = [ 11, 22, 33, 44 ] # a.fetch(1) #=> 22 @@ -122,17 +122,17 @@ class Array # ary.fill(start [, length] ) { |index| block } -> ary # ary.fill(range) { |index| block } -> ary # - # The first three forms set the selected elements of +self+ (which - # may be the entire array) to +obj+. + # The first three forms set the selected elements of `self` (which + # may be the entire array) to `obj`. # - # A +start+ of +nil+ is equivalent to zero. + # A `start` of `nil` is equivalent to zero. # - # A +length+ of +nil+ is equivalent to the length of the array. + # A `length` of `nil` is equivalent to the length of the array. # # The last three forms fill the array with the value of the given block, # which is passed the absolute index of each element to be filled. # - # Negative values of +start+ count from the end of the array, where +-1+ is + # Negative values of `start` count from the end of the array, where +-1+ is # the last element. # # a = [ "a", "b", "c", "d" ] @@ -174,7 +174,7 @@ class Array # ary.delete_if { |item| block } -> ary # ary.delete_if -> Enumerator # - # Deletes every element of +self+ for which block evaluates to +true+. + # Deletes every element of `self` for which block evaluates to `true`. # # The array is changed instantly every time the block is called, not after # the iteration is over. @@ -206,8 +206,8 @@ class Array # ary.reject! { |item| block } -> ary or nil # ary.reject! -> Enumerator # - # Equivalent to Array#delete_if, deleting elements from +self+ for which the - # block evaluates to +true+, but returns +nil+ if no changes were made. + # Equivalent to Array#delete_if, deleting elements from `self` for which the + # block evaluates to `true`, but returns `nil` if no changes were made. # # The array is changed instantly every time the block is called, not after # the iteration is over. @@ -349,8 +349,8 @@ class Array # ary.keep_if { |item| block } -> ary # ary.keep_if -> Enumerator # - # Deletes every element of +self+ for which the given block evaluates to - # +false+. + # Deletes every element of `self` for which the given block evaluates to + # `false`. # # See also Array#select! # @@ -379,10 +379,10 @@ class Array # ary.select! {|item| block } -> ary or nil # ary.select! -> Enumerator # - # Invokes the given block passing in successive elements from +self+, - # deleting elements for which the block returns a +false+ value. + # Invokes the given block passing in successive elements from `self`, + # deleting elements for which the block returns a `false` value. # - # If changes were made, it will return +self+, otherwise it returns +nil+. + # If changes were made, it will return `self`, otherwise it returns `nil`. # # See also Array#keep_if # @@ -409,9 +409,9 @@ class Array # call-seq: # ary.dig(idx, ...) -> object # - # Extracts the nested value specified by the sequence of idx - # objects by calling +dig+ at each step, returning +nil+ if any - # intermediate step is +nil+. + # Extracts the nested value specified by the sequence of *idx* + # objects by calling `dig` at each step, returning `nil` if any + # intermediate step is `nil`. # def dig(idx,*args) idx = idx.__to_int @@ -430,10 +430,10 @@ class Array # ary.permutation(n) { |p| block } -> ary # ary.permutation(n) -> Enumerator # - # When invoked with a block, yield all permutations of length +n+ of the + # When invoked with a block, yield all permutations of length `n` of the # elements of the array, then return the array itself. # - # If +n+ is not specified, yield all permutations of all elements. + # If `n` is not specified, yield all permutations of all elements. # # The implementation makes no guarantees about the order in which the # permutations are yielded. @@ -478,7 +478,7 @@ class Array # ary.combination(n) { |c| block } -> ary # ary.combination(n) -> Enumerator # - # When invoked with a block, yields all combinations of length +n+ of elements + # When invoked with a block, yields all combinations of length `n` of elements # from the array and then returns the array itself. # # The implementation makes no guarantees about the order in which the @@ -554,9 +554,9 @@ class Array # ary.to_h -> Hash # ary.to_h{|item| ... } -> Hash # - # Returns the result of interpreting array as an array of - # [key, value] pairs. If a block is given, it should - # return [key, value] pairs to construct a hash. + # Returns the result of interpreting *array* as an array of + # `[key, value]` pairs. If a block is given, it should + # return `[key, value]` pairs to construct a hash. # # [[:foo, :bar], [1, 2]].to_h # # => {:foo => :bar, 1 => 2} @@ -584,8 +584,8 @@ class Array # ary.fetch_values(idx, ...) { |i| block } -> array # # Returns an array containing the values associated with the given indexes. - # but also raises IndexError when one of indexes can't be found. - # Also see Array#values_at and Array#fetch. + # but also raises `IndexError` when one of indexes can't be found. + # Also see `Array#values_at` and `Array#fetch`. # # a = ["cat", "dog", "cow"] # @@ -625,40 +625,44 @@ class Array total *= arys[i -= 1].size while i > 0 if block - result = self - list = ->(*, e) { block.call e } - class << list; alias []= call; end + i = 0 + while i < total + group = self.__product_group(arys, i, size + 1) + block.call(group) + i += 1 + end + return self else result = [nil] * total - list = result - end - - i = 0 - while i < total - group = [nil] * (size + 1) - j = size - n = i - while j > 0 - j -= 1 - a = arys[j] - b = a.size - group[j + 1] = a[n % b] - n /= b + i = 0 + while i < total + group = self.__product_group(arys, i, size + 1) + result[i] = group + i += 1 end - group[0] = self[n] - list[i] = group - i += 1 + return result end - - result end ## # call-seq: - # ary.repeated_combination(n) { |combination| ... } -> self - # ary.repeated_combination(n) -> enumerator + # ary.repeated_combination(n) { |combination| ... } -> ary + # ary.repeated_combination(n) -> Enumerator # - # A +combination+ method that contains the same elements. + # When invoked with a block, yields all length `n` combinations of elements + # from the array, with replacement, and then returns the array itself. + # + # This means that, unlike `combination`, elements can be chosen more than once. + # + # The implementation makes no guarantees about the order in which the + # combinations are yielded. + # + # If no block is given, an Enumerator is returned instead. + # + # Examples: + # + # a = [1, 2, 3] + # a.repeated_combination(2).to_a #=> [[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 @@ -667,10 +671,23 @@ class Array ## # call-seq: - # ary.repeated_permutation(n) { |permutation| ... } -> self - # ary.repeated_permutation(n) -> enumerator + # ary.repeated_permutation(n) { |permutation| ... } -> ary + # ary.repeated_permutation(n) -> Enumerator # - # A +permutation+ method that contains the same elements. + # When invoked with a block, yields all length `n` permutations of elements + # from the array, with replacement, and then returns the array itself. + # + # This means that, unlike `permutation`, elements can be chosen more than once. + # + # The implementation makes no guarantees about the order in which the + # permutations are yielded. + # + # If no block is given, an Enumerator is returned instead. + # + # Examples: + # + # 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 @@ -684,6 +701,7 @@ class Array when 0 yield [] when 1 + # Keep fast Ruby path for n=1 i = 0 while i < self.size yield [self[i]] @@ -691,36 +709,17 @@ class Array end else if n > 0 - v = [0] * n - while true + # 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[v[i]] + tmp[i] = self[indices[i]] i += 1 end - yield tmp - - tmp = self.size - i = n - 1 - while i >= 0 - v[i] += 1 - break if v[i] < tmp - i -= 1 - end - break unless v[0] < tmp - i = 1 - while i < n - unless v[i] < tmp - if permutation - v[i] = 0 - else - v[i] = v[i - 1] - end - end - i += 1 - end end end end diff --git a/mrbgems/mruby-array-ext/src/array.c b/mrbgems/mruby-array-ext/src/array.c index 2f3cf9ef9..fddc65596 100644 --- a/mrbgems/mruby-array-ext/src/array.c +++ b/mrbgems/mruby-array-ext/src/array.c @@ -4,8 +4,54 @@ #include #include #include +#include #include #include +#include +#include + +/* khash set for temporary array operations */ +static inline khint_t +ary_set_hash_func(mrb_state *mrb, mrb_value key) +{ + return (khint_t)mrb_obj_hash_code(mrb, key); +} + +static inline mrb_bool +ary_set_equal_func(mrb_state *mrb, mrb_value a, mrb_value b) +{ + return mrb_eql(mrb, a, b); +} + +KHASH_DECLARE(ary_set, mrb_value, char, 0) +KHASH_DEFINE(ary_set, mrb_value, char, 0, ary_set_hash_func, ary_set_equal_func) + +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; +}; + +static void +mrb_combination_state_free(mrb_state *mrb, void *ptr) +{ + struct mrb_combination_state *state = (struct mrb_combination_state*)ptr; + if (state) { + if (state->indices) { + mrb_free(mrb, state->indices); + } + mrb_free(mrb, state); + } +} + +static struct mrb_data_type mrb_combination_state_type = { + "CombinationState", mrb_combination_state_free +}; /* * call-seq: @@ -16,8 +62,8 @@ * using obj.==. * Returns the first contained array that matches (that * is, the first associated array), - * or +nil+ if no match is found. - * See also Array#rassoc. + * or `nil` if no match is found. + * See also `Array#rassoc`. * * s1 = [ "colors", "red", "blue", "green" ] * s2 = [ "letters", "a", "b", "c" ] @@ -30,12 +76,10 @@ static mrb_value ary_assoc(mrb_state *mrb, mrb_value ary) { - mrb_int i; - mrb_value v; mrb_value k = mrb_get_arg1(mrb); - for (i = 0; i < RARRAY_LEN(ary); i++) { - v = mrb_check_array_type(mrb, RARRAY_PTR(ary)[i]); + for (mrb_int i = 0; i < RARRAY_LEN(ary); i++) { + mrb_value v = mrb_check_array_type(mrb, RARRAY_PTR(ary)[i]); if (!mrb_nil_p(v) && RARRAY_LEN(v) > 0 && mrb_equal(mrb, RARRAY_PTR(v)[0], k)) return v; @@ -49,8 +93,8 @@ ary_assoc(mrb_state *mrb, mrb_value ary) * * Searches through the array whose elements are also arrays. Compares * _obj_ with the second element of each contained array using - * ==. Returns the first contained array that matches. See - * also Array#assoc. + * `==`. Returns the first contained array that matches. See + * also `Array#assoc`. * * a = [ [ 1, "one"], [2, "two"], [3, "three"], ["ii", "two"] ] * a.rassoc("two") #=> [2, "two"] @@ -60,12 +104,10 @@ ary_assoc(mrb_state *mrb, mrb_value ary) static mrb_value ary_rassoc(mrb_state *mrb, mrb_value ary) { - mrb_int i; - mrb_value v; mrb_value value = mrb_get_arg1(mrb); - for (i = 0; i < RARRAY_LEN(ary); i++) { - v = RARRAY_PTR(ary)[i]; + for (mrb_int i = 0; i < RARRAY_LEN(ary); i++) { + mrb_value v = RARRAY_PTR(ary)[i]; if (mrb_array_p(v) && RARRAY_LEN(v) > 1 && mrb_equal(mrb, RARRAY_PTR(v)[1], value)) @@ -79,8 +121,8 @@ ary_rassoc(mrb_state *mrb, mrb_value ary) * ary.at(index) -> obj or nil * * Returns the element at _index_. A - * negative index counts from the end of +self+. Returns +nil+ - * if the index is out of range. See also Array#[]. + * negative index counts from the end of `self`. Returns `nil` + * if the index is out of range. See also `Array#[]`. * * a = [ "a", "b", "c", "d", "e" ] * a.at(0) #=> "a" @@ -106,8 +148,8 @@ ary_ref(mrb_state *mrb, mrb_value ary, mrb_int n) * call-seq: * ary.values_at(selector, ...) -> new_ary * - * Returns an array containing the elements in +self+ corresponding to the - * given +selector+(s). The selectors may be either integer indices or ranges. + * Returns an array containing the elements in `self` corresponding to the + * given `selector`(s). The selectors may be either integer indices or ranges. * * a = %w{ a b c d e f } * a.values_at(1, 3, 5) # => ["b", "d", "f"] @@ -133,10 +175,10 @@ mrb_value mrb_ary_delete_at(mrb_state *mrb, mrb_value self); * ary.slice!(start, length) -> new_ary or nil * ary.slice!(range) -> new_ary or nil * - * Deletes the element(s) given by an +index+ (optionally up to +length+ - * elements) or by a +range+. + * Deletes the element(s) given by an `index` (optionally up to `length` + * elements) or by a `range`. * - * Returns the deleted object (or objects), or +nil+ if the +index+ is out of + * Returns the deleted object (or objects), or `nil` if the `index` is out of * range. * * a = [ "a", "b", "c" ] @@ -152,9 +194,7 @@ static mrb_value ary_slice_bang(mrb_state *mrb, mrb_value self) { struct RArray *a = mrb_ary_ptr(self); - mrb_int i, j, len, alen; - mrb_value *ptr; - mrb_value ary; + mrb_int i, len; mrb_ary_modify(mrb, a); @@ -162,67 +202,45 @@ ary_slice_bang(mrb_state *mrb, mrb_value self) mrb_value index = mrb_get_arg1(mrb); if (mrb_type(index) == MRB_TT_RANGE) { - if (mrb_range_beg_len(mrb, index, &i, &len, ARY_LEN(a), TRUE) == MRB_RANGE_OK) { - goto delete_pos_len; + if (mrb_range_beg_len(mrb, index, &i, &len, ARY_LEN(a), TRUE) != MRB_RANGE_OK) { + return mrb_nil_value(); } - return mrb_nil_value(); } - return mrb_ary_delete_at(mrb, self); + else { + return mrb_ary_delete_at(mrb, self); + } + } + else { + mrb_get_args(mrb, "ii", &i, &len); } - mrb_get_args(mrb, "ii", &i, &len); - delete_pos_len: - alen = ARY_LEN(a); + mrb_int alen = ARY_LEN(a); if (i < 0) i += alen; if (i < 0 || alen < i) return mrb_nil_value(); if (len < 0) return mrb_nil_value(); if (alen == i) return mrb_ary_new(mrb); if (len > alen - i) len = alen - i; - ptr = ARY_PTR(a) + i; - ary = mrb_ary_new_from_values(mrb, len, ptr); + mrb_value ary = mrb_ary_new_from_values(mrb, len, ARY_PTR(a) + i); - for (j = i; j < alen - len; j++) { - *ptr = *(ptr+len); - ptr++; + /* refresh pointer after mrb_ary_new_from_values */ + a = mrb_ary_ptr(self); + + for (int j = i; j < alen - len; j++) { + ARY_PTR(a)[j] = ARY_PTR(a)[j+len]; } mrb_ary_resize(mrb, self, alen - len); return ary; } -/* - * call-seq: - * ary.compact -> new_ary - * - * Returns a copy of +self+ with all +nil+ elements removed. - * - * [ "a", nil, "b", nil, "c", nil ].compact - * #=> [ "a", "b", "c" ] - */ - -static mrb_value -ary_compact(mrb_state *mrb, mrb_value self) -{ - mrb_value ary = mrb_ary_new(mrb); - mrb_int len = RARRAY_LEN(self); - mrb_value *p = RARRAY_PTR(self); - - for (mrb_int i = 0; i < len; i++) { - if (!mrb_nil_p(p[i])) { - mrb_ary_push(mrb, ary, p[i]); - } - } - return ary; -} - /* * call-seq: * ary.compact! -> ary or nil * - * Removes +nil+ elements from the array. - * Returns +nil+ if no changes were made, otherwise returns - * ary. + * Removes `nil` elements from the array. + * Returns `nil` if no changes were made, otherwise returns + * *ary*. * * [ "a", nil, "b", nil, "c" ].compact! #=> [ "a", "b", "c" ] * [ "a", "b", "c" ].compact! #=> nil @@ -235,10 +253,12 @@ ary_compact_bang(mrb_state *mrb, mrb_value self) mrb_int len = ARY_LEN(a); mrb_ary_modify(mrb, a); - mrb_value *p = ARY_PTR(a); + /* a is still valid here, as mrb_ary_modify only modifies the RArray struct, not reallocates it */ + /* Hoist pointer retrieval outside loop to avoid repeated conditionals */ + mrb_value *ptr = RARRAY_PTR(self); for (i = 0; i < len; i++) { - if (!mrb_nil_p(p[i])) { - if (i != j) p[j] = p[i]; + if (!mrb_nil_p(ptr[i])) { + if (i != j) ptr[j] = ptr[i]; j++; } } @@ -247,16 +267,34 @@ ary_compact_bang(mrb_state *mrb, mrb_value self) return self; } +/* + * call-seq: + * ary.compact -> new_ary + * + * Returns a copy of `self` with all `nil` elements removed. + * + * [ "a", nil, "b", nil, "c", nil ].compact + * #=> [ "a", "b", "c" ] + */ + +static mrb_value +ary_compact(mrb_state *mrb, mrb_value self) +{ + mrb_value ary = mrb_ary_dup(mrb, self); + ary_compact_bang(mrb, ary); + return ary; +} + /* * call-seq: * ary.rotate(count=1) -> new_ary * - * Returns a new array by rotating +self+ so that the element at +count+ is + * Returns a new array by rotating `self` so that the element at `count` is * the first element of the new array. * - * If +count+ is negative then it rotates in the opposite direction, starting - * from the end of +self+ where +-1+ is the last element. + * If `count` is negative then it rotates in the opposite direction, starting + * from the end of `self` where +-1+ is the last element. * * a = [ "a", "b", "c", "d" ] * a.rotate #=> ["b", "c", "d", "a"] @@ -272,7 +310,6 @@ ary_rotate(mrb_state *mrb, mrb_value self) mrb_value ary = mrb_ary_new(mrb); mrb_int len = RARRAY_LEN(self); - mrb_value *p = RARRAY_PTR(self); mrb_int idx; if (len <= 0) return ary; @@ -282,8 +319,10 @@ ary_rotate(mrb_state *mrb, mrb_value self) else { idx = count % len; } + /* Hoist pointer retrieval outside loop */ + mrb_value *ptr = RARRAY_PTR(self); for (mrb_int i = 0; i ary * - * Rotates +self+ in place so that the element at +count+ comes first, and - * returns +self+. + * Rotates `self` in place so that the element at `count` comes first, and + * returns `self`. * - * If +count+ is negative then it rotates in the opposite direction, starting - * from the end of the array where +-1+ is the last element. + * If `count` is negative then it rotates in the opposite direction, starting + * from the end of the array where `-1` is the last element. * * a = [ "a", "b", "c", "d" ] * a.rotate! #=> ["b", "c", "d", "a"] @@ -360,76 +399,121 @@ ary_rotate_bang(mrb_state *mrb, mrb_value self) #define SET_OP_HASH_THRESHOLD 32 -static mrb_value -ary_subtract_internal(mrb_state *mrb, mrb_value self, mrb_int other_argc, const mrb_value *other_argv) +/* Helper functions for temporary khash sets */ +static void +ary_init_temp_set(mrb_state *mrb, ary_set_t *set, mrb_int capacity) { - mrb_value result_ary; - struct RArray *self_ary; - mrb_value *p, *p_end; - mrb_int total_other_len = 0; + kh_init_data(ary_set, mrb, set, (khint_t)(capacity > 0 ? capacity : 8)); +} - if (other_argc == 0) { - return mrb_ary_dup(mrb, self); +static void +ary_populate_temp_set(mrb_state *mrb, ary_set_t *set, mrb_value ary) +{ + mrb_int len = RARRAY_LEN(ary); + for (mrb_int i = 0; i < len; i++) { + kh_put(ary_set, mrb, set, RARRAY_PTR(ary)[i]); } +} - for (mrb_int i = 0; i < other_argc; i++) { - mrb_value other = mrb_check_array_type(mrb, other_argv[i]); +static void +ary_destroy_temp_set(mrb_state *mrb, ary_set_t *set) +{ + if (set) { + kh_destroy_data(ary_set, mrb, set); + } +} + + +static mrb_int +ary_get_array_args(mrb_state *mrb, mrb_int argc, const mrb_value **argv_ptr) +{ + mrb_int total_len = 0; + const mrb_value *argv = *argv_ptr; + mrb_value *converted_argv = (mrb_value *)mrb_alloca(mrb, sizeof(mrb_value) * argc); + + for (mrb_int i = 0; i < argc; i++) { + mrb_value other = mrb_check_array_type(mrb, argv[i]); if (mrb_nil_p(other)) { mrb_raise(mrb, E_TYPE_ERROR, "can't convert passed argument to Array"); } - total_other_len += RARRAY_LEN(other); + converted_argv[i] = other; + total_len += RARRAY_LEN(other); + } + *argv_ptr = converted_argv; + return total_len; +} + +static mrb_value +ary_subtract_internal(mrb_state *mrb, mrb_value self, mrb_int argc, const mrb_value *argv) +{ + if (argc == 0) { + return mrb_ary_dup(mrb, self); } - self_ary = mrb_ary_ptr(self); - p = ARY_PTR(self_ary); - p_end = p + ARY_LEN(self_ary); - result_ary = mrb_ary_new(mrb); + mrb_int total_len = ary_get_array_args(mrb, argc, &argv); - if (total_other_len > SET_OP_HASH_THRESHOLD) { - mrb_value hash = mrb_hash_new_capa(mrb, total_other_len); + mrb_value result = mrb_ary_new(mrb); - for (mrb_int i = 0; i < other_argc; i++) { - struct RArray *other_ary = mrb_ary_ptr(other_argv[i]); - mrb_value *other_p = ARY_PTR(other_ary); - mrb_value *other_p_end = other_p + ARY_LEN(other_ary); - while (other_p < other_p_end) { - mrb_hash_set(mrb, hash, *other_p, mrb_true_value()); - other_p++; - } + if (total_len > SET_OP_HASH_THRESHOLD) { + /* Create shared copies to protect elements during khash operations */ + mrb_value *argv_copies = (mrb_value *)mrb_alloca(mrb, sizeof(mrb_value) * argc); + for (mrb_int i = 0; i < argc; i++) { + argv_copies[i] = mrb_ary_make_shared_copy(mrb, argv[i]); } - while (p < p_end) { - mrb_value val = mrb_hash_get(mrb, hash, *p); - if (mrb_nil_p(val)) { /* key doesn't exist in any other_ary */ - mrb_ary_push(mrb, result_ary, *p); + ary_set_t set_struct; + ary_set_t *set = &set_struct; + ary_init_temp_set(mrb, set, total_len); + + struct mrb_jmpbuf *prev_jmp = mrb->jmp; + struct mrb_jmpbuf c_jmp; + + MRB_TRY(&c_jmp) { + mrb->jmp = &c_jmp; + for (mrb_int i = 0; i < argc; i++) { + ary_populate_temp_set(mrb, set, argv_copies[i]); } - p++; + + for (mrb_int i = 0; i < RARRAY_LEN(self); i++) { + mrb_value p = RARRAY_PTR(self)[i]; + khiter_t k = kh_get(ary_set, mrb, set, p); + if (kh_is_end(set, k)) { /* key doesn't exist in any ary */ + mrb_ary_push(mrb, result, p); + } + } + mrb->jmp = prev_jmp; } + MRB_CATCH(&c_jmp) { + mrb->jmp = prev_jmp; + ary_destroy_temp_set(mrb, set); + MRB_THROW(mrb->jmp); + } + MRB_END_EXC(&c_jmp); + + ary_destroy_temp_set(mrb, set); } else { - while (p < p_end) { + mrb_int self_len = RARRAY_LEN(self); + for (mrb_int i = 0; i < self_len; i++) { + mrb_value p = RARRAY_PTR(self)[i]; mrb_bool found = FALSE; - for (mrb_int i = 0; i < other_argc; i++) { - struct RArray *other_ary = mrb_ary_ptr(other_argv[i]); - mrb_value *other_p = ARY_PTR(other_ary); - mrb_value *other_p_end = other_p + ARY_LEN(other_ary); - while (other_p < other_p_end) { - if (mrb_equal(mrb, *p, *other_p)) { + for (mrb_int j = 0; j < argc; j++) { + mrb_int len = RARRAY_LEN(argv[j]); + for (mrb_int k = 0; k < len; k++) { + if (mrb_equal(mrb, p, RARRAY_PTR(argv[j])[k])) { found = TRUE; break; } - other_p++; } if (found) break; } if (!found) { - mrb_ary_push(mrb, result_ary, *p); + mrb_ary_push(mrb, result, p); } - p++; } } - return result_ary; + return result; } /* @@ -437,7 +521,7 @@ ary_subtract_internal(mrb_state *mrb, mrb_value self, mrb_int other_argc, const * ary - other_ary -> new_ary * * Returns a new array that is a copy of the original array, with any items - * that also appear in +other_ary+ removed. + * that also appear in `other_ary` removed. * * [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ] */ @@ -455,7 +539,7 @@ ary_sub(mrb_state *mrb, mrb_value self) * ary.difference(other_ary, ...) -> new_ary * * Returns a new array that is a copy of the original array, removing all - * occurrences of any item that also appear in any of the +other_ary+s. + * occurrences of any item that also appear in any of the `other_ary`s. * The order is preserved from the original array. * * [1, 2, 3, 4, 5].difference([2, 4], [1, 5]) #=> [3] @@ -469,112 +553,108 @@ ary_difference(mrb_state *mrb, mrb_value self) return ary_subtract_internal(mrb, self, argc, argv); } + +static void +add_uniq(mrb_state *mrb, mrb_value item, mrb_value result) +{ + const mrb_int len = RARRAY_LEN(result); + for (mrb_int i = 0; i < len; i++) { + if (mrb_eql(mrb, item, RARRAY_PTR(result)[i])) { + return; + } + } + mrb_ary_push(mrb, result, item); +} + +static mrb_value +ary_union_internal(mrb_state *mrb, mrb_value self, mrb_int argc, const mrb_value *argv) +{ + mrb_int total_len = ary_get_array_args(mrb, argc, &argv) + RARRAY_LEN(self); + + mrb_value result = mrb_ary_new(mrb); + + if (total_len > SET_OP_HASH_THRESHOLD) { + /* Create shared copies to protect elements during khash operations */ + mrb_value self_copy = mrb_ary_make_shared_copy(mrb, self); + mrb_value *argv_copies = (mrb_value *)mrb_alloca(mrb, sizeof(mrb_value) * argc); + for (mrb_int i = 0; i < argc; i++) { + argv_copies[i] = mrb_ary_make_shared_copy(mrb, argv[i]); + } + + ary_set_t set_struct; + ary_set_t *set = &set_struct; + ary_init_temp_set(mrb, set, total_len); + + struct mrb_jmpbuf *prev_jmp = mrb->jmp; + struct mrb_jmpbuf c_jmp; + + MRB_TRY(&c_jmp) { + mrb->jmp = &c_jmp; + /* Add unique elements from self */ + for (mrb_int i = 0; i < RARRAY_LEN(self_copy); i++) { + mrb_value elem = RARRAY_PTR(self_copy)[i]; + khiter_t k = kh_get(ary_set, mrb, set, elem); + if (kh_is_end(set, k)) { + kh_put(ary_set, mrb, set, elem); + mrb_ary_push(mrb, result, elem); + } + } + + /* Add unique elements from others */ + for (mrb_int i = 0; i < argc; i++) { + mrb_value other = argv_copies[i]; + for (mrb_int j = 0; j < RARRAY_LEN(other); j++) { + mrb_value elem = RARRAY_PTR(other)[j]; + khiter_t k = kh_get(ary_set, mrb, set, elem); + if (kh_is_end(set, k)) { + kh_put(ary_set, mrb, set, elem); + mrb_ary_push(mrb, result, elem); + } + } + } + mrb->jmp = prev_jmp; + } + MRB_CATCH(&c_jmp) { + mrb->jmp = prev_jmp; + ary_destroy_temp_set(mrb, set); + MRB_THROW(mrb->jmp); + } + MRB_END_EXC(&c_jmp); + + ary_destroy_temp_set(mrb, set); + } + else { + /* Use linear search for small arrays */ + /* Add unique elements from self */ + mrb_int alen = RARRAY_LEN(self); + for (mrb_int i = 0; i < alen; i++) { + add_uniq(mrb, RARRAY_PTR(self)[i], result); + } + + /* Add unique elements from others */ + for (mrb_int i = 0; i < argc; i++) { + mrb_value other = argv[i]; + mrb_int olen = RARRAY_LEN(other); + for (mrb_int j = 0; j < olen; j++) { + add_uniq(mrb, RARRAY_PTR(other)[j], result); + } + } + } + + return result; +} + /* * call-seq: * ary | other_ary -> new_ary * * Set Union---Returns a new array by joining this array with - * other_ary, removing duplicates. + * `other_ary`, removing duplicates. * * [ "a", "b", "c" ] | [ "c", "d", "a" ] * #=> [ "a", "b", "c", "d" ] */ -static mrb_value -ary_union_internal(mrb_state *mrb, mrb_value self, mrb_int other_argc, const mrb_value *other_argv) -{ - mrb_value result_ary; - mrb_int total_len = RARRAY_LEN(self); - - for (mrb_int i = 0; i < other_argc; i++) { - mrb_value other = mrb_check_array_type(mrb, other_argv[i]); - if (mrb_nil_p(other)) { - mrb_raise(mrb, E_TYPE_ERROR, "can't convert passed argument to Array"); - } - total_len += RARRAY_LEN(other); - } - - result_ary = mrb_ary_new(mrb); - - if (total_len > SET_OP_HASH_THRESHOLD) { - mrb_value hash = mrb_hash_new_capa(mrb, total_len); - - /* Add elements from self */ - struct RArray *self_ary = mrb_ary_ptr(self); - mrb_value *p = ARY_PTR(self_ary); - mrb_value *p_end = p + ARY_LEN(self_ary); - while (p < p_end) { - mrb_value val = mrb_hash_get(mrb, hash, *p); - if (mrb_nil_p(val)) { /* key doesn't exist */ - mrb_hash_set(mrb, hash, *p, mrb_true_value()); - mrb_ary_push(mrb, result_ary, *p); - } - p++; - } - - /* Add elements from others */ - for (mrb_int i = 0; i < other_argc; i++) { - struct RArray *other_ary = mrb_ary_ptr(other_argv[i]); - mrb_value *other_p = ARY_PTR(other_ary); - mrb_value *other_p_end = other_p + ARY_LEN(other_ary); - while (other_p < other_p_end) { - mrb_value val = mrb_hash_get(mrb, hash, *other_p); - if (mrb_nil_p(val)) { /* key doesn't exist */ - mrb_hash_set(mrb, hash, *other_p, mrb_true_value()); - mrb_ary_push(mrb, result_ary, *other_p); - } - other_p++; - } - } - } - else { - /* Use linear search for small arrays */ - /* Add unique elements from self */ - struct RArray *self_ary = mrb_ary_ptr(self); - mrb_value *p = ARY_PTR(self_ary); - mrb_value *p_end = p + ARY_LEN(self_ary); - while (p < p_end) { - mrb_bool found = FALSE; - mrb_int result_len = RARRAY_LEN(result_ary); - mrb_value *result_ptr = ARY_PTR(RARRAY(result_ary)); - for (mrb_int j = 0; j < result_len; j++) { - if (mrb_equal(mrb, *p, result_ptr[j])) { - found = TRUE; - break; - } - } - if (!found) { - mrb_ary_push(mrb, result_ary, *p); - } - p++; - } - - /* Add unique elements from others */ - for (mrb_int i = 0; i < other_argc; i++) { - mrb_value other = other_argv[i]; - mrb_value *other_p = ARY_PTR(RARRAY(other)); - mrb_value *other_p_end = other_p + ARY_LEN(RARRAY(other)); - while (other_p < other_p_end) { - mrb_bool found = FALSE; - mrb_int result_len = RARRAY_LEN(result_ary); - mrb_value *result_ptr = ARY_PTR(RARRAY(result_ary)); - for (mrb_int j = 0; j < result_len; j++) { - if (mrb_equal(mrb, *other_p, result_ptr[j])) { - found = TRUE; - break; - } - } - if (!found) { - mrb_ary_push(mrb, result_ary, *other_p); - } - other_p++; - } - } - } - - return result_ary; -} - static mrb_value ary_union(mrb_state *mrb, mrb_value self) { @@ -588,7 +668,7 @@ ary_union(mrb_state *mrb, mrb_value self) * ary.union(other_ary,...) -> new_ary * * Set Union---Returns a new array by joining this array with - * other_arys, removing duplicates. + * `other_ary`s, removing duplicates. * * ["a", "b", "c"].union(["c", "d", "a"], ["a", "c", "e"]) * #=> ["a", "b", "c", "d", "e"] @@ -602,6 +682,95 @@ ary_union_multi(mrb_state *mrb, mrb_value self) return ary_union_internal(mrb, self, argc, argv); } +static mrb_value +ary_intersection_internal(mrb_state *mrb, mrb_value self, mrb_int argc, const mrb_value *argv) +{ + if (argc == 0) { + return mrb_ary_new(mrb); + } + + mrb_int total_len = ary_get_array_args(mrb, argc, &argv); + + mrb_value result = mrb_ary_new(mrb); + + if (total_len > SET_OP_HASH_THRESHOLD) { + /* Create shared copies to protect elements during khash operations */ + mrb_value *argv_copies = (mrb_value *)mrb_alloca(mrb, sizeof(mrb_value) * argc); + for (mrb_int i = 0; i < argc; i++) { + argv_copies[i] = mrb_ary_make_shared_copy(mrb, argv[i]); + } + + ary_set_t set_struct; + ary_set_t *set = &set_struct; + ary_init_temp_set(mrb, set, total_len); + + struct mrb_jmpbuf *prev_jmp = mrb->jmp; + struct mrb_jmpbuf c_jmp; + + MRB_TRY(&c_jmp) { + mrb->jmp = &c_jmp; + for (mrb_int i = 0; i < argc; i++) { + ary_populate_temp_set(mrb, set, argv_copies[i]); + } + + for (mrb_int i = 0; i < RARRAY_LEN(self); i++) { + mrb_value p = RARRAY_PTR(self)[i]; + khiter_t k = kh_get(ary_set, mrb, set, p); + if (!kh_is_end(set, k)) { + mrb_ary_push(mrb, result, p); + kh_del(ary_set, mrb, set, k); + } + } + mrb->jmp = prev_jmp; + } + MRB_CATCH(&c_jmp) { + mrb->jmp = prev_jmp; + ary_destroy_temp_set(mrb, set); + MRB_THROW(mrb->jmp); + } + MRB_END_EXC(&c_jmp); + + ary_destroy_temp_set(mrb, set); + } + else { + mrb_int self_len = RARRAY_LEN(self); + for (mrb_int i = 0; i < self_len; i++) { + mrb_value p = RARRAY_PTR(self)[i]; + mrb_bool found_in_all = TRUE; + + for (mrb_int j = 0; j < argc; j++) { + mrb_bool found_in_current_other = FALSE; + mrb_int len = RARRAY_LEN(argv[j]); + for (mrb_int k = 0; k < len; k++) { + if (mrb_equal(mrb, p, RARRAY_PTR(argv[j])[k])) { + found_in_current_other = TRUE; + break; + } + } + if (!found_in_current_other) { + found_in_all = FALSE; + break; + } + } + + if (found_in_all) { + mrb_bool already_added = FALSE; + mrb_int result_len = RARRAY_LEN(result); + for (mrb_int j = 0; j < result_len; j++) { + if (mrb_equal(mrb, p, RARRAY_PTR(result)[j])) { + already_added = TRUE; + break; + } + } + if (!already_added) { + mrb_ary_push(mrb, result, p); + } + } + } + } + return result; +} + /* * call-seq: * ary & other_ary -> new_ary @@ -612,103 +781,6 @@ ary_union_multi(mrb_state *mrb, mrb_value self) * [ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ] */ -static mrb_value -ary_intersection_internal(mrb_state *mrb, mrb_value self, mrb_int other_argc, const mrb_value *other_argv) -{ - mrb_value result_ary; - struct RArray *self_ary; - mrb_value *p, *p_end; - mrb_int total_other_len = 0; - - if (other_argc == 0) { - return mrb_ary_new(mrb); - } - - for (mrb_int i = 0; i < other_argc; i++) { - mrb_value other = mrb_check_array_type(mrb, other_argv[i]); - if (mrb_nil_p(other)) { - mrb_raise(mrb, E_TYPE_ERROR, "can't convert passed argument to Array"); - } - total_other_len += RARRAY_LEN(other); - } - - self_ary = mrb_ary_ptr(self); - p = ARY_PTR(self_ary); - p_end = p + ARY_LEN(self_ary); - - result_ary = mrb_ary_new(mrb); - - if (total_other_len > SET_OP_HASH_THRESHOLD) { - mrb_value hash = mrb_hash_new_capa(mrb, total_other_len); - - /* Populate hash with elements from all other_argv */ - for (mrb_int i = 0; i < other_argc; i++) { - struct RArray *other_ary = mrb_ary_ptr(other_argv[i]); - mrb_value *other_p = ARY_PTR(other_ary); - mrb_value *other_p_end = other_p + ARY_LEN(other_ary); - while (other_p < other_p_end) { - mrb_hash_set(mrb, hash, *other_p, mrb_true_value()); - other_p++; - } - } - - /* Check elements from self against hash */ - while (p < p_end) { - mrb_value val = mrb_hash_get(mrb, hash, *p); - if (!mrb_nil_p(val)) { /* key exists in other_ary */ - mrb_ary_push(mrb, result_ary, *p); - mrb_hash_delete_key(mrb, hash, *p); /* remove to ensure uniqueness */ - } - p++; - } - } - else { - /* Use linear search for small arrays */ - while (p < p_end) { - mrb_bool found_in_all = TRUE; - for (mrb_int i = 0; i < other_argc; i++) { - struct RArray *other_ary = mrb_ary_ptr(other_argv[i]); - mrb_value *other_p = ARY_PTR(other_ary); - mrb_value *other_p_end = other_p + ARY_LEN(other_ary); - mrb_bool found_in_current_other = FALSE; - - while (other_p < other_p_end) { - if (mrb_equal(mrb, *p, *other_p)) { - found_in_current_other = TRUE; - break; - } - other_p++; - } - if (!found_in_current_other) { - found_in_all = FALSE; - break; - } - } - - if (found_in_all) { - /* Check if already in result to ensure uniqueness */ - mrb_int result_len = RARRAY_LEN(result_ary); - mrb_value *result_ptr = RARRAY_PTR(result_ary); - mrb_bool already_added = FALSE; - - for (mrb_int i = 0; i < result_len; i++) { - if (mrb_equal(mrb, *p, result_ptr[i])) { - already_added = TRUE; - break; - } - } - - if (!already_added) { - mrb_ary_push(mrb, result_ary, *p); - } - } - p++; - } - } - - return result_ary; -} - static mrb_value ary_intersection(mrb_state *mrb, mrb_value self) { @@ -722,7 +794,7 @@ ary_intersection(mrb_state *mrb, mrb_value self) * ary.intersection(other_ary,...) -> new_ary * * Set Intersection---Returns a new array containing elements common to - * this array and other_arys, removing duplicates. The order is + * this array and `other_ary`s, removing duplicates. The order is * preserved from the original array. * * [1, 2, 3].intersection([3, 4, 1], [1, 3, 5]) #=> [1, 3] @@ -736,13 +808,12 @@ ary_intersection_multi(mrb_state *mrb, mrb_value self) return ary_intersection_internal(mrb, self, argc, argv); } - /* * call-seq: * ary.intersect?(other_ary) -> true or false * - * Returns +true+ if the array and +other_ary+ have at least one element in - * common, otherwise returns +false+. + * Returns `true` if the array and `other_ary` have at least one element in + * common, otherwise returns `false`. * * a = [ 1, 2, 3 ] * b = [ 3, 4, 5 ] @@ -755,68 +826,65 @@ static mrb_value ary_intersect_p(mrb_state *mrb, mrb_value self) { mrb_value other; - struct RArray *self_ary, *other_ary, *shorter_ary, *longer_ary; - mrb_value *shorter_p, *shorter_p_end, *longer_p, *longer_p_end; - mrb_get_args(mrb, "A", &other); - self_ary = mrb_ary_ptr(self); - other_ary = mrb_ary_ptr(other); - - /* Choose shorter array for hash, longer for iteration (optimization) */ - if (ARY_LEN(self_ary) > ARY_LEN(other_ary)) { - shorter_ary = other_ary; - longer_ary = self_ary; + mrb_value shorter_ary, longer_ary; + if (RARRAY_LEN(self) > RARRAY_LEN(other)) { + shorter_ary = other; + longer_ary = self; } else { - shorter_ary = self_ary; - longer_ary = other_ary; + shorter_ary = self; + longer_ary = other; } - /* Early termination for empty arrays */ - if (ARY_LEN(shorter_ary) == 0 || ARY_LEN(longer_ary) == 0) { + if (RARRAY_LEN(shorter_ary) == 0 || RARRAY_LEN(longer_ary) == 0) { return mrb_false_value(); } - if (ARY_LEN(shorter_ary) > SET_OP_HASH_THRESHOLD) { - /* Use hash for large arrays to achieve O(n) performance */ - mrb_value hash = mrb_hash_new_capa(mrb, ARY_LEN(shorter_ary)); + if (RARRAY_LEN(shorter_ary) > SET_OP_HASH_THRESHOLD) { + mrb_value shorter_ary_copy = mrb_ary_make_shared_copy(mrb, shorter_ary); - /* Populate hash with elements from shorter array */ - shorter_p = ARY_PTR(shorter_ary); - shorter_p_end = shorter_p + ARY_LEN(shorter_ary); - while (shorter_p < shorter_p_end) { - mrb_hash_set(mrb, hash, *shorter_p, mrb_true_value()); - shorter_p++; - } + ary_set_t set_struct; + ary_set_t *set = &set_struct; + ary_init_temp_set(mrb, set, RARRAY_LEN(shorter_ary_copy)); - /* Check elements from longer array against hash with early termination */ - longer_p = ARY_PTR(longer_ary); - longer_p_end = longer_p + ARY_LEN(longer_ary); - while (longer_p < longer_p_end) { - mrb_value val = mrb_hash_get(mrb, hash, *longer_p); - if (!mrb_nil_p(val)) { /* key exists in shorter array */ - return mrb_true_value(); /* Early termination */ + struct mrb_jmpbuf *prev_jmp = mrb->jmp; + struct mrb_jmpbuf c_jmp; + mrb_bool found = FALSE; + + MRB_TRY(&c_jmp) { + mrb->jmp = &c_jmp; + ary_populate_temp_set(mrb, set, shorter_ary_copy); + + for (mrb_int i = 0; i < RARRAY_LEN(longer_ary); i++) { + khiter_t k = kh_get(ary_set, mrb, set, RARRAY_PTR(longer_ary)[i]); + if (!kh_is_end(set, k)) { + found = TRUE; + break; + } } - longer_p++; + mrb->jmp = prev_jmp; + } + MRB_CATCH(&c_jmp) { + mrb->jmp = prev_jmp; + ary_destroy_temp_set(mrb, set); + MRB_THROW(mrb->jmp); + } + MRB_END_EXC(&c_jmp); + + ary_destroy_temp_set(mrb, set); + if (found) { + return mrb_true_value(); } } else { - /* Use linear search for small arrays */ - longer_p = ARY_PTR(longer_ary); - longer_p_end = longer_p + ARY_LEN(longer_ary); - while (longer_p < longer_p_end) { - /* Check if element exists in shorter array */ - shorter_p = ARY_PTR(shorter_ary); - shorter_p_end = shorter_p + ARY_LEN(shorter_ary); - - while (shorter_p < shorter_p_end) { - if (mrb_equal(mrb, *longer_p, *shorter_p)) { - return mrb_true_value(); /* Early termination */ + for (mrb_int i = 0; i < RARRAY_LEN(longer_ary); i++) { + for (mrb_int j = 0; j < RARRAY_LEN(shorter_ary); j++) { + if (mrb_equal(mrb, RARRAY_PTR(longer_ary)[i], RARRAY_PTR(shorter_ary)[j])) { + return mrb_true_value(); } - shorter_p++; } - longer_p++; } } @@ -834,9 +902,7 @@ ary_fill_parse_arg(mrb_state *mrb, mrb_value self) { mrb_value arg0 = mrb_nil_value(), arg1 = mrb_nil_value(), arg2 = mrb_nil_value(); mrb_value block = mrb_nil_value(); - mrb_int argc; - - argc = mrb_get_args(mrb, "|ooo&", &arg0, &arg1, &arg2, &block); + mrb_int argc = mrb_get_args(mrb, "|ooo&", &arg0, &arg1, &arg2, &block); struct RArray *ary = mrb_ary_ptr(self); mrb_int ary_len = ARY_LEN(ary); @@ -926,6 +992,13 @@ ary_fill_exec(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "iio", &start, &length, &obj); + if (start < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "negative start index"); + } + if (length < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "negative length"); + } + struct RArray *ary = mrb_ary_ptr(self); mrb_int ary_len = ARY_LEN(ary); @@ -950,53 +1023,6 @@ ary_fill_exec(mrb_state *mrb, mrb_value self) return self; } -/* - * Internal helper for Array#uniq without blocks. - * Uses hash-based deduplication for large arrays, - * linear search for small arrays. - */ -static mrb_value -ary_uniq(mrb_state *mrb, mrb_value self) -{ - struct RArray *ary = mrb_ary_ptr(self); - mrb_int len = ARY_LEN(ary); - mrb_value *ptr = ARY_PTR(ary); - mrb_value result = mrb_ary_new_capa(mrb, len); - - if (len == 0) { - return result; - } - - if (len > SET_OP_HASH_THRESHOLD) { - mrb_value hash = mrb_hash_new_capa(mrb, len); - for (mrb_int i = 0; i < len; i++) { - mrb_value elem = ptr[i]; - if (mrb_nil_p(mrb_hash_get(mrb, hash, elem))) { - mrb_hash_set(mrb, hash, elem, mrb_true_value()); - mrb_ary_push(mrb, result, elem); - } - } - } - else { - for (mrb_int i = 0; i < len; i++) { - mrb_value elem = ptr[i]; - mrb_bool found = FALSE; - mrb_value *result_ptr = ARY_PTR(RARRAY(result)); - for (mrb_int j = 0; j < RARRAY_LEN(result); j++) { - if (mrb_equal(mrb, elem, result_ptr[j])) { - found = TRUE; - break; - } - } - if (!found) { - mrb_ary_push(mrb, result, elem); - } - } - } - - return result; -} - /* * Internal helper for Array#uniq! without blocks. * Modifies array in-place, returns nil if no changes. @@ -1004,43 +1030,65 @@ ary_uniq(mrb_state *mrb, mrb_value self) static mrb_value ary_uniq_bang(mrb_state *mrb, mrb_value self) { - struct RArray *ary = mrb_ary_ptr(self); - mrb_int len = ARY_LEN(ary); + mrb_int len = RARRAY_LEN(self); if (len <= 1) { return mrb_nil_value(); } - mrb_ary_modify(mrb, ary); - mrb_value *ptr = ARY_PTR(ary); + mrb_ary_modify(mrb, mrb_ary_ptr(self)); mrb_int write_pos = 0; if (len > SET_OP_HASH_THRESHOLD) { - mrb_value hash = mrb_hash_new_capa(mrb, len); - for (mrb_int read_pos = 0; read_pos < len; read_pos++) { - mrb_value elem = ptr[read_pos]; - if (mrb_nil_p(mrb_hash_get(mrb, hash, elem))) { - mrb_hash_set(mrb, hash, elem, mrb_true_value()); - if (write_pos != read_pos) { - ptr[write_pos] = elem; + /* Create shared copy to protect elements during khash operations */ + mrb_value self_copy = mrb_ary_make_shared_copy(mrb, self); + + ary_set_t set_struct; + ary_set_t *set = &set_struct; + ary_init_temp_set(mrb, set, len); + + struct mrb_jmpbuf *prev_jmp = mrb->jmp; + struct mrb_jmpbuf c_jmp; + + MRB_TRY(&c_jmp) { + mrb->jmp = &c_jmp; + ary_populate_temp_set(mrb, set, self_copy); + + for (mrb_int read_pos = 0; read_pos < len; read_pos++) { + mrb_value elem = RARRAY_PTR(self)[read_pos]; + khiter_t k = kh_get(ary_set, mrb, set, elem); + if (!kh_is_end(set, k)) { + if (write_pos != read_pos) { + RARRAY_PTR(self)[write_pos] = elem; + } + write_pos++; + kh_del(ary_set, mrb, set, k); } - write_pos++; } + mrb->jmp = prev_jmp; } + MRB_CATCH(&c_jmp) { + mrb->jmp = prev_jmp; + ary_destroy_temp_set(mrb, set); + MRB_THROW(mrb->jmp); + } + MRB_END_EXC(&c_jmp); + + ary_destroy_temp_set(mrb, set); } else { for (mrb_int read_pos = 0; read_pos < len; read_pos++) { - mrb_value elem = ptr[read_pos]; + mrb_value elem = RARRAY_PTR(self)[read_pos]; mrb_bool found = FALSE; for (mrb_int j = 0; j < write_pos; j++) { - if (mrb_equal(mrb, elem, ptr[j])) { + if (mrb_equal(mrb, elem, RARRAY_PTR(self)[j])) { found = TRUE; break; } } if (!found) { if (write_pos != read_pos) { - ptr[write_pos] = elem; + RARRAY_PTR(self)[write_pos] = elem; } write_pos++; } @@ -1055,6 +1103,19 @@ ary_uniq_bang(mrb_state *mrb, mrb_value self) return self; } +/* + * Internal helper for Array#uniq without blocks. + * Uses hash-based deduplication for large arrays, + * linear search for small arrays. + */ +static mrb_value +ary_uniq(mrb_state *mrb, mrb_value self) +{ + mrb_value ary = mrb_ary_dup(mrb, self); + ary_uniq_bang(mrb, ary); + return ary; +} + /* Internal helper for flatten operations using iterative stack-based approach */ static mrb_value flatten_internal(mrb_state *mrb, mrb_value self, mrb_int level, mrb_bool *modified) @@ -1103,7 +1164,7 @@ flatten_internal(mrb_state *mrb, mrb_value self, mrb_int level, mrb_bool *modifi * Returns a new array that is a one-dimensional flattening of this * array (recursively). That is, for every element that is an array, * extract its elements into the new array. If the optional - * level argument determines the level of recursion to flatten. + * `level` argument determines the level of recursion to flatten. * * s = [ 1, 2, 3 ] #=> [1, 2, 3] * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] @@ -1196,10 +1257,9 @@ ary_fetch(mrb_state *mrb, mrb_value self) * ary.flatten! -> ary or nil * ary.flatten!(level) -> array or nil * - * Flattens +self+ in place. - * Returns nil if no modifications were made (i.e., - * ary contains no subarrays.) If the optional level - * argument determines the level of recursion to flatten. + * Flattens `self` in place. Returns `nil` if no modifications were made + * (i.e., *ary* contains no subarrays.) If the optional `level` argument + * determines the level of recursion to flatten. * * a = [ 1, 2, [3, [4, 5] ] ] * a.flatten! #=> [1, 2, 3, 4, 5] @@ -1280,6 +1340,36 @@ ary_insert(mrb_state *mrb, mrb_value self) return self; } +/* + * Internal helper for Array#product to construct a group array. + * Takes the base array (self), the array of other arrays (arys), + * the current iteration index (current_i), and the desired length + * of the group array (group_len). + */ +static mrb_value +ary_product_group(mrb_state *mrb, mrb_value self_ary) +{ + mrb_value arys_ary; + mrb_int current_i, group_len; + mrb_get_args(mrb, "Aii", &arys_ary, ¤t_i, &group_len); + + mrb_value group = mrb_ary_new_capa(mrb, group_len); + mrb_int j = RARRAY_LEN(arys_ary); // Corresponds to 'size' in Ruby + mrb_int n = current_i; + + while (j > 0) { + j -= 1; + mrb_value a = RARRAY_PTR(arys_ary)[j]; // arys[j] + mrb_check_type(mrb, a, MRB_TT_ARRAY); + mrb_int b = RARRAY_LEN(a); // a.size + mrb_ary_set(mrb, group, j + 1, RARRAY_PTR(a)[n % b]); + n /= b; + } + mrb_ary_set(mrb, group, 0, RARRAY_PTR(self_ary)[n]); + + return group; +} + /* * call-seq: * ary.deconstruct -> ary @@ -1305,6 +1395,113 @@ ary_deconstruct(mrb_state *mrb, mrb_value ary) return ary; } + +/* + * Internal method to initialize combination state. + * Returns opaque state object for use by __combination_next. + */ +static mrb_value +ary_combination_init(mrb_state *mrb, mrb_value self) +{ + mrb_int n; + mrb_bool permutation; + + mrb_get_args(mrb, "ib", &n, &permutation); +#if MRB_INT_MAX > SIZE_MAX + if (n > SIZE_MAX) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "number too large"); + } +#endif + + 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); + + if (n > 0) { + state->indices = (mrb_int*)mrb_calloc(mrb, n, sizeof(mrb_int)); + } + + return mrb_obj_value(d); +} + +/* + * Internal method to get next combination as index array. + * Returns array of indices or nil when iteration is complete. + */ +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"); + } + + /* Check if iteration is complete */ + if (state->finished) return mrb_nil_value(); + + /* Validate array hasn't been modified during iteration */ + if (RARRAY_LEN(self) != state->array_size) { + 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; + 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--; + } + + 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; + } + else { + state->indices[i] = state->indices[i - 1]; + } + } + } + + return result; +} + void mrb_mruby_array_ext_gem_init(mrb_state* mrb) { @@ -1336,6 +1533,9 @@ mrb_mruby_array_ext_gem_init(mrb_state* mrb) mrb_define_method_id(mrb, a, MRB_SYM(__fetch), ary_fetch, MRB_ARGS_REQ(3)); mrb_define_method_id(mrb, a, MRB_SYM(insert), ary_insert, MRB_ARGS_ARG(1, -1)); mrb_define_method_id(mrb, a, MRB_SYM(deconstruct), ary_deconstruct, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, a, MRB_SYM(__product_group), ary_product_group, MRB_ARGS_REQ(4)); + mrb_define_method_id(mrb, a, MRB_SYM(__combination_init), ary_combination_init, MRB_ARGS_REQ(2)); + mrb_define_method_id(mrb, a, MRB_SYM(__combination_next), ary_combination_next, MRB_ARGS_REQ(1)); } void diff --git a/mrbgems/mruby-bigint/core/bigint.c b/mrbgems/mruby-bigint/core/bigint.c index 7bf440e8e..331c30169 100644 --- a/mrbgems/mruby-bigint/core/bigint.c +++ b/mrbgems/mruby-bigint/core/bigint.c @@ -24,31 +24,181 @@ #define imin(x,y) (((x)<(y))?(x):(y)) #define dg(x,i) (((size_t)i < (x)->sz)?(x)->p[i]:0) -static void -mpz_init(mrb_state *mrb, mpz_t *s) +#ifndef MRB_BIGINT_POOL_SIZE +#define MRB_BIGINT_POOL_SIZE 512 /* 2KB on 32-bit, 4KB on 64-bit */ +#endif + +/* Scoped Memory Pool Infrastructure */ +#if MRB_BIGINT_POOL_SIZE == 0 +#define mpz_ctx_t mrb_state +#define MPZ_MRB(ctx) (ctx) +#define MPZ_HAS_POOL(ctx) (0) +#define MPZ_CTX_INIT(mrb_ptr, ctx, pool_ptr) mrb_state *ctx = (mrb_ptr); +#define pool_save(ctx) 0 +#define pool_restore(ctx, state) (void)state +#define pool_alloc(pool, limbs) NULL +#else +typedef struct mpz_pool { + mp_limb data[MRB_BIGINT_POOL_SIZE]; + size_t used; +} mpz_pool_t; + +/* MPZ Context Architecture - unified parameter for mrb_state and optional pool */ +typedef struct mpz_context { + mrb_state *mrb; + mpz_pool_t *pool; /* NULL for heap-only operations */ +} mpz_ctx_t; + +/* Convenience macros for context creation */ +#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 = &(ctx ## _struct); + +/* Access macros for readability */ +#define MPZ_MRB(ctx) ((ctx)->mrb) +#define MPZ_POOL(ctx) ((ctx)->pool) +#define MPZ_HAS_POOL(ctx) ((ctx)->pool != NULL) + +static size_t +pool_save(mpz_ctx_t *ctx) { - s->p = NULL; - s->sn=0; - s->sz=0; + mpz_pool_t *pool = MPZ_POOL(ctx); + return pool ? pool->used : 0; } static void -mpz_realloc(mrb_state *mrb, mpz_t *x, size_t size) +pool_restore(mpz_ctx_t *ctx, size_t state) +{ + mpz_pool_t *pool = MPZ_POOL(ctx); + if (pool) { + pool->used = state; + } +} + +static mp_limb* +pool_alloc(mpz_pool_t *pool, size_t limbs) +{ + if (!pool || pool->used + limbs > MRB_BIGINT_POOL_SIZE) { + return NULL; /* Force fallback to heap */ + } + + mp_limb *ptr = &pool->data[pool->used]; + pool->used += limbs; + return ptr; +} +#endif + +/* Zero n limbs at p */ +static inline void +limb_zero(mp_limb *p, size_t n) +{ + memset(p, 0, n * sizeof(mp_limb)); +} + +static void +mpz_init(mpz_ctx_t *ctx, mpz_t *s) +{ + s->p = NULL; + s->sn = 0; + s->sz = 0; +} + +/* Heap-preferred allocation */ +static void +mpz_init_heap(mpz_ctx_t *ctx, mpz_t *s, size_t hint) +{ + s->sn = 0; + if (hint > 0) { + s->p = (mp_limb*)mrb_malloc(MPZ_MRB(ctx), hint * sizeof(mp_limb)); + limb_zero(s->p, hint); + s->sz = hint; + } + else { + s->p = NULL; /* Lazy allocation via mpz_realloc later */ + s->sz = 0; + } +} + +#if MRB_BIGINT_POOL_SIZE > 0 +/* Pool-preferred allocation (future: mpz_init_temp) */ +static void +mpz_init_temp(mpz_ctx_t *ctx, mpz_t *s, size_t hint) +{ + s->sn = 0; + + + if (hint > 0 && MPZ_HAS_POOL(ctx)) { + mp_limb *pool_ptr = pool_alloc(MPZ_POOL(ctx), hint); + if (pool_ptr) { + s->p = pool_ptr; + s->sz = hint; + return; + } + } + /* Fallback to heap allocation */ + mpz_init_heap(ctx, s, hint); +} +#else +#define mpz_init_temp(ctx, s, hint) mpz_init_heap(ctx, s, hint) +#endif + +/* Check if mpz_t uses pool memory */ +#if MRB_BIGINT_POOL_SIZE > 0 +static int +is_pool_memory(mpz_t *z, mpz_pool_t *pool) +{ + if (!pool || !z->p) return 0; + uintptr_t ptr_addr = (uintptr_t)z->p; + uintptr_t pool_start = (uintptr_t)pool->data; + uintptr_t pool_end = pool_start + sizeof(pool->data); + return ptr_addr >= pool_start && ptr_addr < pool_end; +} +#endif + +static void +mpz_realloc(mpz_ctx_t *ctx, mpz_t *x, size_t size) { if (x->sz < size) { - x->p=(mp_limb*)mrb_realloc(mrb, x->p, size*sizeof(mp_limb)); - for (size_t i=x->sz; ip[i] = 0; + /* Check for overflow in size calculation */ + if (size > SIZE_MAX / sizeof(mp_limb)) { + mrb_state *mrb = MPZ_MRB(ctx); + mrb_raise(mrb, E_RUNTIME_ERROR, "bigint size too large"); + } + + size_t old_sz = x->sz; + +#if MRB_BIGINT_POOL_SIZE > 0 + /* Pool memory cannot be reallocated - must use heap */ + if (MPZ_HAS_POOL(ctx) && is_pool_memory(x, MPZ_POOL(ctx))) { + /* Allocate new heap memory and copy from pool */ + mp_limb *new_p = (mp_limb*)mrb_malloc(MPZ_MRB(ctx), size * sizeof(mp_limb)); + if (x->p) { + memcpy(new_p, x->p, old_sz * sizeof(mp_limb)); + } + x->p = new_p; + } + else { +#endif + /* Regular heap reallocation */ + x->p = (mp_limb*)mrb_realloc(MPZ_MRB(ctx), x->p, size * sizeof(mp_limb)); +#if MRB_BIGINT_POOL_SIZE > 0 + } +#endif + + /* Zero-initialize new limbs */ + limb_zero(x->p + old_sz, size - old_sz); x->sz = size; } } static void -mpz_set(mrb_state *mrb, mpz_t *y, mpz_t *x) +mpz_set(mpz_ctx_t *ctx, mpz_t *y, mpz_t *x) { size_t i, k = x->sz; - mpz_realloc(mrb, y, k); + mpz_realloc(ctx, y, k); for (i=0;i < k; i++) y->p[i] = x->p[i]; @@ -57,14 +207,14 @@ mpz_set(mrb_state *mrb, mpz_t *y, mpz_t *x) } static void -mpz_init_set(mrb_state *mrb, mpz_t *s, mpz_t *t) +mpz_init_set(mpz_ctx_t *ctx, mpz_t *s, mpz_t *t) { - mpz_init(mrb, s); - mpz_set(mrb, s, t); + mpz_init(ctx, s); + mpz_set(ctx, s, t); } static void -mpz_set_int(mrb_state *mrb, mpz_t *y, mrb_int v) +mpz_set_int(mpz_ctx_t *ctx, mpz_t *y, mrb_int v) { mrb_uint u; @@ -83,25 +233,26 @@ mpz_set_int(mrb_state *mrb, mpz_t *y, mrb_int v) } #if MRB_INT_BIT > DIG_SIZE if ((u & ~DIG_MASK) != 0) { - mpz_realloc(mrb, y, 2); + mpz_realloc(ctx, y, 2); y->p[1] = (mp_limb)HIGH(u); y->p[0] = (mp_limb)LOW(u); return; } #endif - mpz_realloc(mrb, y, 1); + mpz_realloc(ctx, y, 1); y->p[0] = (mp_limb)u; } + static void -mpz_set_uint64(mrb_state *mrb, mpz_t *y, uint64_t u) +mpz_set_uint64(mpz_ctx_t *ctx, mpz_t *y, uint64_t u) { size_t len = 0; for (uint64_t u0=u; u0; u0>>=DIG_SIZE,len++) ; y->sn = (u != 0); - mpz_realloc(mrb, y, len); + mpz_realloc(ctx, y, len); for (size_t i=0; ip[i] = (mp_limb)LOW(u); u >>= DIG_SIZE; @@ -110,7 +261,7 @@ mpz_set_uint64(mrb_state *mrb, mpz_t *y, uint64_t u) #ifdef MRB_INT32 static void -mpz_set_int64(mrb_state *mrb, mpz_t *y, int64_t v) +mpz_set_int64(mpz_ctx_t *ctx, mpz_t *y, int64_t v) { uint64_t u; @@ -121,7 +272,7 @@ mpz_set_int64(mrb_state *mrb, mpz_t *y, int64_t v) else { u = v; } - mpz_set_uint64(mrb, y, u); + mpz_set_uint64(ctx, y, u); if (v < 0) { y->sn = -1; } @@ -129,25 +280,45 @@ mpz_set_int64(mrb_state *mrb, mpz_t *y, int64_t v) #endif static void -mpz_init_set_int(mrb_state *mrb, mpz_t *y, mrb_int v) +mpz_init_set_int(mpz_ctx_t *ctx, mpz_t *y, mrb_int v) { - mpz_init(mrb, y); - mpz_set_int(mrb, y, v); + mpz_init(ctx, y); + mpz_set_int(ctx, y, v); } static void -mpz_clear(mrb_state *mrb, mpz_t *s) +mpz_clear(mpz_ctx_t *ctx, mpz_t *s) { - if (s->p) mrb_free(mrb, s->p); - s->p = NULL; + if (s->p) { +#if MRB_BIGINT_POOL_SIZE > 0 + if (MPZ_HAS_POOL(ctx) && is_pool_memory(s, MPZ_POOL(ctx))) { + /* Pool memory - don't free, just mark as unused */ + } + else { + mrb_free(MPZ_MRB(ctx), s->p); + } +#else + mrb_free(MPZ_MRB(ctx), s->p); +#endif + s->p = NULL; + } s->sn = 0; s->sz = 0; } static void -mpz_move(mrb_state *mrb, mpz_t *y, mpz_t *x) +mpz_move(mpz_ctx_t *ctx, mpz_t *y, mpz_t *x) { - mpz_clear(mrb, y); +#if MRB_BIGINT_POOL_SIZE > 0 + if (MPZ_HAS_POOL(ctx) && is_pool_memory(x, MPZ_POOL(ctx))) { + /* Source is pool memory - use deep copy instead of pointer transfer */ + mpz_set(ctx, y, x); + mpz_clear(ctx, x); + return; + } +#endif + /* Normal move: transfer ownership */ + mpz_clear(ctx, y); y->sn = x->sn; y->sz = x->sz; y->p = x->p; @@ -162,8 +333,8 @@ digits(mpz_t *x) size_t i; if (x->sz == 0) return 0; - for (i = x->sz - 1; x->p[i] == 0; i--) - if (i == 0) break; + for (i = x->sz - 1; x->p[i] == 0 && i > 0; i--) + ; return i+1; } @@ -176,51 +347,150 @@ trim(mpz_t *x) } /* z = x + y, without regard for sign */ +/* Core addition algorithm for unsigned operands */ static void -uadd(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y) +uadd(mpz_t *z, mpz_t *x, mpz_t *y) { - if (y->sz < x->sz) { - mpz_t *t; /* swap x,y */ - t=x; x=y; y=t; - } - - /* now y->sz >= x->sz */ - mpz_realloc(mrb, z, y->sz+1); - + /* Core multi-limb addition with carry propagation */ mp_dbl_limb c = 0; size_t i; - for (i=0; isz; i++) { + size_t min_sz = (x->sz < y->sz) ? x->sz : y->sz; + + /* Add overlapping limbs from both operands */ + /* 4x unrolled loop for better performance */ + for (i = 0; i + 4 <= min_sz; i += 4) { + c += (mp_dbl_limb)y->p[i] + (mp_dbl_limb)x->p[i]; + z->p[i] = LOW(c); + c >>= DIG_SIZE; + + c += (mp_dbl_limb)y->p[i+1] + (mp_dbl_limb)x->p[i+1]; + z->p[i+1] = LOW(c); + c >>= DIG_SIZE; + + c += (mp_dbl_limb)y->p[i+2] + (mp_dbl_limb)x->p[i+2]; + z->p[i+2] = LOW(c); + c >>= DIG_SIZE; + + c += (mp_dbl_limb)y->p[i+3] + (mp_dbl_limb)x->p[i+3]; + z->p[i+3] = LOW(c); + c >>= DIG_SIZE; + } + + /* Handle remaining elements in overlap */ + for (; i < min_sz; i++) { c += (mp_dbl_limb)y->p[i] + (mp_dbl_limb)x->p[i]; z->p[i] = LOW(c); c >>= DIG_SIZE; } - for (;isz; i++) { + + /* Add remaining limbs from x if it's larger */ + for (; i < x->sz; i++) { + c += x->p[i]; + z->p[i] = LOW(c); + c >>= DIG_SIZE; + } + + /* Add remaining limbs from larger operand */ + /* 4x unrolled loop for better performance */ + for (; i + 4 <= y->sz; i += 4) { + c += y->p[i]; + z->p[i] = LOW(c); + c >>= DIG_SIZE; + + c += y->p[i+1]; + z->p[i+1] = LOW(c); + c >>= DIG_SIZE; + + c += y->p[i+2]; + z->p[i+2] = LOW(c); + c >>= DIG_SIZE; + + c += y->p[i+3]; + z->p[i+3] = LOW(c); + c >>= DIG_SIZE; + } + + /* Handle remaining elements */ + for (; i < y->sz; i++) { c += y->p[i]; z->p[i] = LOW(c); c >>= DIG_SIZE; } + + /* Store final carry */ z->p[y->sz] = (mp_limb)c; } /* z = y - x, ignoring sign */ /* precondition: abs(y) >= abs(x) */ +/* Core subtraction algorithm for unsigned operands */ static void -usub(mrb_state *mrb, mpz_t *z, mpz_t *y, mpz_t *x) +usub(mpz_t *z, mpz_t *y, mpz_t *x) { - mpz_realloc(mrb, z, (size_t)(y->sz)); + /* Core multi-limb subtraction with borrow propagation */ mp_dbl_limb_signed b = 0; size_t i; - for (i=0;isz;i++) { + + /* Subtract overlapping limbs from both operands */ + /* 4x unrolled loop for better performance */ + for (i = 0; i + 4 <= x->sz; i += 4) { + b += (mp_dbl_limb_signed)y->p[i]; + b -= (mp_dbl_limb_signed)x->p[i]; + z->p[i] = LOW(b); + b = HIGH(b); + + b += (mp_dbl_limb_signed)y->p[i+1]; + b -= (mp_dbl_limb_signed)x->p[i+1]; + z->p[i+1] = LOW(b); + b = HIGH(b); + + b += (mp_dbl_limb_signed)y->p[i+2]; + b -= (mp_dbl_limb_signed)x->p[i+2]; + z->p[i+2] = LOW(b); + b = HIGH(b); + + b += (mp_dbl_limb_signed)y->p[i+3]; + b -= (mp_dbl_limb_signed)x->p[i+3]; + z->p[i+3] = LOW(b); + b = HIGH(b); + } + + /* Handle remaining elements */ + for (; i < x->sz; i++) { b += (mp_dbl_limb_signed)y->p[i]; b -= (mp_dbl_limb_signed)x->p[i]; z->p[i] = LOW(b); b = HIGH(b); } - for (;isz; i++) { + + /* Process remaining limbs from minuend with borrow */ + /* 4x unrolled loop for better performance */ + for (; i + 4 <= y->sz; i += 4) { + b += y->p[i]; + z->p[i] = LOW(b); + b = HIGH(b); + + b += y->p[i+1]; + z->p[i+1] = LOW(b); + b = HIGH(b); + + b += y->p[i+2]; + z->p[i+2] = LOW(b); + b = HIGH(b); + + b += y->p[i+3]; + z->p[i+3] = LOW(b); + b = HIGH(b); + } + + /* Handle remaining elements */ + for (; i < y->sz; i++) { b += y->p[i]; z->p[i] = LOW(b); b = HIGH(b); } + + /* Normalize result size */ z->sz = digits(z); } @@ -271,25 +541,98 @@ zero(mpz_t *x) /* z = x + y */ static void -mpz_add(mrb_state *mrb, mpz_t *zz, mpz_t *x, mpz_t *y) +mpz_add(mpz_ctx_t *ctx, mpz_t *zz, mpz_t *x, mpz_t *y) { if (zero_p(x)) { - mpz_set(mrb, zz, y); + mpz_set(ctx, zz, y); return; } if (zero_p(y)) { - mpz_set(mrb, zz, x); + mpz_set(ctx, zz, x); return; } + + /* Fast path: single-limb + multi-limb */ + if (y->sz == 1 && x->sz > 1) { + mp_limb y_limb = y->p[0]; + mpz_t z; + mpz_init_heap(ctx, &z, x->sz + 1); + + if ((x->sn > 0 && y->sn > 0) || (x->sn < 0 && y->sn < 0)) { + /* Same signs: addition */ + mp_dbl_limb carry = y_limb; + carry += x->p[0]; + z.p[0] = (mp_limb)carry; + carry >>= DIG_SIZE; + + /* Propagate carry through remaining limbs */ + for (size_t i = 1; i < x->sz; i++) { + carry += x->p[i]; + z.p[i] = (mp_limb)carry; + carry >>= DIG_SIZE; + } + z.p[x->sz] = (mp_limb)carry; + z.sn = x->sn; + } + else { + /* Different signs: subtraction */ + if (x->sz == 1 && y_limb == x->p[0]) { + /* Equal magnitude: result is zero */ + zero(&z); + } + else if (x->sz == 1 && x->p[0] > y_limb) { + /* |x| > |y|: result has sign of x */ + z.p[0] = x->p[0] - y_limb; + z.p[1] = 0; + z.sn = x->sn; + } + else { + /* |x| > |y|: subtract y from x */ + mp_dbl_limb borrow = y_limb; + if (x->p[0] >= borrow) { + z.p[0] = x->p[0] - (mp_limb)borrow; + borrow = 0; + } + else { + z.p[0] = (mp_limb)(((mp_dbl_limb)1 << DIG_SIZE) + x->p[0] - (mp_limb)borrow); + borrow = 1; + } + + /* Propagate borrow through remaining limbs */ + for (size_t i = 1; i < x->sz; i++) { + if (x->p[i] >= borrow) { + z.p[i] = x->p[i] - (mp_limb)borrow; + borrow = 0; + } + else { + z.p[i] = (mp_limb)(((mp_dbl_limb)1 << DIG_SIZE) + x->p[i] - (mp_limb)borrow); + borrow = 1; + } + } + z.sn = x->sn; + } + } + trim(&z); + mpz_move(ctx, zz, &z); + return; + } + + if (x->sz == 1 && y->sz > 1) { + /* Swap and use the same fast path */ + mpz_add(ctx, zz, y, x); + return; + } + mpz_t z; - mpz_init(mrb, &z); + size_t estimated_size = ((x->sz > y->sz) ? x->sz : y->sz) + 1; + mpz_init_heap(ctx, &z, estimated_size); if (x->sn > 0 && y->sn > 0) { - uadd(mrb, &z, x, y); + uadd(&z, x, y); z.sn = 1; } else if (x->sn < 0 && y->sn < 0) { - uadd(mrb, &z, x, y); + uadd(&z, x, y); z.sn = -1; } else { @@ -300,23 +643,23 @@ mpz_add(mrb_state *mrb, mpz_t *zz, mpz_t *x, mpz_t *y) zero(&z); } else if (mg > 0) { /* abs(y) < abs(x) */ - usub(mrb, &z, x, y); + usub(&z, x, y); z.sn = (x->sn > 0 && y->sn < 0) ? 1 : (-1); } else { /* abs(y) > abs(x) */ - usub(mrb, &z, y, x); + usub(&z, y, x); z.sn = (x->sn < 0 && y->sn > 0) ? 1 : (-1); } } trim(&z); - mpz_move(mrb, zz, &z); + mpz_move(ctx, zz, &z); } /* x += n */ /* ignores sign of x */ /* assumes n is positive and small (fits in mp_limb) */ static void -mpz_add_int(mrb_state *mrb, mpz_t *x, mrb_int n) +mpz_add_int(mpz_ctx_t *ctx, mpz_t *x, mrb_int n) { // If n is zero, no operation is needed if (n == 0) return; @@ -330,7 +673,7 @@ mpz_add_int(mrb_state *mrb, mpz_t *x, mrb_int n) } if (carry != 0) { - mpz_realloc(mrb, x, x->sz + 1); + mpz_realloc(ctx, x, x->sz + 1); x->p[x->sz-1] = (mp_limb)carry; x->sn = 1; } @@ -339,29 +682,30 @@ mpz_add_int(mrb_state *mrb, mpz_t *x, mrb_int n) /* z = x - y -- just use mpz_add - I'm lazy */ static void -mpz_sub(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y) +mpz_sub(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mpz_t *y) { mpz_t u; - mpz_init(mrb, &u); + /* Initialize u as a view of y with negated sign - no new memory allocated */ u.p = y->p; u.sz = y->sz; u.sn = -(y->sn); - mpz_add(mrb, z, x, &u); + mpz_add(ctx, z, x, &u); + /* No mpz_clear needed since u.p points to y->p (no separate allocation) */ } /* x -= n */ /* ignores sign of x */ /* assumes n is positive and small (fits in mp_limb) */ static void -mpz_sub_int(mrb_state *mrb, mpz_t *x, mrb_int n) +mpz_sub_int(mpz_ctx_t *ctx, mpz_t *x, mrb_int n) { // If n is zero, no operation is needed if (n == 0) return; // If x is zero, set x to n if (zero_p(x) || x->sz == 0) { - mpz_set_int(mrb, x, n); + mpz_set_int(ctx, x, n); return; } @@ -385,40 +729,412 @@ mpz_sub_int(mrb_state *mrb, mpz_t *x, mrb_int n) trim(x); } -/* w = u * v */ -/* Simple Multiply */ +/* Multiply-and-add: rp[0..n-1] += s1p[0..n-1] * limb; return carry (high limb) */ +static inline mp_limb +limb_addmul_1(mp_limb *rp, const mp_limb *s1p, size_t n, mp_limb limb) +{ +#if defined(__SIZEOF_INT128__) && (__SIZEOF_INT128__ == 16) + /* Use 128-bit arithmetic with 8x unrolling for maximum efficiency */ + unsigned __int128 acc = 0; + size_t i; + + /* 8x unrolled loop for large operands */ + for (i = 0; i + 8 <= n; i += 8) { + acc += (unsigned __int128)rp[i] + (unsigned __int128)s1p[i] * (unsigned __int128)limb; + rp[i] = (mp_limb)acc; + acc >>= DIG_SIZE; + + acc += (unsigned __int128)rp[i+1] + (unsigned __int128)s1p[i+1] * (unsigned __int128)limb; + rp[i+1] = (mp_limb)acc; + acc >>= DIG_SIZE; + + acc += (unsigned __int128)rp[i+2] + (unsigned __int128)s1p[i+2] * (unsigned __int128)limb; + rp[i+2] = (mp_limb)acc; + acc >>= DIG_SIZE; + + acc += (unsigned __int128)rp[i+3] + (unsigned __int128)s1p[i+3] * (unsigned __int128)limb; + rp[i+3] = (mp_limb)acc; + acc >>= DIG_SIZE; + + acc += (unsigned __int128)rp[i+4] + (unsigned __int128)s1p[i+4] * (unsigned __int128)limb; + rp[i+4] = (mp_limb)acc; + acc >>= DIG_SIZE; + + acc += (unsigned __int128)rp[i+5] + (unsigned __int128)s1p[i+5] * (unsigned __int128)limb; + rp[i+5] = (mp_limb)acc; + acc >>= DIG_SIZE; + + acc += (unsigned __int128)rp[i+6] + (unsigned __int128)s1p[i+6] * (unsigned __int128)limb; + rp[i+6] = (mp_limb)acc; + acc >>= DIG_SIZE; + + acc += (unsigned __int128)rp[i+7] + (unsigned __int128)s1p[i+7] * (unsigned __int128)limb; + rp[i+7] = (mp_limb)acc; + acc >>= DIG_SIZE; + } + + /* 4x unrolled loop for medium operands */ + for (; i + 4 <= n; i += 4) { + acc += (unsigned __int128)rp[i] + (unsigned __int128)s1p[i] * (unsigned __int128)limb; + rp[i] = (mp_limb)acc; + acc >>= DIG_SIZE; + + acc += (unsigned __int128)rp[i+1] + (unsigned __int128)s1p[i+1] * (unsigned __int128)limb; + rp[i+1] = (mp_limb)acc; + acc >>= DIG_SIZE; + + acc += (unsigned __int128)rp[i+2] + (unsigned __int128)s1p[i+2] * (unsigned __int128)limb; + rp[i+2] = (mp_limb)acc; + acc >>= DIG_SIZE; + + acc += (unsigned __int128)rp[i+3] + (unsigned __int128)s1p[i+3] * (unsigned __int128)limb; + rp[i+3] = (mp_limb)acc; + acc >>= DIG_SIZE; + } + + /* Handle remaining elements */ + for (; i < n; i++) { + acc += (unsigned __int128)rp[i] + (unsigned __int128)s1p[i] * (unsigned __int128)limb; + rp[i] = (mp_limb)acc; + acc >>= DIG_SIZE; + } + + return (mp_limb)acc; + +#else + /* Portable double-limb path with 4x unrolling */ + mp_dbl_limb acc = 0; + size_t i; + + /* 4x unrolled loop for better performance */ + for (i = 0; i + 4 <= n; i += 4) { + acc += (mp_dbl_limb)rp[i] + (mp_dbl_limb)s1p[i] * (mp_dbl_limb)limb; + rp[i] = LOW(acc); + acc = HIGH(acc); + + acc += (mp_dbl_limb)rp[i+1] + (mp_dbl_limb)s1p[i+1] * (mp_dbl_limb)limb; + rp[i+1] = LOW(acc); + acc = HIGH(acc); + + acc += (mp_dbl_limb)rp[i+2] + (mp_dbl_limb)s1p[i+2] * (mp_dbl_limb)limb; + rp[i+2] = LOW(acc); + acc = HIGH(acc); + + acc += (mp_dbl_limb)rp[i+3] + (mp_dbl_limb)s1p[i+3] * (mp_dbl_limb)limb; + rp[i+3] = LOW(acc); + acc = HIGH(acc); + } + + /* Handle remaining elements */ + for (; i < n; i++) { + acc += (mp_dbl_limb)rp[i] + (mp_dbl_limb)s1p[i] * (mp_dbl_limb)limb; + rp[i] = LOW(acc); + acc = HIGH(acc); + } + + return (mp_limb)acc; +#endif +} + +#define KARATSUBA_THRESHOLD 8 + +static inline mrb_bool +should_use_karatsuba(size_t x_len, size_t y_len) +{ + return x_len >= KARATSUBA_THRESHOLD && y_len >= KARATSUBA_THRESHOLD; +} + +/* w = u * v (optimized schoolbook using limb_addmul_1) */ static void -mpz_mul(mrb_state *mrb, mpz_t *ww, mpz_t *u, mpz_t *v) +mpz_mul_basic(mpz_ctx_t *ctx, mpz_t *ww, mpz_t *u, mpz_t *v) { if (zero_p(u) || zero_p(v)) { zero(ww); return; } - mpz_t w; - mpz_init(mrb, &w); - mpz_realloc(mrb, &w, u->sz + v->sz); + /* Ensure outer loop iterates over the shorter operand for better cache use */ + mpz_t *a, *b; + if (v->sz > u->sz) { + a = v; b = u; + } + else { + a = u; b = v; + } - for (size_t j = 0; j < u->sz; j++) { - size_t i; - mp_dbl_limb cc = (mp_limb)0; - mp_limb u0 = u->p[j]; - if (u0 == 0) continue; - for (i = 0; i < v->sz; i++) { - mp_limb v0 = v->p[i]; - if (v0 == 0) continue; - cc += (mp_dbl_limb)w.p[i + j] + (mp_dbl_limb)u0 * (mp_dbl_limb)v0; - w.p[i + j] = LOW(cc); - cc = HIGH(cc); - } - if (cc) { - w.p[i + j] = (mp_limb)cc; + /* Fast path: single-limb * multi-limb */ + if (b->sz == 1) { + mp_limb scalar = b->p[0]; + mpz_t w; + mpz_init_heap(ctx, &w, a->sz + 1); + limb_zero(w.p, a->sz + 1); + + mp_limb carry = limb_addmul_1(w.p, a->p, a->sz, scalar); + w.p[a->sz] = carry; + + w.sn = a->sn * b->sn; + trim(&w); + mpz_move(ctx, ww, &w); + return; + } + + mpz_t w; + mpz_init_heap(ctx, &w, a->sz + b->sz); + limb_zero(w.p, a->sz + b->sz); + + for (size_t j = 0; j < a->sz; j++) { + mp_limb a_limb = a->p[j]; + if (a_limb == 0) continue; + + mp_limb carry = limb_addmul_1(w.p + j, b->p, b->sz, a_limb); + + /* Properly handle carry propagation to avoid overflow */ + size_t k = j + b->sz; + while (carry && k < a->sz + b->sz) { + mp_dbl_limb sum = (mp_dbl_limb)w.p[k] + (mp_dbl_limb)carry; + w.p[k] = LOW(sum); + carry = HIGH(sum); + k++; } } - w.sn = u->sn * v->sn; + w.sn = a->sn * b->sn; trim(&w); - mpz_move(mrb, ww, &w); + mpz_move(ctx, ww, &w); +} + +/* Allocation-free Karatsuba helper functions */ + +/* Copy limbs: dest[0..n-1] = src[0..n-1] */ +static void +limb_copy(mp_limb *dest, const mp_limb *src, size_t n) +{ + if (n > 0) { + memcpy(dest, src, n * sizeof(mp_limb)); + } +} + +/* Add limbs at offset: dest[offset..offset+n-1] += src[0..n-1] */ +static void +limb_add_at(mp_limb *dest, size_t dest_len, const mp_limb *src, size_t n, size_t offset) +{ + mp_limb carry = 0; + size_t i = 0; + for (i = 0; i < n; i++) { + mp_dbl_limb sum = (mp_dbl_limb)dest[offset + i] + (mp_dbl_limb)src[i] + carry; + dest[offset + i] = LOW(sum); + carry = HIGH(sum); + } + /* Propagate final carry */ + i = offset + n; + while (carry && i < dest_len) { + mp_dbl_limb sum = (mp_dbl_limb)dest[i] + carry; + dest[i] = LOW(sum); + carry = HIGH(sum); + i++; + } +} + +/* Subtract limbs: dest[0..n-1] -= src[0..n-1], returns borrow */ +static mp_limb +limb_sub(mp_limb *dest, const mp_limb *src, size_t n) +{ + mp_dbl_limb_signed borrow = 0; + for (size_t i = 0; i < n; i++) { + borrow += (mp_dbl_limb_signed)dest[i] - (mp_dbl_limb_signed)src[i]; + dest[i] = LOW(borrow); + borrow = HIGH(borrow); + } + return (mp_limb)(-borrow); +} + +/* Basic multiplication for small operands */ +static void +mpz_mul_basic_limbs(mp_limb *result, const mp_limb *x, size_t x_len, + const mp_limb *y, size_t y_len) +{ + limb_zero(result, x_len + y_len); + + for (size_t i = 0; i < x_len; i++) { + if (x[i] == 0) continue; + mp_limb carry = limb_addmul_1(result + i, y, y_len, x[i]); + if (i + y_len < x_len + y_len) { + result[i + y_len] += carry; + } + } +} + +/* Calculate scratch space needed for Karatsuba */ +static size_t +karatsuba_scratch_size(size_t x_len, size_t y_len) +{ + if (!should_use_karatsuba(x_len, y_len)) { + return 0; + } + + if (x_len < y_len) { + size_t tmp = x_len; x_len = y_len; y_len = tmp; + } + + size_t half = y_len / 2; + size_t x1_len = x_len - half; + size_t y1_len = y_len - half; + + size_t sum_x_len = x1_len + 1; + size_t sum_y_len = y1_len + 1; + + size_t z0_len = half + half; + size_t z2_len = x1_len + y1_len; + size_t z1_len = sum_x_len + sum_y_len; + + size_t current_level_scratch = z0_len + z2_len + z1_len + sum_x_len + sum_y_len; + + size_t sub_scratch = karatsuba_scratch_size(sum_x_len, sum_y_len); + size_t sub2 = karatsuba_scratch_size(x1_len, y1_len); + size_t sub3 = karatsuba_scratch_size(half, half); + + if (sub2 > sub_scratch) sub_scratch = sub2; + if (sub3 > sub_scratch) sub_scratch = sub3; + + return current_level_scratch + sub_scratch; +} + +/* Pool-aware Karatsuba - zero intermediate allocations */ +static void +mpz_mul_karatsuba(mpz_ctx_t *ctx, mp_limb *result, + const mp_limb *x, size_t x_len, + const mp_limb *y, size_t y_len, + mp_limb *scratch) +{ + /* Base case - use basic multiplication */ + if (!should_use_karatsuba(x_len, y_len)) { + mpz_mul_basic_limbs(result, x, x_len, y, y_len); + return; + } + + /* Make x the larger operand for consistent partitioning */ + if (x_len < y_len) { + const mp_limb *tmp_ptr = x; x = y; y = tmp_ptr; + size_t tmp_len = x_len; x_len = y_len; y_len = tmp_len; + } + + /* Partition inputs */ + size_t half = y_len / 2; + + const mp_limb *x0 = x; + const mp_limb *x1 = x + half; + const mp_limb *y0 = y; + const mp_limb *y1 = y + half; + + size_t x0_len = half; + size_t x1_len = x_len - half; + size_t y0_len = half; + size_t y1_len = y_len - half; + + /* Partition scratch memory */ + size_t offset = 0; + mp_limb *z0 = scratch + offset; offset += x0_len + y0_len; + mp_limb *z2 = scratch + offset; offset += x1_len + y1_len; + mp_limb *sum_x = scratch + offset; offset += x1_len + 1; + mp_limb *sum_y = scratch + offset; offset += y1_len + 1; + mp_limb *z1 = scratch + offset; + size_t z1_alloc_len = (x1_len + 1) + (y1_len + 1); + offset += z1_alloc_len; + + /* Step 1: Compute sums x0+x1 and y0+y1 */ + mp_limb carry_x = 0; + size_t i; + for (i = 0; i < x1_len; i++) { + mp_dbl_limb sum = (mp_dbl_limb)(i < x0_len ? x0[i] : 0) + (mp_dbl_limb)x1[i] + carry_x; + sum_x[i] = LOW(sum); + carry_x = HIGH(sum); + } + sum_x[i] = carry_x; + size_t sum_x_len = x1_len + (carry_x != 0); + + mp_limb carry_y = 0; + for (i = 0; i < y1_len; i++) { + mp_dbl_limb sum = (mp_dbl_limb)(i < y0_len ? y0[i] : 0) + (mp_dbl_limb)y1[i] + carry_y; + sum_y[i] = LOW(sum); + carry_y = HIGH(sum); + } + sum_y[i] = carry_y; + size_t sum_y_len = y1_len + (carry_y != 0); + + /* Step 2: Recursive multiplications */ + mp_limb *recursive_scratch = scratch + offset; + mpz_mul_karatsuba(ctx, z0, x0, x0_len, y0, y0_len, recursive_scratch); + mpz_mul_karatsuba(ctx, z2, x1, x1_len, y1, y1_len, recursive_scratch); + mpz_mul_karatsuba(ctx, z1, sum_x, sum_x_len, sum_y, sum_y_len, recursive_scratch); + + /* Step 3: Compute z1 = z1 - z0 - z2 */ + size_t z0_len = x0_len + y0_len; + size_t z2_len = x1_len + y1_len; + size_t z1_len = sum_x_len + sum_y_len; + + mp_limb borrow = limb_sub(z1, z0, z0_len); + for (i = z0_len; i < z1_len && borrow; i++) { + mp_dbl_limb_signed diff = (mp_dbl_limb_signed)z1[i] - borrow; + z1[i] = LOW(diff); + borrow = (diff < 0) ? 1 : 0; + } + + borrow = limb_sub(z1, z2, z2_len); + for (i = z2_len; i < z1_len && borrow; i++) { + mp_dbl_limb_signed diff = (mp_dbl_limb_signed)z1[i] - borrow; + z1[i] = LOW(diff); + borrow = (diff < 0) ? 1 : 0; + } + + /* Step 4: Final assembly: result = z0 + z1*B + z2*B^2 */ + size_t result_len = x_len + y_len; + limb_zero(result, result_len); + limb_copy(result, z0, z0_len); + limb_add_at(result, result_len, z1, z1_len, half); + limb_add_at(result, result_len, z2, z2_len, 2 * half); +} + +/* w = u * v */ +static void +mpz_mul(mpz_ctx_t *ctx, mpz_t *ww, mpz_t *u, mpz_t *v) +{ + if (zero_p(u) || zero_p(v)) { + zero(ww); + return; + } + + if (!should_use_karatsuba(u->sz, v->sz)) { + mpz_mul_basic(ctx, ww, u, v); + return; + } + + size_t result_size = u->sz + v->sz; + mpz_realloc(ctx, ww, result_size); + + size_t scratch_size = karatsuba_scratch_size(u->sz, v->sz); + /* Add safety margin to account for rounding in recursive partitioning */ + scratch_size += 8; + size_t pool_state = pool_save(ctx); + mp_limb *scratch = NULL; + + if (MPZ_HAS_POOL(ctx)) { + scratch = pool_alloc(MPZ_POOL(ctx), scratch_size); + } + + if (scratch) { + mpz_mul_karatsuba(ctx, ww->p, u->p, u->sz, v->p, v->sz, scratch); + pool_restore(ctx, pool_state); + } + else { + /* Fallback to heap allocation for scratch space if pool fails */ + scratch = (mp_limb*)mrb_malloc(MPZ_MRB(ctx), scratch_size * sizeof(mp_limb)); + mpz_mul_karatsuba(ctx, ww->p, u->p, u->sz, v->p, v->sz, scratch); + mrb_free(MPZ_MRB(ctx), scratch); + } + + ww->sz = result_size; + ww->sn = u->sn * v->sn; + trim(ww); } /* number of leading zero bits in digit */ @@ -443,12 +1159,12 @@ lzb(mp_limb x) /* c1 = a>>n */ /* n must be < DIG_SIZE */ static void -urshift(mrb_state *mrb, mpz_t *c1, mpz_t *a, size_t n) +urshift(mpz_ctx_t *ctx, mpz_t *c1, mpz_t *a, size_t n) { mrb_assert(n < DIG_SIZE); if (n == 0) - mpz_set(mrb, c1, a); + mpz_set(ctx, c1, a); else if (uzero_p(a)) { zero(c1); } @@ -457,26 +1173,25 @@ urshift(mrb_state *mrb, mpz_t *c1, mpz_t *a, size_t n) mp_limb cc = 0; mp_dbl_limb rm = (((mp_dbl_limb)1<sz); + mpz_init_heap(ctx, &c, a->sz); for (size_t i=a->sz-1;; i--) { c.p[i] = ((a->p[i] >> n) | cc) & DIG_MASK; cc = (a->p[i] & rm) << (DIG_SIZE - n); if (i == 0) break; } trim(&c); - mpz_move(mrb, c1, &c); + mpz_move(ctx, c1, &c); } } /* c1 = a<sz+1); + mpz_init_heap(ctx, &c, a->sz+1); size_t i; for (i=0; isz; i++) { @@ -495,141 +1209,390 @@ ulshift(mrb_state *mrb, mpz_t *c1, mpz_t *a, size_t n) } c.p[i] = cc; trim(&c); - mpz_move(mrb, c1, &c); + mpz_move(ctx, c1, &c); } } +/* Fast division by single limb */ +static void +div_limb(mpz_ctx_t *ctx, mpz_t *q, mpz_t *r, mpz_t *x, mp_limb d) +{ + mrb_state *mrb = MPZ_MRB(ctx); + size_t pool_state = pool_save(ctx); + mpz_t temp_q, temp_r; + size_t n; + mp_dbl_limb remainder; + + if (zero_p(x)) { + zero(q); + zero(r); + goto cleanup; + } + + if (d == 0) { + mrb_raise(mrb, E_ZERODIV_ERROR, "divided by 0"); + } + + /* Power-of-2 divisor optimization */ + if ((d & (d - 1)) == 0) { + /* d is power of 2, use bit operations */ + int shift = 0; + mp_limb temp = d; + while (temp > 1) { + temp >>= 1; + shift++; + } + + /* Quotient = x >> shift */ + if (shift == 0) { + mpz_init(ctx, &temp_q); + mpz_init(ctx, &temp_r); + mpz_set(ctx, &temp_q, x); + } + else { + /* Manual right shift implementation */ + size_t limb_shift = shift / DIG_SIZE; + size_t bit_shift = shift % DIG_SIZE; + + if (limb_shift >= x->sz) { + mpz_init(ctx, &temp_q); + mpz_init(ctx, &temp_r); + zero(&temp_q); + } + else { + size_t new_size = x->sz - limb_shift; + mpz_init_heap(ctx, &temp_q, new_size); + mpz_init(ctx, &temp_r); + + if (bit_shift == 0) { + /* Simple limb copy */ + for (size_t i = 0; i < new_size; i++) { + temp_q.p[i] = x->p[i + limb_shift]; + } + } + else { + /* Bit shift within limbs */ + mp_limb carry = 0; + for (size_t i = new_size; i > 0; i--) { + mp_limb current = x->p[i - 1 + limb_shift]; + temp_q.p[i - 1] = (current >> bit_shift) | carry; + carry = (current << (DIG_SIZE - bit_shift)) & DIG_MASK; + } + } + temp_q.sz = new_size; + trim(&temp_q); + temp_q.sn = (temp_q.sz == 0) ? 0 : 1; + } + } + + /* Remainder = x & (d - 1) */ + /* temp_r is already initialized in all code paths above */ + mpz_realloc(ctx, &temp_r, 1); + temp_r.p[0] = x->p[0] & (d - 1); + temp_r.sz = (temp_r.p[0] == 0) ? 0 : 1; + temp_r.sn = (temp_r.sz == 0) ? 0 : 1; + mpz_move(ctx, q, &temp_q); + mpz_move(ctx, r, &temp_r); + goto cleanup; + } + + /* General single-limb division */ + if (x->sz == 1) { + /* Both dividend and divisor are single limb */ + mpz_init_heap(ctx, &temp_q, 1); + mpz_init_heap(ctx, &temp_r, 1); + + temp_q.p[0] = x->p[0] / d; + temp_r.p[0] = x->p[0] % d; + + temp_q.sz = (temp_q.p[0] == 0) ? 0 : 1; + temp_q.sn = (temp_q.sz == 0) ? 0 : 1; + + temp_r.sz = (temp_r.p[0] == 0) ? 0 : 1; + temp_r.sn = (temp_r.sz == 0) ? 0 : 1; + mpz_move(ctx, q, &temp_q); + mpz_move(ctx, r, &temp_r); + goto cleanup; + } + + /* Multi-limb dividend, single-limb divisor */ + n = x->sz; + mpz_init_heap(ctx, &temp_q, n); + mpz_init_heap(ctx, &temp_r, 1); + + remainder = 0; + + /* Process from most significant limb to least significant */ + for (size_t i = n; i > 0; i--) { + remainder = (remainder << DIG_SIZE) + x->p[i-1]; + temp_q.p[i-1] = (mp_limb)(remainder / d); + remainder = remainder % d; + } + + /* Set remainder */ + temp_r.p[0] = (mp_limb)remainder; + temp_r.sz = (remainder == 0) ? 0 : 1; + temp_r.sn = (temp_r.sz == 0) ? 0 : 1; + + /* Trim leading zeros from quotient */ + trim(&temp_q); + temp_q.sn = (temp_q.sz == 0) ? 0 : 1; + + /* Copy results to avoid pool/heap mixing */ + mpz_move(ctx, q, &temp_q); + mpz_move(ctx, r, &temp_r); + +cleanup: + pool_restore(ctx, pool_state); +} + + /* internal routine to compute x/y and x%y ignoring signs */ /* qq = xx/yy; rr = xx%yy */ static void -udiv(mrb_state *mrb, mpz_t *qq, mpz_t *rr, mpz_t *xx, mpz_t *yy) +udiv(mpz_ctx_t *ctx, mpz_t *qq, mpz_t *rr, mpz_t *xx, mpz_t *yy) { - /* simple cases */ + /* Handle simple cases */ int cmp = ucmp(xx, yy); if (cmp == 0) { - mpz_set_int(mrb, qq, 1); + mpz_set_int(ctx, qq, 1); zero(rr); return; } else if (cmp < 0) { zero(qq); - mpz_set(mrb, rr, xx); + mpz_set(ctx, rr, xx); return; } - mpz_t q, x, y; + /* Fast path for single-limb divisor */ + if (yy->sz == 1) { + div_limb(ctx, qq, rr, xx, yy->p[0]); + return; + } mrb_assert(yy->sn != 0); /* divided by zero */ mrb_assert(yy->sz > 0); /* divided by zero */ - mpz_init(mrb, &q); - mpz_init(mrb, &x); - mpz_init(mrb, &y); - mpz_realloc(mrb, &x, xx->sz+1); + mrb_assert(!uzero_p(yy)); /* divided by zero */ + + /* Use new context architecture with automatic pool/heap management */ + size_t pool_state = pool_save(ctx); + mpz_t q, x, y; + mpz_init_temp(ctx, &q, xx->sz - yy->sz + 1); /* Quotient size estimate */ + mpz_init_temp(ctx, &x, xx->sz + 1); /* Dividend with potential carry */ + mpz_init_temp(ctx, &y, yy->sz); /* Divisor copy */ + mpz_realloc(ctx, &x, xx->sz+1); size_t yd = digits(yy); size_t ns = lzb(yy->p[yd-1]); - ulshift(mrb, &x, xx, ns); - ulshift(mrb, &y, yy, ns); + ulshift(ctx, &x, xx, ns); + ulshift(ctx, &y, yy, ns); + trim(&y); /* Trim after shift to remove any zero limbs */ size_t xd = digits(&x); - mpz_realloc(mrb, &q, xd); - mp_dbl_limb z = y.p[yd-1]; - if (xd>=yd) { - for (size_t j=xd-yd;; j--) { - mp_dbl_limb_signed b=0; - mp_dbl_limb qhat; + yd = digits(&y); - if (j+yd == xd) - qhat = x.p[j+yd-1] / z; - else - qhat = (((mp_dbl_limb)x.p[j+yd] << DIG_SIZE) + x.p[j+yd-1]) / z; - if (qhat) { + /* Handle edge case: divisor became zero after normalization */ + if (yd == 0 || y.p[yd-1] == 0) { + /* This should not happen with valid inputs, but handle gracefully */ + zero(qq); + zero(rr); + mpz_clear(ctx, &q); + mpz_clear(ctx, &x); + mpz_clear(ctx, &y); + pool_restore(ctx, pool_state); + return; + } + + mpz_realloc(ctx, &q, xd-yd+1); // Quotient has xd-yd+1 digits maximum + + /* Core Knuth Algorithm D division loop */ + mp_dbl_limb z = y.p[yd-1]; + mrb_assert(z != 0); /* Divisor high limb must be non-zero after normalization */ + + if (xd >= yd) { + for (size_t j = xd - yd;; j--) { + mp_dbl_limb qhat; + mp_dbl_limb rhat; + + if (j + yd == xd) { + /* Only one high limb available */ + mp_dbl_limb dividend_val = (((mp_dbl_limb)0 << DIG_SIZE) + x.p[j+yd-1]); + qhat = dividend_val / z; + rhat = dividend_val % z; + } + else { + /* Two limbs available - use enhanced estimation */ + mp_dbl_limb dividend_val = ((mp_dbl_limb)x.p[j+yd] << DIG_SIZE) + x.p[j+yd-1]; + qhat = dividend_val / z; + rhat = dividend_val % z; + + /* Three-limb pre-adjustment when available */ + if (yd >= 2 && j+yd-2 < x.sz && y.p[yd-2] != 0) { + mp_dbl_limb y_second = y.p[yd-2]; + mp_dbl_limb x_third = x.p[j+yd-2]; + + if (qhat > 0) { + mp_dbl_limb left = qhat * y_second; + mp_dbl_limb right = (rhat << DIG_SIZE) + x_third; + + if (qhat >= ((mp_dbl_limb)1 << DIG_SIZE) || left > right) { + qhat--; + rhat += z; + } + } + } + } + + /* Enhanced qhat refinement step */ + if (yd > 2) { // Now considering at least 3 limbs of divisor + mp_dbl_limb y_second = y.p[yd-2]; + mp_dbl_limb y_third = y.p[yd-3]; // New: third limb of divisor + mp_dbl_limb x_third = (j+yd-2 < x.sz) ? x.p[j+yd-2] : 0; + mp_dbl_limb x_fourth = (j+yd-3 < x.sz) ? x.p[j+yd-3] : 0; // New: fourth limb of dividend + + // Initial check with 2 limbs + mp_dbl_limb left_side = qhat * y_second; + mp_dbl_limb right_side = (rhat << DIG_SIZE) + x_third; + + while (qhat >= ((mp_dbl_limb)1 << DIG_SIZE) || (left_side > right_side)) { + qhat--; + rhat += z; + if (rhat >= ((mp_dbl_limb)1 << DIG_SIZE)) break; + left_side -= y_second; + right_side = (rhat << DIG_SIZE) + x_third; + } + + // Additional check with 3 limbs (new refinement) + left_side = qhat * y_third; + right_side = (rhat << DIG_SIZE) + x_fourth; + + while (qhat >= ((mp_dbl_limb)1 << DIG_SIZE) || (left_side > right_side)) { + qhat--; + rhat += z; + if (rhat >= ((mp_dbl_limb)1 << DIG_SIZE)) break; + left_side -= y_third; + right_side = (rhat << DIG_SIZE) + x_fourth; + } + } + else if (yd == 2) { // Original 2-limb check + mp_dbl_limb y_second = y.p[yd-2]; + mp_dbl_limb x_third = (j+yd-2 < x.sz) ? x.p[j+yd-2] : 0; + mp_dbl_limb left_side = qhat * y_second; + mp_dbl_limb right_side = (rhat << DIG_SIZE) + x_third; + + while (qhat >= ((mp_dbl_limb)1 << DIG_SIZE) || (left_side > right_side)) { + qhat--; + rhat += z; + if (rhat >= ((mp_dbl_limb)1 << DIG_SIZE)) break; + left_side -= y_second; + right_side = (rhat << DIG_SIZE) + x_third; + } + } + + if (qhat > 0) { + /* Subtract qhat * divisor from dividend */ + mp_dbl_limb_signed borrow = 0; size_t i; - for (i=0; i 0) { + x.p[i+j] += (mp_limb)carry; + } } - b += c; } + q.p[j] = (mp_limb)qhat; if (j == 0) break; } } - x.sz = yy->sz; - urshift(mrb, rr, &x, ns); + x.sz = yd; + urshift(ctx, rr, &x, ns); trim(&q); - mpz_move(mrb, qq, &q); - mpz_clear(mrb, &x); - mpz_clear(mrb, &y); + mpz_move(ctx, qq, &q); + mpz_clear(ctx, &q); + mpz_clear(ctx, &x); + mpz_clear(ctx, &y); + pool_restore(ctx, pool_state); } static void -mpz_mdiv(mrb_state *mrb, mpz_t *q, mpz_t *x, mpz_t *y) +mpz_mdiv(mpz_ctx_t *ctx, mpz_t *q, mpz_t *x, mpz_t *y) { mpz_t r; short sn1 = x->sn, sn2 = y->sn, qsign; if (zero_p(x)) { - mpz_init_set_int(mrb, q, 0); + mpz_init_set_int(ctx, q, 0); return; } - mpz_init(mrb, &r); - udiv(mrb, q, &r, x, y); - qsign = q->sn = sn1*sn2; + mpz_init(ctx, &r); + udiv(ctx, q, &r, x, y); + qsign = q->sn = sn1 * sn2; if (uzero_p(q)) q->sn = 0; /* now if r != 0 and q < 0 we need to round q towards -inf */ if (!uzero_p(&r) && qsign < 0) { /* add 1 to magnitude */ - mpz_add_int(mrb, q, 1); + mpz_add_int(ctx, q, 1); /* force negative sign in case the value of q was zero before rounding */ q->sn = -1; } - mpz_clear(mrb, &r); + mpz_clear(ctx, &r); } static void -mpz_mmod(mrb_state *mrb, mpz_t *r, mpz_t *x, mpz_t *y) +mpz_mmod(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mpz_t *y) { mpz_t q; short sn1 = x->sn, sn2 = y->sn, sn3; - mpz_init(mrb, &q); + mpz_init(ctx, &q); if (sn1 == 0) { zero(r); return; } - udiv(mrb, &q, r, x, y); - mpz_clear(mrb, &q); + udiv(ctx, &q, r, x, y); + mpz_clear(ctx, &q); if (uzero_p(r)) { r->sn = 0; return; } - sn3 = sn1*sn2; + sn3 = sn1 * sn2; if (sn3 > 0) r->sn = sn1; else if (sn1 < 0 && sn2 > 0) { r->sn = 1; - mpz_sub(mrb, r, y, r); + mpz_sub(ctx, r, y, r); } else { r->sn = 1; - mpz_add(mrb, r, y, r); + mpz_add(ctx, r, y, r); } } static void -mpz_mdivmod(mrb_state *mrb, mpz_t *q, mpz_t *r, mpz_t *x, mpz_t *y) +mpz_mdivmod(mpz_ctx_t *ctx, mpz_t *q, mpz_t *r, mpz_t *x, mpz_t *y) { short sn1 = x->sn, sn2 = y->sn, qsign; @@ -638,8 +1601,8 @@ mpz_mdivmod(mrb_state *mrb, mpz_t *q, mpz_t *r, mpz_t *x, mpz_t *y) zero(r); return; } - udiv(mrb, q, r, x, y); - qsign = q->sn = sn1*sn2; + udiv(ctx, q, r, x, y); + qsign = q->sn = sn1 * sn2; if (uzero_p(r)) { /* q != 0, since q=r=0 would mean x=0, which was tested above */ r->sn = 0; @@ -649,43 +1612,101 @@ mpz_mdivmod(mrb_state *mrb, mpz_t *q, mpz_t *r, mpz_t *x, mpz_t *y) r->sn = sn1; else if (sn1 < 0 && sn2 > 0) { r->sn = 1; - mpz_sub(mrb, r, y, r); + mpz_sub(ctx, r, y, r); } else { r->sn = 1; - mpz_add(mrb, r, y, r); + mpz_add(ctx, r, y, r); } if (uzero_p(q)) q->sn = 0; /* now if r != 0 and q < 0 we need to round q towards -inf */ if (!uzero_p(r) && qsign < 0) { /* add 1 to magnitude */ - mpz_add_int(mrb, q, 1); + mpz_add_int(ctx, q, 1); /* force negative sign in case the value of q was zero before rounding */ q->sn = -1; } } +/* Fast modular reduction for single-limb modulus */ static void -mpz_mod(mrb_state *mrb, mpz_t *r, mpz_t *x, mpz_t *y) +mpz_mod_limb(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mp_limb m) +{ + if (zero_p(x)) { + zero(r); + return; + } + + if (x->sz == 1) { + /* Single limb case - simple modulo */ + mp_limb result = x->p[0] % m; + mpz_set_int(ctx, r, result); + r->sn = x->sn; + return; + } + + /* Multi-limb case - use repeated division */ + mp_dbl_limb remainder = 0; + for (size_t i = x->sz; i > 0; i--) { + remainder = (remainder << DIG_SIZE) | x->p[i-1]; + remainder %= m; + } + + mpz_set_int(ctx, r, (mp_limb)remainder); + r->sn = x->sn; + if (remainder == 0) + r->sn = 0; +} + +/* Forward declarations for Barrett reduction functions */ +static void mpz_barrett_mu(mpz_ctx_t *ctx, mpz_t *mu, mpz_t *m); +static void mpz_barrett_reduce(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mpz_t *m, mpz_t *mu); + +static void +mpz_mod(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mpz_t *y) { - mpz_t q; short sn = x->sn; if (zero_p(x)) { zero(r); return; } - mpz_init(mrb, &q); - udiv(mrb, &q, r, x, y); + + /* Fast path for single-limb modulus */ + if (y->sz == 1) { + mpz_mod_limb(ctx, r, x, y->p[0]); + if (y->sn < 0) r->sn = -r->sn; + return; + } + + /* Barrett reduction for moderate-sized moduli */ + if (y->sz >= 2 && y->sz <= 16 && x->sz >= y->sz + 2) { + mpz_t mu; + mpz_init_temp(ctx, &mu, y->sz + 1); + mpz_barrett_mu(ctx, &mu, y); + mpz_realloc(ctx, r, y->sz); + mpz_barrett_reduce(ctx, r, x, y, &mu); + r->sn = sn; + if (uzero_p(r)) + r->sn = 0; + mpz_clear(ctx, &mu); + return; + } + + /* General division fallback */ + mpz_t q; + mpz_init_temp(ctx, &q, x->sz); + mpz_realloc(ctx, r, y->sz); + udiv(ctx, &q, r, x, y); r->sn = sn; if (uzero_p(r)) r->sn = 0; - mpz_clear(mrb, &q); + mpz_clear(ctx, &q); } static mrb_int -mpz_cmp(mrb_state *mrb, mpz_t *x, mpz_t *y) +mpz_cmp(mpz_ctx_t *ctx, mpz_t *x, mpz_t *y) { if (x->sn < 0 && y->sn > 0) return (-1); @@ -716,7 +1737,7 @@ mpz_sizeinbase(mpz_t *x, mrb_int base) /* assumes x and n are positive or zero */ /* assumes n is small (fits in mp_limb) */ static void -mpz_mul_int(mrb_state *mrb, mpz_t *x, mrb_int n) +mpz_mul_int(mpz_ctx_t *ctx, mpz_t *x, mrb_int n) { if (n == 0 || zero_p(x)) { zero(x); @@ -727,7 +1748,7 @@ mpz_mul_int(mrb_state *mrb, mpz_t *x, mrb_int n) size_t new_sz = x_sz + 1; // Maximum possible size after multiplication // Reallocate x if necessary - mpz_realloc(mrb, x, new_sz); + mpz_realloc(ctx, x, new_sz); mp_dbl_limb cc = 0; mp_limb n_limb = (mp_limb)n; @@ -740,9 +1761,11 @@ mpz_mul_int(mrb_state *mrb, mpz_t *x, mrb_int n) } if (cc) { - // If there is a remaining carry, store it + // If there is a remaining carry, store it and update size x->p[x_sz] = (mp_limb)cc; - } else { + x->sz = x_sz + 1; + } + else { x->sz = x_sz; } @@ -751,13 +1774,13 @@ mpz_mul_int(mrb_state *mrb, mpz_t *x, mrb_int n) } static int -mpz_init_set_str(mrb_state *mrb, mpz_t *x, const char *s, mrb_int len, mrb_int base) +mpz_init_set_str(mpz_ctx_t *ctx, mpz_t *x, const char *s, mrb_int len, mrb_int base) { int retval = 0; short sn; uint8_t k; - mpz_init(mrb, x); + mpz_init(ctx, x); if (*s == '-') { sn = -1; s++; } @@ -782,8 +1805,8 @@ mpz_init_set_str(mrb_state *mrb, mpz_t *x, const char *s, mrb_int len, mrb_int b retval = (-1); break; } - mpz_mul_int(mrb, x, base); - mpz_add_int(mrb, x, k); + mpz_mul_int(ctx, x, base); + mpz_add_int(ctx, x, k); } x->sn = x->sz == 0 ? 0 : sn; return retval; @@ -866,8 +1889,10 @@ static const mp_limb base_limit[34*2] = { }; static char* -mpz_get_str(mrb_state *mrb, char *s, mrb_int sz, mrb_int base, mpz_t *x) +mpz_get_str(mpz_ctx_t *ctx, char *s, mrb_int sz, mrb_int base, mpz_t *x) { + mrb_state *mrb = MPZ_MRB(ctx); + mrb_assert(2 <= base && base <= 36); if (zero_p(x)) { *s='0'; @@ -881,11 +1906,12 @@ mpz_get_str(mrb_state *mrb, char *s, mrb_int sz, mrb_int base, mpz_t *x) if ((base & (base - 1)) == 0) { // base is a power of 2 int shift = 0; - while ((1 << shift) < base) shift++; + while (((uint64_t)1 << shift) < (uint64_t)base) shift++; mp_limb mask = (mp_limb)base - 1; mp_dbl_limb value = 0; int bits = 0; + /* Process all limbs */ for (int i = 0; i < xlen; i++) { value |= (mp_dbl_limb)x->p[i] << bits; bits += DIG_SIZE; @@ -898,11 +1924,27 @@ mpz_get_str(mrb_state *mrb, char *s, mrb_int sz, mrb_int base, mpz_t *x) else *s++ = 'a' + digit - 10; } } + + /* Handle any remaining bits */ + while (bits > 0) { + mp_limb digit = value & mask; + value >>= shift; + bits -= shift; + + if (digit < 10) *s++ = '0' + digit; + else *s++ = 'a' + digit - 10; + } } else { - mp_limb *t = (mp_limb*)mrb_malloc(mrb, xlen*sizeof(mp_limb)); + /* Check for overflow in size calculation */ + if ((size_t)xlen > SIZE_MAX / sizeof(mp_limb)) { + mrb_raise(mrb, E_RUNTIME_ERROR, "bigint size too large for string conversion"); + } + + mp_limb *t = (mp_limb*)mrb_malloc(mrb, xlen * sizeof(mp_limb)); + mp_limb *tend = t + xlen; - memcpy(t, x->p, xlen*sizeof(mp_limb)); + memcpy(t, x->p, xlen * sizeof(mp_limb)); mp_limb b2 = base_limit[base-3]; for (;;) { @@ -916,7 +1958,7 @@ mpz_get_str(mrb_state *mrb, char *s, mrb_int sz, mrb_int base, mpz_t *x) } // convert to character - for (mp_limb b=b2; b>=base; b/=base) { + for (mp_limb b=b2; b>=base; b/=(mp_limb)base) { char a0 = (char)(a % base); if (a0 < 10) a0 += '0'; else a0 += 'a' - 10; @@ -957,6 +1999,24 @@ mpz_get_int(mpz_t *y, mrb_int *v) return TRUE; } +#ifdef MRB_NO_MPZ64BIT + /* When using 16-bit limbs, we need to handle larger accumulation */ + mrb_uint i = 0; + mp_limb *d = y->p + y->sz; + + while (d-- > y->p) { + /* Check for overflow before shifting */ + if (i > (mrb_uint)(MRB_INT_MAX >> DIG_SIZE)) { + return FALSE; + } + i = (i << DIG_SIZE) | *d; + } + + if (i > (mrb_uint)MRB_INT_MAX) { + return FALSE; + } +#else + /* Original logic for 32-bit limbs */ mp_dbl_limb i = 0; mp_limb *d = y->p + y->sz; @@ -971,6 +2031,8 @@ mpz_get_int(mpz_t *y, mrb_int *v) /* overflow */ return FALSE; } +#endif + if (y->sn < 0) { *v = -(mrb_int)i; } @@ -981,52 +2043,55 @@ mpz_get_int(mpz_t *y, mrb_int *v) } static void -mpz_mul_2exp(mrb_state *mrb, mpz_t *z, mpz_t *x, mrb_int e) +mpz_mul_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e) { if (e==0) - mpz_set(mrb, z, x); + mpz_set(ctx, z, x); else { short sn = x->sn; size_t digs = e / DIG_SIZE; size_t bs = e % DIG_SIZE; mpz_t y; - mpz_init(mrb, &y); - mpz_realloc(mrb, &y, x->sz+digs); + mpz_init_heap(ctx, &y, x->sz+digs); for (size_t i=0;isz;i++) y.p[i+digs] = x->p[i]; if (bs) { - ulshift(mrb, z, &y, bs); - mpz_clear(mrb, &y); + ulshift(ctx, z, &y, bs); + mpz_clear(ctx, &y); } else { - mpz_move(mrb, z, &y); + mpz_move(ctx, z, &y); } z->sn = sn; } } static void -mpz_div_2exp(mrb_state *mrb, mpz_t *z, mpz_t *x, mrb_int e) +mpz_div_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e) { short sn = x->sn; - if (e==0) - mpz_set(mrb, z, x); + if (e == 0) { + mpz_init_heap(ctx, z, x->sz); + mpz_set(ctx, z, x); + } else { size_t digs = e / DIG_SIZE; size_t bs = e % DIG_SIZE; mpz_t y; - mpz_init(mrb, &y); - mpz_realloc(mrb, &y, x->sz-digs); - for (size_t i=0; i < x->sz-digs; i++) - y.p[i] = x->p[i+digs]; + size_t new_size = (digs >= x->sz) ? 1 : x->sz - digs; + mpz_init_temp(ctx, &y, new_size); + mpz_realloc(ctx, &y, new_size); + for (size_t i = 0; i < x->sz - digs; i++) + y.p[i] = x->p[i + digs]; if (bs) { - urshift(mrb, z, &y, bs); - mpz_clear(mrb, &y); + mpz_init_heap(ctx, z, new_size); + urshift(ctx, z, &y, bs); + mpz_clear(ctx, &y); } else { - mpz_move(mrb, z, &y); + mpz_move(ctx, z, &y); } if (uzero_p(z)) z->sn = 0; @@ -1037,25 +2102,70 @@ mpz_div_2exp(mrb_state *mrb, mpz_t *z, mpz_t *x, mrb_int e) } static void -mpz_neg(mrb_state *mrb, mpz_t *x, mpz_t *y) +mpz_neg(mpz_ctx_t *ctx, mpz_t *x, mpz_t *y) { - mpz_set(mrb, x, y); + mpz_init_heap(ctx, x, y->sz); + mpz_set(ctx, x, y); x->sn = -(y->sn); } +/* Fast modular reduction by power of 2: z = x mod 2^e */ +static void +mpz_mod_2exp(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mrb_int e) +{ + if (e <= 0) { + mpz_init(ctx, z); + zero(z); + return; + } + + size_t eint = e / DIG_SIZE; + size_t bs = e % DIG_SIZE; + size_t sz = x->sz; + + if (eint >= sz) { + /* x < 2^e, so x mod 2^e = x */ + mpz_init_heap(ctx, z, x->sz); + mpz_set(ctx, z, x); + return; + } + + /* Need to mask off high bits */ + size_t result_sz = eint + (bs > 0 ? 1 : 0); + mpz_init_heap(ctx, z, result_sz); + mpz_realloc(ctx, z, result_sz); + z->sn = x->sn; + z->sz = result_sz; + + /* Copy full limbs */ + for (size_t i = 0; i < eint; i++) { + z->p[i] = x->p[i]; + } + + /* Mask partial limb if needed */ + if (bs > 0) { + mp_limb mask = (1UL << bs) - 1; + z->p[eint] = x->p[eint] & mask; + } + + trim(z); +} + #define make_2comp(v,c) do { v=~(v)+(c); c=((v)==0 && (c));} while (0) static void -mpz_and(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y) +mpz_and(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mpz_t *y) { if (zero_p(x) || zero_p(y)) { + mpz_init(ctx, z); zero(z); return; } mrb_assert(x->sz > 0 || y->sz > 0); size_t max_sz = (x->sz > y->sz) ? x->sz : y->sz; - mpz_realloc(mrb, z, max_sz); + mpz_init_heap(ctx, z, max_sz); + mpz_realloc(ctx, z, max_sz); z->sn = (x->sn == y->sn) ? x->sn : 1; char c1 = 1, c2 = 1, c3 = 1; @@ -1072,20 +2182,23 @@ mpz_and(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y) } static void -mpz_or(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y) /* not the most efficient way to do this */ +mpz_or(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mpz_t *y) /* not the most efficient way to do this */ { if (zero_p(x)) { - mpz_set(mrb, z, y); + mpz_init_heap(ctx, z, y->sz); + mpz_set(ctx, z, y); return; } if (zero_p(y)) { - mpz_set(mrb, z, x); + mpz_init_heap(ctx, z, x->sz); + mpz_set(ctx, z, x); return; } mrb_assert(x->sz > 0 || y->sz > 0); size_t max_sz = (x->sz > y->sz) ? x->sz : y->sz; - mpz_realloc(mrb, z, max_sz); + mpz_init_heap(ctx, z, max_sz); + mpz_realloc(ctx, z, max_sz); z->sn = (x->sn == y->sn) ? x->sn : -1; char c1 = 1, c2 = 1, c3 = 1; @@ -1102,20 +2215,23 @@ mpz_or(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y) /* not the most efficient } static void -mpz_xor(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y) /* not the most efficient way to do this */ +mpz_xor(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x, mpz_t *y) /* not the most efficient way to do this */ { if (zero_p(x)) { - mpz_set(mrb, z, y); + mpz_init_heap(ctx, z, y->sz); + mpz_set(ctx, z, y); return; } if (zero_p(y)) { - mpz_set(mrb, z, x); + mpz_init_heap(ctx, z, x->sz); + mpz_set(ctx, z, x); return; } mrb_assert(x->sz > 0 || y->sz > 0); size_t max_sz = (x->sz > y->sz) ? x->sz : y->sz; - mpz_realloc(mrb, z, max_sz); + mpz_init_heap(ctx, z, max_sz); + mpz_realloc(ctx, z, max_sz); z->sn = (x->sn == y->sn) ? 1 : -1; char c1 = 1, c2 = 1, c3 = 1; @@ -1132,33 +2248,54 @@ mpz_xor(mrb_state *mrb, mpz_t *z, mpz_t *x, mpz_t *y) /* not the most efficient } static void -mpz_pow(mrb_state *mrb, mpz_t *zz, mpz_t *x, mrb_int e) +mpz_pow(mpz_ctx_t *ctx, mpz_t *zz, mpz_t *x, mrb_int e) { - mpz_t t; - mrb_uint mask = 1ULL<<(sizeof(mrb_int)*8-1); - - if (e==0) { - mpz_set_int(mrb, zz, 1L); + if (e == 0) { + mpz_init_set_int(ctx, zz, 1L); return; } - mpz_init_set(mrb, &t, x); - for (;!(mask &e); mask>>=1) - ; - mask>>=1; - for (;mask!=0; mask>>=1) { - mpz_mul(mrb, &t, &t, &t); - if (e & mask) - mpz_mul(mrb, &t, &t, x); + mrb_uint mask = 1ULL << (sizeof(mrb_int) * 8 - 1); + while (mask != 0 && !(mask & e)) { + mask >>= 1; } - mpz_move(mrb, zz, &t); + + /* Set initial value to x for exponentiation */ + mpz_init_set(ctx, zz, x); + + if (mask == 0) { /* e is 0 or 1 */ + if (e == 0) mpz_set_int(ctx, zz, 1L); + return; + } + + mask >>= 1; + + /* Pre-allocate a single temporary variable */ + mpz_t temp; + mpz_init(ctx, &temp); + + for (; mask != 0; mask >>= 1) { + /* squaring: temp = zz * zz */ + mpz_mul(ctx, &temp, zz, zz); + + if (e & mask) { + /* multiplication: zz = temp * x */ + mpz_mul(ctx, zz, &temp, x); + } + else { + /* move result: zz = temp */ + mpz_move(ctx, zz, &temp); + } + } + mpz_clear(ctx, &temp); } static void -mpz_powm(mrb_state *mrb, mpz_t *zz, mpz_t *x, mpz_t *ex, mpz_t *n) +mpz_powm(mpz_ctx_t *ctx, mpz_t *zz, mpz_t *x, mpz_t *ex, mpz_t *n) { + /* Handle special cases */ if (zero_p(ex) || uzero_p(ex)) { - mpz_set_int(mrb, zz, 1); + mpz_set_int(ctx, zz, 1); return; } @@ -1166,32 +2303,59 @@ mpz_powm(mrb_state *mrb, mpz_t *zz, mpz_t *x, mpz_t *ex, mpz_t *n) return; } + size_t pool_state = pool_save(ctx); mpz_t t, b; - mpz_init_set_int(mrb, &t, 1); - mpz_init_set(mrb, &b, x); + mpz_init_set_int(ctx, &t, 1); + mpz_init_set(ctx, &b, x); + + /* Optimize with Barrett reduction for moderate-sized moduli */ + mpz_t mu, temp; + int use_barrett = (n->sz >= 2 && n->sz <= 8); + mpz_init_temp(ctx, &temp, n->sz * 2); /* For intermediate calculations */ + if (use_barrett) { + mpz_init_temp(ctx, &mu, n->sz + 1); /* Barrett parameter */ + mpz_barrett_mu(ctx, &mu, n); + } size_t len = digits(ex); - for (size_t i=0; ip[i]; - for (size_t j=0; j>= 1; - mpz_mul(mrb, &b, &b, &b); - mpz_mod(mrb, &b, &b, n); + mpz_mul(ctx, &temp, &b, &b); + if (use_barrett) { + mpz_barrett_reduce(ctx, &b, &temp, n, &mu); + } + else { + mpz_mod(ctx, &b, &temp, n); + } } } - mpz_move(mrb, zz, &t); - mpz_clear(mrb, &b); + + mpz_move(ctx, zz, &t); + mpz_clear(ctx, &t); + mpz_clear(ctx, &b); + mpz_clear(ctx, &temp); + if (use_barrett) { + mpz_clear(ctx, &mu); + } + pool_restore(ctx, pool_state); } static void -mpz_powm_i(mrb_state *mrb, mpz_t *zz, mpz_t *x, mrb_int ex, mpz_t *n) +mpz_powm_i(mpz_ctx_t *ctx, mpz_t *zz, mpz_t *x, mrb_int ex, mpz_t *n) { if (ex == 0) { - mpz_set_int(mrb, zz, 1); + mpz_set_int(ctx, zz, 1); return; } @@ -1199,52 +2363,274 @@ mpz_powm_i(mrb_state *mrb, mpz_t *zz, mpz_t *x, mrb_int ex, mpz_t *n) return; } + size_t pool_state = pool_save(ctx); mpz_t t, b; - mpz_init_set_int(mrb, &t, 1); - mpz_init_set(mrb, &b, x); + mpz_init_set_int(ctx, &t, 1); + mpz_init_set(ctx, &b, x); + + /* Optimize with Barrett reduction for moderate-sized moduli */ + mpz_t mu, temp; + int use_barrett = (n->sz >= 2 && n->sz <= 8); + mpz_init_temp(ctx, &temp, n->sz * 2); /* For intermediate calculations */ + if (use_barrett) { + mpz_init_temp(ctx, &mu, n->sz + 1); /* Barrett parameter */ + mpz_barrett_mu(ctx, &mu, n); + } while (ex > 0) { if ((ex & 1) == 1) { - mpz_mul(mrb, &t, &t, &b); - mpz_mod(mrb, &t, &t, n); + mpz_mul(ctx, &temp, &t, &b); + if (use_barrett) { + mpz_barrett_reduce(ctx, &t, &temp, n, &mu); + } + else { + mpz_mod(ctx, &t, &temp, n); + } } ex >>= 1; - mpz_mul(mrb, &b, &b, &b); - mpz_mod(mrb, &b, &b, n); + if (ex > 0) { /* Skip final squaring when ex becomes 0 */ + mpz_mul(ctx, &temp, &b, &b); + if (use_barrett) { + mpz_barrett_reduce(ctx, &b, &temp, n, &mu); + } + else { + mpz_mod(ctx, &b, &temp, n); + } + } } - mpz_move(mrb, zz, &t); - mpz_clear(mrb, &b); + + mpz_move(ctx, zz, &t); + mpz_clear(ctx, &t); + mpz_clear(ctx, &b); + mpz_clear(ctx, &temp); + if (use_barrett) { + mpz_clear(ctx, &mu); + } + pool_restore(ctx, pool_state); } -#ifdef MRB_USE_RATIONAL -static void -mpz_abs(mrb_state *mrb, mpz_t *x, mpz_t *y) -{ - mpz_init_set(mrb, x, y); - if (zero_p(y)) - x->sn = 0; - else - x->sn = 1; +/* Helper functions for pool-based GCD operations */ +static int +mpz_abs_copy(mpz_ctx_t *ctx, mpz_t *result, mpz_t *operand) { + if (!operand || operand->sz == 0) { + result->sz = 0; + result->sn = 0; + return 1; + } + + /* Copy limbs */ + for (size_t i = 0; i < operand->sz && i < result->sz; i++) { + result->p[i] = operand->p[i]; + } + result->sz = (operand->sz < result->sz) ? operand->sz : result->sz; + result->sn = (operand->sn < 0) ? -operand->sn : operand->sn; /* Always positive */ + + return 1; } static void -mpz_gcd(mrb_state *mrb, mpz_t *gg, mpz_t *aa, mpz_t *bb) +mpz_abs(mpz_ctx_t *ctx, mpz_t *x, mpz_t *y) { - mpz_t a, b, t; - mpz_abs(mrb, &a, aa); mpz_abs(mrb, &b, bb); - mpz_init(mrb, &t); - - while (b.sn != 0) { - mpz_mod(mrb, &t, &a, &b); - mpz_set(mrb, &a, &b); - mpz_set(mrb, &b, &t); - } - trim(&a); - mpz_move(mrb, gg, &a); - mpz_clear(mrb, &b); - mpz_clear(mrb, &t); + mpz_init_heap(ctx, x, y->sz); + mpz_realloc(ctx, x, y->sz); + mpz_abs_copy(ctx, x, y); } + +/* Fast GCD for single limbs using binary algorithm */ +static mp_limb +limb_gcd(mp_limb a, mp_limb b) +{ + if (a == 0) return b; + if (b == 0) return a; + + /* Find power of 2 dividing both a and b */ + int shift = 0; + while (((a | b) & 1) == 0) { + a >>= 1; + b >>= 1; + shift++; + } + + /* Make a odd */ + while ((a & 1) == 0) { + a >>= 1; + } + + /* From here on, a is always odd */ + do { + /* Make b odd */ + while ((b & 1) == 0) { + b >>= 1; + } + + /* Now both a and b are odd. Ensure a >= b */ + if (a < b) { + mp_limb temp = a; + a = b; + b = temp; + } + + /* Replace b with (b - a) */ + b = b - a; + + } while (b != 0); + + /* Restore common factors of 2 */ + return a << shift; +} + +/* Count trailing zero bits in a multi-precision integer */ +static size_t +mpz_trailing_zeros(mpz_t *x) +{ + if (zero_p(x) || x->sz == 0) return 0; + + size_t zeros = 0; + + /* Count complete zero limbs */ + size_t i = 0; + while (i < x->sz && x->p[i] == 0) { + zeros += DIG_SIZE; + i++; + } + + /* Count trailing zeros in first non-zero limb */ + if (i < x->sz) { + mp_limb limb = x->p[i]; +#if (defined(__GNUC__) || __has_builtin(__builtin_ctzll)) + if (sizeof(mp_limb) == sizeof(unsigned long long)) { + zeros += __builtin_ctzll(limb); + } + else if (sizeof(mp_limb) == sizeof(unsigned long)) { + zeros += __builtin_ctzl(limb); + } + else { + zeros += __builtin_ctz(limb); + } +#else + /* Fallback bit counting */ + while ((limb & 1) == 0) { + limb >>= 1; + zeros++; + } #endif + } + + return zeros; +} + +/* Check if a number is a power of 2 */ +static int +mpz_power_of_2_p(mpz_t *x) +{ + if (zero_p(x) || x->sz == 0) return 0; + + /* Count non-zero limbs */ + size_t non_zero_limbs = 0; + size_t non_zero_index = 0; + + for (size_t i = 0; i < x->sz; i++) { + if (x->p[i] != 0) { + non_zero_limbs++; + non_zero_index = i; + if (non_zero_limbs > 1) return 0; /* More than one non-zero limb */ + } + } + + if (non_zero_limbs == 0) return 0; /* All zero */ + if (non_zero_limbs > 1) return 0; /* Multiple non-zero limbs */ + + /* Check if the single non-zero limb is a power of 2 */ + mp_limb limb = x->p[non_zero_index]; + return (limb != 0) && ((limb & (limb - 1)) == 0); +} + +/* Binary GCD algorithm (Stein's algorithm) - faster than Euclidean GCD */ +static void +mpz_gcd(mpz_ctx_t *ctx, mpz_t *gg, mpz_t *aa, mpz_t *bb) +{ + size_t pool_state = pool_save(ctx); + mpz_t a, b; + size_t shift; + size_t a_zeros; + size_t b_zeros; + + /* Handle special cases */ + if (zero_p(aa)) { + mpz_abs(ctx, gg, bb); + goto cleanup; + } + if (zero_p(bb)) { + mpz_abs(ctx, gg, aa); + goto cleanup; + } + + /* Fast path for single-limb numbers */ + if (aa->sz <= 1 && bb->sz <= 1) { + mp_limb a_limb = (aa->sz == 0) ? 0 : aa->p[0]; + mp_limb b_limb = (bb->sz == 0) ? 0 : bb->p[0]; + mp_limb result = limb_gcd(a_limb, b_limb); + + mpz_init(ctx, gg); + if (result == 0) { + gg->sn = 0; + gg->sz = 0; + } + else { + mpz_realloc(ctx, gg, 1); + gg->p[0] = result; + gg->sn = 1; + } + goto cleanup; + } + + /* Fast path for powers of 2 */ + if (mpz_power_of_2_p(aa)) { + a_zeros = mpz_trailing_zeros(aa); + b_zeros = mpz_trailing_zeros(bb); + size_t min_zeros = (a_zeros < b_zeros) ? a_zeros : b_zeros; + + mpz_init_set_int(ctx, gg, 1); + mpz_mul_2exp(ctx, gg, gg, min_zeros); + goto cleanup; + } + if (mpz_power_of_2_p(bb)) { + a_zeros = mpz_trailing_zeros(aa); + b_zeros = mpz_trailing_zeros(bb); + size_t min_zeros = (a_zeros < b_zeros) ? a_zeros : b_zeros; + + mpz_init_set_int(ctx, gg, 1); + mpz_mul_2exp(ctx, gg, gg, min_zeros); + goto cleanup; + } + + mpz_init_set(ctx, &a, aa); + mpz_init_set(ctx, &b, bb); + + shift = 0; + a_zeros = mpz_trailing_zeros(&a); + b_zeros = mpz_trailing_zeros(&b); + shift = (a_zeros < b_zeros) ? a_zeros : b_zeros; + + mpz_div_2exp(ctx, &a, &a, a_zeros); + mpz_div_2exp(ctx, &b, &b, b_zeros); + + /* Euclidean algorithm for multi-limb numbers */ + 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); + } + mpz_mul_2exp(ctx, gg, &a, shift); + mpz_clear(ctx, &a); + mpz_clear(ctx, &b); +cleanup: + pool_restore(ctx, pool_state); +} + static size_t mpz_bits(const mpz_t *x) @@ -1261,46 +2647,133 @@ mpz_bits(const mpz_t *x) return i * limb_bits + (limb_bits - lzb(high)); } +/* Compute Barrett parameter mu = floor(2^(2k) / m) where k ~ log2(m) */ static void -mpz_sqrt(mrb_state *mrb, mpz_t *z, mpz_t *x) +mpz_barrett_mu(mpz_ctx_t *ctx, mpz_t *mu, mpz_t *m) +{ + size_t k = mpz_bits(m); + mpz_t temp; + + mpz_init_set_int(ctx, &temp, 1); + mpz_mul_2exp(ctx, &temp, &temp, 2 * k); /* temp = 2^(2k) */ + mpz_mdiv(ctx, mu, &temp, m); /* mu = floor(2^(2k) / m) */ + mpz_clear(ctx, &temp); +} + +/* Barrett reduction: r = x mod m using precomputed mu */ +static void +mpz_barrett_reduce(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mpz_t *m, mpz_t *mu) +{ + size_t k = mpz_bits(m); + + /* If x < m, then x mod m = x */ + if (mpz_cmp(ctx, x, m) < 0) { + mpz_set(ctx, r, x); + return; + } + + mpz_t q1, q2, q3, r1, r2; + /* Conservative size estimates for Barrett reduction temporaries */ + size_t q_size = x->sz + mu->sz + 1; /* For multiplication results */ + size_t r_size = m->sz + 1; /* For modular reduction results */ + + mpz_init_temp(ctx, &q1, x->sz + 1); + mpz_init_temp(ctx, &q2, q_size); + mpz_init_temp(ctx, &q3, q_size); + mpz_init_temp(ctx, &r1, r_size); + mpz_init_temp(ctx, &r2, r_size); + + /* Step 1: q1 = floor(x / 2^(k-1)) */ + if (k > 1) { + mpz_div_2exp(ctx, &q1, x, k - 1); + } + else { + mpz_set(ctx, &q1, x); + } + + /* Step 2: q2 = q1 * mu */ + mpz_mul(ctx, &q2, &q1, mu); + + /* Step 3: q3 = floor(q2 / 2^(k+1)) */ + mpz_div_2exp(ctx, &q3, &q2, k + 1); + + /* Step 4: r1 = x mod 2^(k+1) */ + mpz_mod_2exp(ctx, &r1, x, k + 1); + + /* Step 5: r2 = (q3 * m) mod 2^(k+1) */ + mpz_mul(ctx, &r2, &q3, m); + mpz_mod_2exp(ctx, &r2, &r2, k + 1); + + /* Step 6: r = r1 - r2 */ + if (mpz_cmp(ctx, &r1, &r2) >= 0) { + mpz_sub(ctx, r, &r1, &r2); + } + else { + /* r1 < r2, so add 2^(k+1) to r1 */ + mpz_t power; + mpz_init_set_int(ctx, &power, 1); + mpz_mul_2exp(ctx, &power, &power, k + 1); + mpz_add(ctx, &r1, &r1, &power); + mpz_sub(ctx, r, &r1, &r2); + mpz_clear(ctx, &power); + } + + /* Step 7: Final correction - ensure 0 <= r < m */ + while (mpz_cmp(ctx, r, m) >= 0) { + mpz_sub(ctx, r, r, m); + } + + mpz_clear(ctx, &q1); + mpz_clear(ctx, &q2); + mpz_clear(ctx, &q3); + mpz_clear(ctx, &r1); + mpz_clear(ctx, &r2); +} + +static void +mpz_sqrt(mpz_ctx_t *ctx, mpz_t *z, mpz_t *x) { mrb_assert(x->sn >= 0); if (x->sz == 0) { // sqrt(0) = 0 + mpz_init(ctx, z); z->sn = 0; z->sz = 0; return; } - // Estimate initial value: 1 << (bit_length(x) / 2) + // Use heap-only implementation for now size_t xbits = mpz_bits(x); size_t sbit = (xbits + 1) / 2; mpz_t s, t; - mpz_init_set_int(mrb, &s, 1); - mpz_mul_2exp(mrb, &s, &s, sbit); + mpz_init_set_int(ctx, &s, 1); + mpz_mul_2exp(ctx, &s, &s, sbit); - mpz_init(mrb, &t); + mpz_init_temp(ctx, &t, x->sz + 1); // Iteratively refine s using Newton-Raphson method: // s = (s + x / s) / 2 for (;;) { - mpz_mdiv(mrb, &t, x, &s); // t = x / s - mpz_add(mrb, &t, &t, &s); // t = s + x/s - mpz_div_2exp(mrb, &t, &t, 1); // t = (s + x/s) / 2 + mpz_mdiv(ctx, &t, x, &s); // t = x / s + mpz_add(ctx, &t, &t, &s); // t = s + x/s + mpz_div_2exp(ctx, &t, &t, 1); // t = (s + x/s) / 2 - if (mpz_cmp(mrb, &t, &s) >= 0) { + if (mpz_cmp(ctx, &t, &s) >= 0) { // Converged: t >= s break; } - mpz_set(mrb, &s, &t); + mpz_set(ctx, &s, &t); } - mpz_move(mrb, z, &s); - mpz_clear(mrb, &t); + mpz_move(ctx, z, &s); + mpz_clear(ctx, &t); } + +/* Barrett reduction for efficient modular arithmetic with repeated operations */ + /* --- mruby functions --- */ /* initialize mpz_t from RBigint (not need to clear) */ static void @@ -1311,36 +2784,49 @@ bint_as_mpz(struct RBigint *b, mpz_t *x) x->sn = RBIGINT_SIGN(b); } -static struct RBigint* -bint_new(mrb_state *mrb, mpz_t *x) +/* Transfer mpz_t data to RBigint structure */ +static void +bint_set(mpz_ctx_t *ctx, struct RBigint *b, mpz_t *x) { - struct RBigint *b = MRB_OBJ_ALLOC(mrb, MRB_TT_BIGINT, mrb->integer_class); if (x->sz <= RBIGINT_EMBED_SIZE_MAX) { RBIGINT_SET_EMBED_SIZE(b, x->sz); RBIGINT_SET_EMBED_SIGN(b, x->sn); - if (x->p) memcpy(RBIGINT_EMBED_ARY(b), x->p, x->sz*sizeof(mp_limb)); - mpz_clear(mrb, x); + if (x->p) { + memcpy(RBIGINT_EMBED_ARY(b), x->p, x->sz*sizeof(mp_limb)); + } + else { + /* Initialize embedded array to zero when x->p is NULL */ + memset(RBIGINT_EMBED_ARY(b), 0, x->sz*sizeof(mp_limb)); + } + mpz_clear(ctx, x); } else { RBIGINT_SET_HEAP(b); - b->as.heap = *x; + mpz_move(ctx, &b->as.heap, x); } +} + +static struct RBigint* +bint_new(mpz_ctx_t *ctx, mpz_t *x) +{ + struct RBigint *b = MRB_OBJ_ALLOC(MPZ_MRB(ctx), MRB_TT_BIGINT, MPZ_MRB(ctx)->integer_class); + bint_set(ctx, b, x); return b; } static struct RBigint* -bint_new_int(mrb_state *mrb, mrb_int n) +bint_new_int(mpz_ctx_t *ctx, mrb_int n) { mpz_t x; - - mpz_init_set_int(mrb, &x, n); - return bint_new(mrb, &x); + mpz_init_set_int(ctx, &x, n); + return bint_new(ctx, &x); } mrb_value mrb_bint_new_int(mrb_state *mrb, mrb_int x) { - struct RBigint *b = bint_new_int(mrb, x); + MPZ_CTX_INIT(mrb, ctx, pool); + struct RBigint *b = bint_new_int(ctx, x); return mrb_obj_value(b); } @@ -1349,8 +2835,10 @@ mrb_value mrb_bint_new_int64(mrb_state *mrb, int64_t n) { mpz_t x; - mpz_set_int64(mrb, &x, n); - struct RBigint *b = bint_new(mrb, &x); + MPZ_CTX_INIT(mrb, ctx, pool); + + mpz_set_int64(ctx, &x, n); + struct RBigint *b = bint_new(ctx, &x); return mrb_obj_value(b); } #endif @@ -1359,28 +2847,11 @@ mrb_value mrb_bint_new_uint64(mrb_state *mrb, uint64_t x) { mpz_t z; - mpz_init(mrb, &z); - mpz_set_uint64(mrb, &z, x); - struct RBigint *b = bint_new(mrb ,&z); - return mrb_obj_value(b); -} + MPZ_CTX_INIT(mrb, ctx, pool); -mrb_value -mrb_bint_new_str(mrb_state *mrb, const char *x, mrb_int len, mrb_int base) -{ - mpz_t z; - - int sn = 1; - if (base < 0) { - base = -base; - sn = -1; - } - mrb_assert(2 <= base && base <= 36); - mpz_init_set_str(mrb, &z, x, len, base); - if (sn < 0) { - z.sn = sn; - } - struct RBigint *b = bint_new(mrb, &z); + mpz_init(ctx, &z); + mpz_set_uint64(ctx, &z, x); + struct RBigint *b = bint_new(ctx, &z); return mrb_obj_value(b); } @@ -1397,12 +2868,34 @@ bint_norm(mrb_state *mrb, struct RBigint *b) return mrb_obj_value(b); } +mrb_value +mrb_bint_new_str(mrb_state *mrb, const char *x, mrb_int len, mrb_int base) +{ + mpz_t z; + int sn = 1; + + if (base < 0) { + base = -base; + sn = -1; + } + mrb_assert(2 <= base && base <= 36); + + MPZ_CTX_INIT(mrb, ctx, pool); + mpz_init_set_str(ctx, &z, x, len, base); + if (sn < 0) { + z.sn = sn; + } + return bint_norm(mrb, bint_new(ctx, &z)); +} + void mrb_gc_free_bint(mrb_state *mrb, struct RBasic *x) { struct RBigint *b = (struct RBigint*)x; + MPZ_CTX_INIT(mrb, ctx, pool); + if (!RBIGINT_EMBED_P(b)) { - mpz_clear(mrb, &b->as.heap); + mpz_clear(ctx, &b->as.heap); } } @@ -1429,8 +2922,9 @@ mrb_bint_new_float(mrb_state *mrb, mrb_float x) return mrb_fixnum_value(0); } + MPZ_CTX_INIT(mrb, ctx, pool); mpz_t r; - mpz_init(mrb, &r); + mpz_init(ctx, &r); r.sn = sn; mrb_float b = (double)DIG_BASE; @@ -1440,7 +2934,7 @@ mrb_bint_new_float(mrb_state *mrb, mrb_float x) for (rn = 1; x >= b; rn++) x *= bi; - mpz_realloc(mrb, &r, rn); + mpz_realloc(ctx, &r, rn); mp_limb *rp = r.p; for (size_t i=rn-1;;i--) { mp_limb f = LOW((mp_limb)x); @@ -1449,7 +2943,7 @@ mrb_bint_new_float(mrb_state *mrb, mrb_float x) rp[i] = f; if (i == 0) break; } - return bint_norm(mrb, bint_new(mrb, &r)); + return bint_norm(mrb, bint_new(ctx, &r)); } mrb_float @@ -1561,25 +3055,28 @@ mrb_bint_add_n(mrb_state *mrb, mrb_value x, mrb_value y) mpz_t a, b, z; bint_as_mpz(RBIGINT(x), &a); + + MPZ_CTX_INIT(mrb, ctx, pool); + if (mrb_integer_p(y)) { mrb_int n = mrb_integer(y); if (int_fit_limb_p(n)) { - mpz_init_set(mrb, &z, &a); + mpz_init_set(ctx, &z, &a); if ((n > 0) ^ (z.sn > 0)) { - mpz_sub_int(mrb, &z, n<0 ? -n : n); + mpz_sub_int(ctx, &z, n<0 ? -n : n); } else { - mpz_add_int(mrb, &z, n<0 ? -n : n); + mpz_add_int(ctx, &z, n<0 ? -n : n); } - struct RBigint *v = bint_new(mrb, &z); + struct RBigint *v = bint_new(ctx, &z); return mrb_obj_value(v); } } y = mrb_as_bint(mrb, y); bint_as_mpz(RBIGINT(y), &b); - mpz_init(mrb, &z); - mpz_add(mrb, &z, &a, &b); - struct RBigint *v = bint_new(mrb, &z); + mpz_init(ctx, &z); + mpz_add(ctx, &z, &a, &b); + struct RBigint *v = bint_new(ctx, &z); return mrb_obj_value(v); } @@ -1602,27 +3099,28 @@ mrb_value mrb_bint_sub_n(mrb_state *mrb, mrb_value x, mrb_value y) { mpz_t a, b, z; + MPZ_CTX_INIT(mrb, ctx, pool); bint_as_mpz(RBIGINT(x), &a); if (mrb_integer_p(y)) { mrb_int n = mrb_integer(y); if (int_fit_limb_p(n)) { - mpz_init_set(mrb, &z, &a); + mpz_init_set(ctx, &z, &a); if ((n > 0) ^ (z.sn > 0)) { - mpz_add_int(mrb, &z, n<0 ? -n : n); + mpz_add_int(ctx, &z, n<0 ? -n : n); } else { - mpz_sub_int(mrb, &z, n<0 ? -n : n); + mpz_sub_int(ctx, &z, n<0 ? -n : n); } - struct RBigint *v = bint_new(mrb, &z); + struct RBigint *v = bint_new(ctx, &z); return mrb_obj_value(v); } } y = mrb_as_bint(mrb, y); bint_as_mpz(RBIGINT(y), &b); - mpz_init(mrb, &z); - mpz_sub(mrb, &z, &a, &b); - struct RBigint *v = bint_new(mrb, &z); + mpz_init(ctx, &z); + mpz_sub(ctx, &z, &a, &b); + struct RBigint *v = bint_new(ctx, &z); return mrb_obj_value(v); } @@ -1648,9 +3146,11 @@ bint_mul(mrb_state *mrb, mrb_value x, mrb_value y) y = mrb_as_bint(mrb, y); bint_as_mpz(RBIGINT(x), &a); bint_as_mpz(RBIGINT(y), &b); - mpz_init(mrb, &z); - mpz_mul(mrb, &z, &a, &b); - return bint_new(mrb, &z); + + MPZ_CTX_INIT(mrb, ctx, pool); + mpz_init(ctx, &z); + mpz_mul(ctx, &z, &a, &b); + return bint_new(ctx, &z); } mrb_value @@ -1699,51 +3199,56 @@ mrb_bint_div(mrb_state *mrb, mrb_value x, mrb_value y) mrb_int_zerodiv(mrb); } bint_as_mpz(RBIGINT(x), &a); - mpz_init(mrb, &z); - mpz_mdiv(mrb, &z, &a, &b); - return bint_norm(mrb, bint_new(mrb, &z)); + + MPZ_CTX_INIT(mrb, ctx, pool); + mpz_init(ctx, &z); + mpz_mdiv(ctx, &z, &a, &b); + return bint_norm(mrb, bint_new(ctx, &z)); } mrb_value mrb_bint_add_ii(mrb_state *mrb, mrb_int x, mrb_int y) { mpz_t a, b, z; + MPZ_CTX_INIT(mrb, ctx, pool); - mpz_init_set_int(mrb, &a, x); - mpz_init_set_int(mrb, &b, y); - mpz_init(mrb, &z); - mpz_add(mrb, &z, &a, &b); - mpz_clear(mrb, &a); - mpz_clear(mrb, &b); - return bint_norm(mrb, bint_new(mrb, &z)); + mpz_init(ctx, &z); + mpz_init_set_int(ctx, &a, x); + mpz_init_set_int(ctx, &b, y); + mpz_add(ctx, &z, &a, &b); + mpz_clear(ctx, &a); + mpz_clear(ctx, &b); + return bint_norm(mrb, bint_new(ctx, &z)); } mrb_value mrb_bint_sub_ii(mrb_state *mrb, mrb_int x, mrb_int y) { mpz_t a, b, z; + MPZ_CTX_INIT(mrb, ctx, pool); - mpz_init_set_int(mrb, &a, x); - mpz_init_set_int(mrb, &b, y); - mpz_init(mrb, &z); - mpz_sub(mrb, &z, &a, &b); - mpz_clear(mrb, &a); - mpz_clear(mrb, &b); - return bint_norm(mrb, bint_new(mrb, &z)); + mpz_init(ctx, &z); + mpz_init_set_int(ctx, &a, x); + mpz_init_set_int(ctx, &b, y); + mpz_sub(ctx, &z, &a, &b); + mpz_clear(ctx, &a); + mpz_clear(ctx, &b); + return bint_norm(mrb, bint_new(ctx, &z)); } mrb_value mrb_bint_mul_ii(mrb_state *mrb, mrb_int x, mrb_int y) { mpz_t a, b, z; + MPZ_CTX_INIT(mrb, ctx, pool); - mpz_init_set_int(mrb, &a, x); - mpz_init_set_int(mrb, &b, y); - mpz_init(mrb, &z); - mpz_mul(mrb, &z, &a, &b); - mpz_clear(mrb, &a); - mpz_clear(mrb, &b); - return bint_norm(mrb, bint_new(mrb, &z)); + mpz_init(ctx, &z); + mpz_init_set_int(ctx, &a, x); + mpz_init_set_int(ctx, &b, y); + mpz_mul(ctx, &z, &a, &b); + mpz_clear(ctx, &a); + mpz_clear(ctx, &b); + return bint_norm(mrb, bint_new(ctx, &z)); } mrb_value @@ -1760,15 +3265,18 @@ mrb_bint_mod(mrb_state *mrb, mrb_value x, mrb_value y) mrb_int_zerodiv(mrb); } mpz_t a, b, z; + x = mrb_as_bint(mrb, x); y = mrb_as_bint(mrb, y); bint_as_mpz(RBIGINT(y), &b); if (zero_p(&b) || uzero_p(&b)) { mrb_int_zerodiv(mrb); } bint_as_mpz(RBIGINT(x), &a); - mpz_init(mrb, &z); - mpz_mmod(mrb, &z, &a, &b); - return bint_norm(mrb, bint_new(mrb, &z)); + + MPZ_CTX_INIT(mrb, ctx, pool); + mpz_init(ctx, &z); + mpz_mmod(ctx, &z, &a, &b); + return bint_norm(mrb, bint_new(ctx, &z)); } mrb_value @@ -1780,15 +3288,18 @@ mrb_bint_rem(mrb_state *mrb, mrb_value x, mrb_value y) mrb_int_zerodiv(mrb); } mpz_t a, b, z; + x = mrb_as_bint(mrb, x); y = mrb_as_bint(mrb, y); bint_as_mpz(RBIGINT(y), &b); if (zero_p(&b) || uzero_p(&b)) { mrb_int_zerodiv(mrb); } bint_as_mpz(RBIGINT(x), &a); - mpz_init(mrb, &z); - mpz_mod(mrb, &z, &a, &b); - return bint_norm(mrb, bint_new(mrb, &z)); + + MPZ_CTX_INIT(mrb, ctx, pool); + mpz_init(ctx, &z); + mpz_mod(ctx, &z, &a, &b); + return bint_norm(mrb, bint_new(ctx, &z)); } mrb_value @@ -1806,10 +3317,12 @@ mrb_bint_divmod(mrb_state *mrb, mrb_value x, mrb_value y) mrb_int_zerodiv(mrb); } bint_as_mpz(RBIGINT(x), &a); - mpz_init(mrb, &c); - mpz_init(mrb, &d); - mpz_mdivmod(mrb, &c, &d, &a, &b); - return mrb_assoc_new(mrb, bint_norm(mrb, bint_new(mrb, &c)), bint_norm(mrb, bint_new(mrb, &d))); + + MPZ_CTX_INIT(mrb, ctx, pool); + mpz_init(ctx, &c); + mpz_init(ctx, &d); + mpz_mdivmod(ctx, &c, &d, &a, &b); + return mrb_assoc_new(mrb, bint_norm(mrb, bint_new(ctx, &c)), bint_norm(mrb, bint_new(ctx, &d))); } mrb_int @@ -1841,7 +3354,8 @@ mrb_bint_cmp(mrb_state *mrb, mrb_value x, mrb_value y) } mpz_t b; bint_as_mpz(RBIGINT(y), &b); - return mpz_cmp(mrb, &a, &b); + MPZ_CTX_INIT(mrb, ctx, pool); + return mpz_cmp(ctx, &a, &b); } mrb_value @@ -1860,10 +3374,10 @@ mrb_bint_pow(mrb_state *mrb, mrb_value x, mrb_value y) } mpz_t z; - mpz_init(mrb, &z); - mpz_pow(mrb, &z, &a, mrb_integer(y)); + MPZ_CTX_INIT(mrb, ctx, pool); + mpz_pow(ctx, &z, &a, mrb_integer(y)); - struct RBigint *b = bint_new(mrb, &z); + struct RBigint *b = bint_new(ctx, &z); return mrb_obj_value(b); } @@ -1871,12 +3385,13 @@ mrb_value mrb_bint_powm(mrb_state *mrb, mrb_value x, mrb_value exp, mrb_value mod) { mpz_t a, b, c, z; + MPZ_CTX_INIT(mrb, ctx, pool); bint_as_mpz(RBIGINT(x), &a); if (mrb_integer_p(mod)) { mrb_int m = mrb_integer(mod); if (m == 0) mrb_int_zerodiv(mrb); - mpz_init_set_int(mrb, &c, m); + mpz_init_set_int(ctx, &c, m); } else { mod = mrb_as_bint(mrb, mod); @@ -1885,22 +3400,22 @@ mrb_bint_powm(mrb_state *mrb, mrb_value x, mrb_value exp, mrb_value mod) mrb_int_zerodiv(mrb); } } - mpz_init(mrb, &z); + mpz_init(ctx, &z); if (mrb_bigint_p(exp)) { bint_as_mpz(RBIGINT(exp), &b); if (b.sn < 0) goto raise; - mpz_powm(mrb, &z, &a, &b, &c); + mpz_powm(ctx, &z, &a, &b, &c); } else { mrb_int e = mrb_integer(exp); if (e < 0) goto raise; - mpz_powm_i(mrb, &z, &a, e, &c); + mpz_powm_i(ctx, &z, &a, e, &c); } - if (mrb_integer_p(mod)) mpz_clear(mrb, &c); - return bint_norm(mrb, bint_new(mrb, &z)); + if (mrb_integer_p(mod)) mpz_clear(ctx, &c); + return bint_norm(mrb, bint_new(ctx, &z)); raise: - if (mrb_integer_p(mod)) mpz_clear(mrb, &c); + if (mrb_integer_p(mod)) mpz_clear(ctx, &c); mrb_raise(mrb, E_ARGUMENT_ERROR, "int.pow(n,m): n must be positive"); /* not reached */ return mrb_nil_value(); @@ -1920,7 +3435,8 @@ mrb_bint_to_s(mrb_state *mrb, mrb_value x, mrb_int base) mrb_raise(mrb, E_ARGUMENT_ERROR, "too long string from Integer"); } mrb_value str = mrb_str_new(mrb, NULL, len+2); - mpz_get_str(mrb, RSTRING_PTR(str), len, base, &a); + MPZ_CTX_INIT(mrb, ctx, pool); + mpz_get_str(ctx, RSTRING_PTR(str), len, base, &a); RSTR_SET_LEN(RSTRING(str), strlen(RSTRING_PTR(str))); return str; } @@ -1944,9 +3460,11 @@ mrb_bint_and(mrb_state *mrb, mrb_value x, mrb_value y) y = mrb_as_bint(mrb, y); bint_as_mpz(RBIGINT(y), &b); if (zero_p(&a) || zero_p(&b)) return mrb_fixnum_value(0); - mpz_init(mrb, &c); - mpz_and(mrb, &c, &a, &b); - return bint_norm(mrb, bint_new(mrb, &c)); + + MPZ_CTX_INIT(mrb, ctx, pool); + mpz_init(ctx, &c); + mpz_and(ctx, &c, &a, &b); + return bint_norm(mrb, bint_new(ctx, &c)); } mrb_value @@ -1961,48 +3479,57 @@ mrb_bint_or(mrb_state *mrb, mrb_value x, mrb_value y) if (z == -1) return y; } + MPZ_CTX_INIT(mrb, ctx, pool); y = mrb_as_bint(mrb, y); bint_as_mpz(RBIGINT(y), &b); if (zero_p(&a)) return y; if (zero_p(&b)) return x; - mpz_init(mrb, &c); - mpz_or(mrb, &c, &b, &a); - return bint_norm(mrb, bint_new(mrb, &c)); + mpz_init(ctx, &c); + mpz_or(ctx, &c, &b, &a); + return bint_norm(mrb, bint_new(ctx, &c)); } mrb_value mrb_bint_xor(mrb_state *mrb, mrb_value x, mrb_value y) { mpz_t a, b, c; + MPZ_CTX_INIT(mrb, ctx, pool); bint_as_mpz(RBIGINT(x), &a); if (mrb_integer_p(y) && a.sn > 0) { mrb_int z = mrb_integer(y); if (z == 0) return x; if (0 < z && (mp_dbl_limb)z < DIG_BASE) { - mpz_init_set(mrb, &c, &a); - c.p[0] ^= z; - return bint_norm(mrb, bint_new(mrb, &c)); + mpz_init_set(ctx, &c, &a); + if (a.sz == 0) { + mpz_realloc(ctx, &c, 1); + c.p[0] = (mp_limb)z; + } + else { + c.p[0] ^= (mp_limb)z; + } + return bint_norm(mrb, bint_new(ctx, &c)); } } y = mrb_as_bint(mrb, y); bint_as_mpz(RBIGINT(y), &b); if (zero_p(&a)) return y; if (zero_p(&b)) return x; - mpz_init(mrb, &c); - mpz_xor(mrb, &c, &a, &b); - return bint_norm(mrb, bint_new(mrb, &c)); + mpz_init(ctx, &c); + mpz_xor(ctx, &c, &a, &b); + return bint_norm(mrb, bint_new(ctx, &c)); } mrb_value mrb_bint_neg(mrb_state *mrb, mrb_value x) { mpz_t a, b; + MPZ_CTX_INIT(mrb, ctx, pool); bint_as_mpz(RBIGINT(x), &a); - mpz_init(mrb, &b); - mpz_neg(mrb, &b, &a); - struct RBigint *b2 = bint_new(mrb, &b); + mpz_init(ctx, &b); + mpz_neg(ctx, &b, &a); + struct RBigint *b2 = bint_new(ctx, &b); /* no normalization */ return mrb_obj_value(b2); } @@ -2011,53 +3538,58 @@ mrb_value mrb_bint_rev(mrb_state *mrb, mrb_value x) { mpz_t a, b; + MPZ_CTX_INIT(mrb, ctx, pool); + bint_as_mpz(RBIGINT(x), &a); - mpz_init(mrb, &b); - mpz_neg(mrb, &b, &a); - mpz_sub_int(mrb, &b, 1); - return bint_norm(mrb, bint_new(mrb, &b)); + mpz_init(ctx, &b); + mpz_neg(ctx, &b, &a); + mpz_sub_int(ctx, &b, 1); + return bint_norm(mrb, bint_new(ctx, &b)); } mrb_value mrb_bint_lshift(mrb_state *mrb, mrb_value x, mrb_int width) { mpz_t a, z; + MPZ_CTX_INIT(mrb, ctx, pool); bint_as_mpz(RBIGINT(x), &a); - mpz_init(mrb, &z); + mpz_init(ctx, &z); if (width < 0) { - mpz_div_2exp(mrb, &z, &a, -width); + mpz_div_2exp(ctx, &z, &a, -width); } else { - mpz_mul_2exp(mrb, &z, &a, width); + mpz_mul_2exp(ctx, &z, &a, width); } - return bint_norm(mrb, bint_new(mrb, &z)); + return bint_norm(mrb, bint_new(ctx, &z)); } mrb_value mrb_bint_rshift(mrb_state *mrb, mrb_value x, mrb_int width) { mpz_t a, z; + MPZ_CTX_INIT(mrb, ctx, pool); bint_as_mpz(RBIGINT(x), &a); - mpz_init(mrb, &z); + mpz_init(ctx, &z); if (width < 0) { - mpz_mul_2exp(mrb, &z, &a, -width); + mpz_mul_2exp(ctx, &z, &a, -width); } else { - mpz_div_2exp(mrb, &z, &a, width); + mpz_div_2exp(ctx, &z, &a, width); } - return bint_norm(mrb, bint_new(mrb, &z)); + return bint_norm(mrb, bint_new(ctx, &z)); } void mrb_bint_copy(mrb_state *mrb, mrb_value x, mrb_value y) { - mpz_t a, b; + mpz_t b, temp; + MPZ_CTX_INIT(mrb, ctx, pool); - bint_as_mpz(RBIGINT(x), &a); bint_as_mpz(RBIGINT(y), &b); - mpz_init_set(mrb, &a, &b); + mpz_init_set(ctx, &temp, &b); + bint_set(ctx, RBIGINT(x), &temp); } size_t @@ -2078,12 +3610,42 @@ mrb_bint_sqrt(mrb_state *mrb, mrb_value x) if (a.sn < 0) { mrb_raise(mrb, E_ARGUMENT_ERROR, "square root of negative number"); } + MPZ_CTX_INIT(mrb, ctx, pool); mpz_t z; - mpz_init(mrb, &z); - mpz_sqrt(mrb, &z, &a); + mpz_init(ctx, &z); + mpz_sqrt(ctx, &z, &a); - return bint_norm(mrb, bint_new(mrb, &z)); + return bint_norm(mrb, bint_new(ctx, &z)); +} + +mrb_int +mrb_bint_sign(mrb_state *mrb, mrb_value bint) +{ + return RBIGINT_SIGN(RBIGINT(bint)); +} + +mrb_int +mrb_bint_size(mrb_state *mrb, mrb_value bint) +{ + mpz_t z; + bint_as_mpz(RBIGINT(bint), &z); + return z.sz * sizeof(mp_limb); +} + +mrb_value +mrb_bint_from_bytes(mrb_state *mrb, const uint8_t *bytes, mrb_int len) +{ + mpz_t z; + size_t limb_len = (len + sizeof(mp_limb) - 1) / sizeof(mp_limb); + MPZ_CTX_INIT(mrb, ctx, pool); + + mpz_init_heap(ctx, &z, limb_len); + memcpy(z.p, bytes, len); + z.sn = (len > 0) ? 1 : 0; + z.sz = limb_len; + trim(&z); + return bint_norm(mrb, bint_new(ctx, &z)); } mrb_value @@ -2102,12 +3664,13 @@ mrb_value mrb_bint_2comp(mrb_state *mrb, mrb_value x) { mpz_t a, z; + MPZ_CTX_INIT(mrb, ctx, pool); bint_as_mpz(RBIGINT(x), &a); - mpz_init(mrb, &z); + mpz_init(ctx, &z); mrb_assert(a.sn < 0); size_t size = a.sz; - mpz_realloc(mrb, &z, size); + mpz_realloc(ctx, &z, size); mp_limb *ds = a.p; mp_limb *dd = z.p; char carry = 1; @@ -2118,7 +3681,7 @@ mrb_bint_2comp(mrb_state *mrb, mrb_value x) } z.sn = 1; - struct RBigint *b2 = bint_new(mrb, &z); + struct RBigint *b2 = bint_new(ctx, &z); return mrb_obj_value(b2); } @@ -2127,22 +3690,95 @@ void mrb_bint_reduce(mrb_state *mrb, mrb_value *xp, mrb_value *yp) { mpz_t r, x, y, a, b; - mpz_init(mrb, &r); - mpz_init(mrb, &a); mpz_init(mrb, &b); + MPZ_CTX_INIT(mrb, ctx, pool); + + mpz_init(ctx, &r); + mpz_init(ctx, &a); mpz_init(ctx, &b); bint_as_mpz(RBIGINT(*xp), &x); bint_as_mpz(RBIGINT(*yp), &y); - mpz_gcd(mrb, &r, &x, &y); + mpz_gcd(ctx, &r, &x, &y); - mpz_mdiv(mrb, &a, &x, &r); - mpz_mdiv(mrb, &b, &y, &r); + mpz_mdiv(ctx, &a, &x, &r); + mpz_mdiv(ctx, &b, &y, &r); - mpz_clear(mrb, &r); + mpz_clear(ctx, &r); - struct RBigint *b1 = bint_new(mrb, &a); - struct RBigint *b2 = bint_new(mrb, &b); + struct RBigint *b1 = bint_new(ctx, &a); + struct RBigint *b2 = bint_new(ctx, &b); *xp = mrb_obj_value(b1); *yp = mrb_obj_value(b2); } #endif + +mrb_value +mrb_bint_gcd(mrb_state *mrb, mrb_value x, mrb_value y) +{ + mpz_t r, a, b; + MPZ_CTX_INIT(mrb, ctx, pool); + + mpz_init(ctx, &r); + bint_as_mpz(RBIGINT(x), &a); + bint_as_mpz(RBIGINT(y), &b); + + mpz_gcd(ctx, &r, &a, &b); + + struct RBigint *result = bint_new(ctx, &r); + return bint_norm(mrb, result); +} + +mrb_value +mrb_bint_lcm(mrb_state *mrb, mrb_value x, mrb_value y) +{ + mpz_t gcd_val, x_mpz, y_mpz, abs_x, abs_y, product, result_mpz; + mrb_value zero = mrb_bint_new_int(mrb, 0); + + if (mrb_bint_cmp(mrb, x, zero) == 0 || mrb_bint_cmp(mrb, y, zero) == 0) { + return zero; + } + + MPZ_CTX_INIT(mrb, ctx, pool); + /* Get input operand sizes for size estimation */ + size_t x_size = RBIGINT_EMBED_P(RBIGINT(x)) ? RBIGINT_EMBED_SIZE(RBIGINT(x)) : RBIGINT(x)->as.heap.sz; + size_t y_size = RBIGINT_EMBED_P(RBIGINT(y)) ? RBIGINT_EMBED_SIZE(RBIGINT(y)) : RBIGINT(y)->as.heap.sz; + size_t max_size = (x_size > y_size) ? x_size : y_size; + + mpz_init_temp(ctx, &gcd_val, max_size); + mpz_init_temp(ctx, &abs_x, x_size); + mpz_init_temp(ctx, &abs_y, y_size); + mpz_init_temp(ctx, &product, x_size + y_size + 1); + mpz_init_temp(ctx, &result_mpz, x_size + y_size + 1); + + bint_as_mpz(RBIGINT(x), &x_mpz); + bint_as_mpz(RBIGINT(y), &y_mpz); + + mpz_abs(ctx, &abs_x, &x_mpz); + mpz_abs(ctx, &abs_y, &y_mpz); + + mpz_gcd(ctx, &gcd_val, &abs_x, &abs_y); + mpz_mul(ctx, &product, &abs_x, &abs_y); + mpz_mdiv(ctx, &result_mpz, &product, &gcd_val); + + mpz_clear(ctx, &gcd_val); + mpz_clear(ctx, &abs_x); + mpz_clear(ctx, &abs_y); + mpz_clear(ctx, &product); + + struct RBigint *result = bint_new(ctx, &result_mpz); + return mrb_obj_value(result); +} + +mrb_value +mrb_bint_abs(mrb_state *mrb, mrb_value x) +{ + mpz_t a, result_mpz; + MPZ_CTX_INIT(mrb, ctx, pool); + + mpz_init(ctx, &result_mpz); + bint_as_mpz(RBIGINT(x), &a); + mpz_abs(ctx, &result_mpz, &a); + + struct RBigint *result = bint_new(ctx, &result_mpz); + return mrb_obj_value(result); +} diff --git a/mrbgems/mruby-bigint/test/bigint.rb b/mrbgems/mruby-bigint/test/bigint.rb index 3280c5004..07f6c783e 100644 --- a/mrbgems/mruby-bigint/test/bigint.rb +++ b/mrbgems/mruby-bigint/test/bigint.rb @@ -42,6 +42,13 @@ assert 'Bigint *' do assert_equal(-1361129467683753853853498429727072845824, -n * n) assert_equal(-1361129467683753853853498429727072845824, n * -n) assert_equal 1361129467683753853853498429727072845824, -n * -n + + # Test multiplication commutativity for large numbers with different limb counts + # This test specifically targets the bug where operands with different + # limb counts would produce different results based on order + a = (2**512) - 1 # 16 limbs + b = 26815615859885194199148049996411692254958731641184786755447122887443528060147093953603748596333806855380063716372972101707507765623893139892867298012168194 # 17 limbs + assert_equal(a * b, b * a) end assert 'Bigint /' do diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c b/mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c index f78c1e1fc..027e29afa 100644 --- a/mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/cmdprint.c @@ -16,26 +16,21 @@ dbgcmd_state dbgcmd_print(mrb_state *mrb, mrdb_state *mrdb) { - mrb_value expr; - mrb_value result; - uint8_t wcnt; - int ai; - if (mrdb->wcnt <= 1) { puts("Parameter not specified."); return DBGST_PROMPT; } - ai = mrb_gc_arena_save(mrb); + int ai = mrb_gc_arena_save(mrb); /* eval expr */ - expr = mrb_str_new_cstr(mrb, NULL); - for (wcnt=1; wcntwcnt; wcnt++) { + mrb_value expr = mrb_str_new_cstr(mrb, NULL); + for (uint8_t wcnt=1; wcntwcnt; wcnt++) { expr = mrb_str_cat_lit(mrb, expr, " "); expr = mrb_str_cat_cstr(mrb, expr, mrdb->words[wcnt]); } - result = mrb_debug_eval(mrb, mrdb->dbg, RSTRING_PTR(expr), RSTRING_LEN(expr), NULL, 0); + mrb_value result = mrb_debug_eval(mrb, mrdb->dbg, RSTRING_PTR(expr), RSTRING_LEN(expr), NULL, 0); /* $print_no = result */ printf("$%lu = ", (unsigned long)mrdb->print_no++); @@ -60,15 +55,10 @@ dbgcmd_eval(mrb_state *mrb, mrdb_state *mrdb) dbgcmd_state dbgcmd_info_local(mrb_state *mrb, mrdb_state *mrdb) { - mrb_value result; - mrb_value s; - int ai; + int ai = mrb_gc_arena_save(mrb); - ai = mrb_gc_arena_save(mrb); - - result = mrb_debug_eval(mrb, mrdb->dbg, "local_variables", 0, NULL, 1); - - s = mrb_str_cat_lit(mrb, result, "\0"); + mrb_value result = mrb_debug_eval(mrb, mrdb->dbg, "local_variables", 0, NULL, 1); + mrb_value s = mrb_str_cat_lit(mrb, result, "\0"); printf("$%lu = %s\n", (unsigned long)mrdb->print_no++, RSTRING_PTR(s)); if (mrdb->print_no == 0) { diff --git a/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c b/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c index 0413c0be0..b3f1a1db8 100644 --- a/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c +++ b/mrbgems/mruby-bin-debugger/tools/mrdb/mrdb.c @@ -673,8 +673,9 @@ main(int argc, char **argv) l_restart: - if (mrb == NULL) { - fputs("Invalid mrb_state, exiting mruby\n", stderr); + if (MRB_OPEN_FAILURE(mrb)) { + mrb_print_error(mrb); /* handles NULL */ + mrb_close(mrb); /* handles NULL */ return EXIT_FAILURE; } diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c b/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c index 3d25b64f8..35901816c 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c @@ -114,10 +114,7 @@ get_history_path(mrb_state *mrb) static void p(mrb_state *mrb, mrb_value obj) { - mrb_value val; - char* msg; - - val = mrb_funcall_argv(mrb, obj, MRB_SYM(inspect), 0, NULL); + mrb_value val = mrb_funcall_argv(mrb, obj, MRB_SYM(inspect), 0, NULL); if (!mrb->exc) { fputs(" => ", stdout); } @@ -127,7 +124,7 @@ p(mrb_state *mrb, mrb_value obj) if (!mrb_string_p(val)) { val = mrb_obj_as_string(mrb, obj); } - msg = mrb_locale_from_utf8(RSTRING_PTR(val), (int)RSTRING_LEN(val)); + char* msg = mrb_locale_from_utf8(RSTRING_PTR(val), (int)RSTRING_LEN(val)); fwrite(msg, strlen(msg), 1, stdout); mrb_locale_free(msg); putc('\n', stdout); @@ -136,14 +133,11 @@ p(mrb_state *mrb, mrb_value obj) static void p_error(mrb_state *mrb, struct RObject* exc) { - mrb_value val; - char* msg; - - val = mrb_exc_get_output(mrb, exc); + mrb_value val = mrb_exc_get_output(mrb, exc); if (!mrb_string_p(val)) { val = mrb_obj_as_string(mrb, val); } - msg = mrb_locale_from_utf8(RSTRING_PTR(val), (int)RSTRING_LEN(val)); + char* msg = mrb_locale_from_utf8(RSTRING_PTR(val), (int)RSTRING_LEN(val)); fwrite(msg, strlen(msg), 1, stdout); mrb_locale_free(msg); putc('\n', stdout); @@ -481,8 +475,9 @@ main(int argc, char **argv) /* new interpreter instance */ mrb = mrb_open(); - if (mrb == NULL) { - fputs("Invalid mrb interpreter, exiting mirb\n", stderr); + if (MRB_OPEN_FAILURE(mrb)) { + mrb_print_error(mrb); /* handles NULL */ + mrb_close(mrb); /* handles NULL */ return EXIT_FAILURE; } diff --git a/mrbgems/mruby-bin-mrbc/tools/mrbc/mrbc.c b/mrbgems/mruby-bin-mrbc/tools/mrbc/mrbc.c index 2b4b44c8c..9050d138b 100644 --- a/mrbgems/mruby-bin-mrbc/tools/mrbc/mrbc.c +++ b/mrbgems/mruby-bin-mrbc/tools/mrbc/mrbc.c @@ -199,14 +199,13 @@ partial_hook(struct mrb_parser_state *p) { mrb_ccontext *c = p->cxt; struct mrbc_args *args = (struct mrbc_args*)c->partial_data; - const char *fn; if (p->f) fclose(p->f); if (args->idx >= args->argc) { p->f = NULL; return -1; } - fn = args->argv[args->idx++]; + const char *fn = args->argv[args->idx++]; p->f = fopen(fn, "rb"); if (p->f == NULL) { fprintf(stderr, "%s: cannot open program file. (%s)\n", args->prog, fn); @@ -219,13 +218,11 @@ partial_hook(struct mrb_parser_state *p) static mrb_value load_file(mrb_state *mrb, struct mrbc_args *args) { - mrb_ccontext *c; - mrb_value result; char *input = args->argv[args->idx]; FILE *infile; mrb_bool need_close = FALSE; - c = mrb_ccontext_new(mrb); + mrb_ccontext *c = mrb_ccontext_new(mrb); if (args->verbose) c->dump_result = TRUE; c->no_exec = TRUE; @@ -248,7 +245,7 @@ load_file(mrb_state *mrb, struct mrbc_args *args) mrb_ccontext_partial_hook(c, partial_hook, (void*)args); } - result = mrb_load_file_cxt(mrb, infile, c); + mrb_value result = mrb_load_file_cxt(mrb, infile, c); if (need_close) fclose(infile); mrb_ccontext_free(mrb, c); if (mrb_undef_p(result)) { @@ -287,17 +284,15 @@ int main(int argc, char **argv) { mrb_state *mrb = mrb_open_core(); - int n, result; struct mrbc_args args; FILE *wfp; - mrb_value load; if (mrb == NULL) { fputs("Invalid mrb_state, exiting mrbc\n", stderr); return EXIT_FAILURE; } - n = parse_args(mrb, argc, argv, &args); + int n = parse_args(mrb, argc, argv, &args); if (n < 0) { cleanup(mrb, &args); usage(argv[0]); @@ -318,7 +313,7 @@ main(int argc, char **argv) } args.idx = n; - load = load_file(mrb, &args); + mrb_value load = load_file(mrb, &args); if (mrb_nil_p(load)) { cleanup(mrb, &args); return EXIT_FAILURE; @@ -345,7 +340,7 @@ main(int argc, char **argv) fputs("Output file is required\n", stderr); return EXIT_FAILURE; } - result = dump_file(mrb, wfp, args.outfile, mrb_proc_ptr(load), &args); + int result = dump_file(mrb, wfp, args.outfile, mrb_proc_ptr(load), &args); fclose(wfp); cleanup(mrb, &args); if (result != MRB_DUMP_OK) { diff --git a/mrbgems/mruby-bin-mrbc/tools/mrbc/stub.c b/mrbgems/mruby-bin-mrbc/tools/mrbc/stub.c index ba6a4bd1d..a0c982230 100644 --- a/mrbgems/mruby-bin-mrbc/tools/mrbc/stub.c +++ b/mrbgems/mruby-bin-mrbc/tools/mrbc/stub.c @@ -94,3 +94,11 @@ void mrb_gc_free_set(mrb_state *mrb, struct RBasic *obj) /* stub for mrbc */ } #endif + +#ifdef MRB_USE_TASK_SCHEDULER +void mrb_task_mark_all(mrb_state *mrb) +{ + /* stub for mrbc */ + (void)mrb; +} +#endif diff --git a/mrbgems/mruby-bin-mruby/mrbgem.rake b/mrbgems/mruby-bin-mruby/mrbgem.rake index 96daecfce..ba7fad1fa 100644 --- a/mrbgems/mruby-bin-mruby/mrbgem.rake +++ b/mrbgems/mruby-bin-mruby/mrbgem.rake @@ -4,8 +4,4 @@ MRuby::Gem::Specification.new('mruby-bin-mruby') do |spec| spec.summary = 'mruby command' spec.bins = %w(mruby) spec.add_dependency('mruby-compiler', :core => 'mruby-compiler') - - if build.cxx_exception_enabled? - build.compile_as_cxx("#{spec.dir}/tools/mruby/mruby.c") - end end diff --git a/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c b/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c index 253410165..b5201d1ea 100644 --- a/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c +++ b/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c @@ -282,8 +282,9 @@ main(int argc, char **argv) mrb_value ARGV; mrb_value v; - if (mrb == NULL) { - fprintf(stderr, "%s: Invalid mrb_state, exiting mruby\n", *argv); + if (MRB_OPEN_FAILURE(mrb)) { + mrb_print_error(mrb); /* handles NULL */ + mrb_close(mrb); /* handles NULL */ return EXIT_FAILURE; } diff --git a/mrbgems/mruby-binding/src/binding.c b/mrbgems/mruby-binding/src/binding.c index 34e089248..d5e3a0424 100644 --- a/mrbgems/mruby-binding/src/binding.c +++ b/mrbgems/mruby-binding/src/binding.c @@ -184,29 +184,34 @@ binding_initialize_copy(mrb_state *mrb, mrb_value binding) return binding; } +static mrb_noreturn void +badname_error(mrb_state *mrb, mrb_sym id) +{ + mrb_raisef(mrb, E_NAME_ERROR, "wrong local variable name %!n for binding", id); +} + static void binding_local_variable_name_check(mrb_state *mrb, mrb_sym id) { if (id == 0) { - badname: - mrb_raisef(mrb, E_NAME_ERROR, "wrong local variable name %!n for binding", id); + badname_error(mrb, id); } mrb_int len; const char *name = mrb_sym_name_len(mrb, id, &len); if (len == 0) { - goto badname; + badname_error(mrb, id); } if (ISASCII(*name) && !(*name == '_' || ISLOWER(*name))) { - goto badname; + badname_error(mrb, id); } len--; name++; for (; len > 0; len--, name++) { if (ISASCII(*name) && !(*name == '_' || ISALNUM(*name))) { - goto badname; + badname_error(mrb, id); } } } @@ -464,6 +469,12 @@ mrb_binding_new(mrb_state *mrb, const struct RProc *proc, mrb_value recv, struct * b = get_binding("hello") * b.eval("param") #=> "hello" */ +static mrb_noreturn void +caller_error(mrb_state *mrb) +{ + mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot create Binding object for non-Ruby caller"); +} + static mrb_value mrb_f_binding(mrb_state *mrb, mrb_value self) { @@ -471,12 +482,11 @@ mrb_f_binding(mrb_state *mrb, mrb_value self) struct REnv *env; if (mrb->c->ci->cci != 0) { - caller_err: - mrb_raise(mrb, E_RUNTIME_ERROR, "Cannot create Binding object for non-Ruby caller"); + caller_error(mrb); } proc = (struct RProc*)mrb_proc_get_caller(mrb, &env); if (!env || MRB_PROC_CFUNC_P(proc)) { - goto caller_err; + caller_error(mrb); } return mrb_binding_new(mrb, proc, self, env); } diff --git a/mrbgems/mruby-catch/mrblib/catch.rb b/mrbgems/mruby-catch/mrblib/catch.rb index 9a60a67a3..bfcfbc20c 100644 --- a/mrbgems/mruby-catch/mrblib/catch.rb +++ b/mrbgems/mruby-catch/mrblib/catch.rb @@ -1,5 +1,26 @@ +# +# Exception raised when a throw is executed without a corresponding catch. +# This error contains the tag and value that were thrown. +# class UncaughtThrowError < ArgumentError - attr_reader :tag, :value + # The tag that was thrown + attr_reader :tag + # The value that was thrown with the tag + attr_reader :value + + # + # call-seq: + # UncaughtThrowError.new(tag, value) -> exception + # + # Creates a new UncaughtThrowError with the given tag and value. + # The tag is the symbol or object that was thrown, and value is + # the associated value. + # + # error = UncaughtThrowError.new(:done, "finished") + # error.tag #=> :done + # error.value #=> "finished" + # error.message #=> "uncaught throw :done" + # def initialize(tag, value) @tag = tag @value = value diff --git a/mrbgems/mruby-catch/src/catch.c b/mrbgems/mruby-catch/src/catch.c index 137755a15..747d2401b 100644 --- a/mrbgems/mruby-catch/src/catch.c +++ b/mrbgems/mruby-catch/src/catch.c @@ -6,11 +6,17 @@ #include #include +/* Pre-defined symbols used by catch implementation */ MRB_PRESYM_DEFINE_VAR_AND_INITER(catch_syms, 3, MRB_SYM(Object), MRB_SYM(new), MRB_SYM(call)) + /* + * Bytecode implementation of catch method: * def catch(r1 = Object.new, &r2) * r2.call(r1) * end + * + * This creates a default tag (Object.new) if none provided, then calls + * the block with the tag as argument. */ static const mrb_code catch_iseq[] = { OP_ENTER, 0x00, 0x20, 0x01, // 000 ENTER 0:1:0:0:0:0:1 (0x2001) @@ -29,6 +35,8 @@ static const mrb_code catch_iseq[] = { OP_SEND, 0x02, 0x02, 0x01, // 023 SEND R2 :call n=1 OP_RETURN, 0x02, // 027 RETURN R2 }; + +/* Instruction representation for catch method bytecode */ static const mrb_irep catch_irep = { 3,5,0, MRB_IREP_STATIC,catch_iseq, @@ -37,12 +45,15 @@ static const mrb_irep catch_irep = { NULL, sizeof(catch_iseq),0,3,0,0 }; + +/* Procedure object for catch method - used to identify catch blocks in call stack */ mrb_alignas(8) static const struct RProc catch_proc = { NULL, NULL, MRB_TT_PROC, MRB_GC_RED, MRB_OBJ_IS_FROZEN, MRB_PROC_SCOPE | MRB_PROC_STRICT, { &catch_irep }, NULL, { NULL } }; +/* Helper function to find a matching catch block in the call stack */ static size_t find_catcher(mrb_state *mrb, mrb_value tag) { @@ -59,6 +70,30 @@ find_catcher(mrb_state *mrb, mrb_value tag) return 0; } +/* + * call-seq: + * throw(tag) -> obj + * throw(tag, obj) -> obj + * + * Transfers control to the end of the active catch block waiting for tag. + * Raises UncaughtThrowError if there is no catch block for the tag. The + * optional second parameter supplies a return value for the catch block, + * which otherwise defaults to nil. + * + * def routine(n) + * puts n + * throw :done if n <= 0 + * routine(n-1) + * end + * + * catch(:done) { routine(3) } + * 3 + * 2 + * 1 + * 0 + * + * catch(:done) { throw :done, "hello" } #=> "hello" + */ static mrb_value throw_m(mrb_state *mrb, mrb_value self) { @@ -80,6 +115,17 @@ throw_m(mrb_state *mrb, mrb_value self) return mrb_nil_value(); } +/* + * Initializes the mruby-catch gem by defining catch and throw methods. + * + * - catch: defined using the pre-compiled bytecode procedure for efficiency, + * marked as private method in Kernel module + * - throw: defined as a regular C method that searches for matching catch blocks, + * also marked as private method in Kernel module + * + * Both methods are added to the Kernel module, making them available globally + * as private methods that can be called without a receiver. + */ void mrb_mruby_catch_gem_init(mrb_state *mrb) { @@ -93,6 +139,10 @@ mrb_mruby_catch_gem_init(mrb_state *mrb) mrb_define_private_method_id(mrb, mrb->kernel_module, MRB_SYM(throw), throw_m, MRB_ARGS_ARG(1,1)); } +/* + * Finalizes the mruby-catch gem. Currently no cleanup is required + * as the catch/throw implementation uses static data structures. + */ void mrb_mruby_catch_gem_final(mrb_state *mrb) { diff --git a/mrbgems/mruby-class-ext/src/class.c b/mrbgems/mruby-class-ext/src/class.c index 0f3a57e5b..d68c3fe10 100644 --- a/mrbgems/mruby-class-ext/src/class.c +++ b/mrbgems/mruby-class-ext/src/class.c @@ -214,7 +214,8 @@ is_ancestor(struct RClass *klass, struct RClass *super) static mrb_value mod_compare_hierarchy(mrb_state *mrb, mrb_value self, mrb_value other) { - if (!mrb_class_p(other) && !mrb_module_p(other) && !mrb_iclass_p(other)) { + if ((!mrb_class_p(self) && !mrb_module_p(self) && !mrb_iclass_p(self)) || + (!mrb_class_p(other) && !mrb_module_p(other) && !mrb_iclass_p(other))) { mrb_raise(mrb, E_TYPE_ERROR, "compared with non class/module"); } diff --git a/mrbgems/mruby-cmath/src/cmath.c b/mrbgems/mruby-cmath/src/cmath.c index 684de5bc7..b0c6675db 100644 --- a/mrbgems/mruby-cmath/src/cmath.c +++ b/mrbgems/mruby-cmath/src/cmath.c @@ -10,6 +10,7 @@ */ #include +#include #ifdef MRB_NO_FLOAT # error CMath conflicts with 'MRB_NO_FLOAT' configuration @@ -145,9 +146,9 @@ cmath_ ## name(mrb_state *mrb, mrb_value self)\ * call-seq: * CMath.exp(z) -> numeric * - * Returns the exponential of +z+. - * If +z+ is a complex number, returns a complex result. - * If +z+ is real and positive, returns a float. + * Returns the exponential of `z`. + * If `z` is a complex number, returns a complex result. + * If `z` is real and positive, returns a float. * * CMath.exp(1) #=> 2.718281828459045 * CMath.exp(1+1i) #=> (1.4686939399158851+2.2873552871788423i) @@ -159,8 +160,8 @@ DEF_CMATH_METHOD(exp) * CMath.log(z) -> numeric * CMath.log(z, base) -> numeric * - * Returns the natural logarithm of +z+. - * If a second argument +base+ is given, returns the logarithm of +z+ to the given base. + * Returns the natural logarithm of `z`. + * If a second argument `base` is given, returns the logarithm of `z` to the given base. * Has a branch cut along the negative real axis. * * CMath.log(1) #=> 0.0 @@ -194,7 +195,7 @@ cmath_log(mrb_state *mrb, mrb_value self) { * call-seq: * CMath.log10(z) -> numeric * - * Returns the base-10 logarithm of +z+. + * Returns the base-10 logarithm of `z`. * Has a branch cut along the negative real axis. * * CMath.log10(100) #=> 2.0 @@ -216,7 +217,7 @@ cmath_log10(mrb_state *mrb, mrb_value self) { * call-seq: * CMath.log2(z) -> numeric * - * Returns the base-2 logarithm of +z+. + * Returns the base-2 logarithm of `z`. * Has a branch cut along the negative real axis. * * CMath.log2(8) #=> 3.0 @@ -238,7 +239,7 @@ cmath_log2(mrb_state *mrb, mrb_value self) { * call-seq: * CMath.sqrt(z) -> numeric * - * Returns the square root of +z+. + * Returns the square root of `z`. * Has a branch cut along the negative real axis. * * CMath.sqrt(4) #=> 2.0 @@ -260,7 +261,7 @@ cmath_sqrt(mrb_state *mrb, mrb_value self) { * call-seq: * CMath.sin(z) -> numeric * - * Returns the sine of +z+. + * Returns the sine of `z`. * * CMath.sin(0) #=> 0.0 * CMath.sin(1i) #=> (0.0+1.1752011936438014i) @@ -271,7 +272,7 @@ DEF_CMATH_METHOD(sin) * call-seq: * CMath.cos(z) -> numeric * - * Returns the cosine of +z+. + * Returns the cosine of `z`. * * CMath.cos(0) #=> 1.0 * CMath.cos(1i) #=> (1.5430806348152437+0.0i) @@ -282,7 +283,7 @@ DEF_CMATH_METHOD(cos) * call-seq: * CMath.tan(z) -> numeric * - * Returns the tangent of +z+. + * Returns the tangent of `z`. * * CMath.tan(0) #=> 0.0 * CMath.tan(1i) #=> (0.0+0.7615941559557649i) @@ -292,7 +293,7 @@ DEF_CMATH_METHOD(tan) * call-seq: * CMath.asin(z) -> numeric * - * Returns the arc sine of +z+. + * Returns the arc sine of `z`. * * CMath.asin(0) #=> 0.0 * CMath.asin(2) #=> (1.5707963267948966-1.3169578969248166i) @@ -303,7 +304,7 @@ DEF_CMATH_METHOD(asin) * call-seq: * CMath.acos(z) -> numeric * - * Returns the arc cosine of +z+. + * Returns the arc cosine of `z`. * * CMath.acos(1) #=> 0.0 * CMath.acos(2) #=> (0.0+1.3169578969248166i) @@ -314,7 +315,7 @@ DEF_CMATH_METHOD(acos) * call-seq: * CMath.atan(z) -> numeric * - * Returns the arc tangent of +z+. + * Returns the arc tangent of `z`. * * CMath.atan(0) #=> 0.0 * CMath.atan(1i) #=> (0.0+Infinity*i) @@ -324,7 +325,7 @@ DEF_CMATH_METHOD(atan) * call-seq: * CMath.sinh(z) -> numeric * - * Returns the hyperbolic sine of +z+. + * Returns the hyperbolic sine of `z`. * * CMath.sinh(0) #=> 0.0 * CMath.sinh(1i) #=> (0.0+0.8414709848078965i) @@ -335,7 +336,7 @@ DEF_CMATH_METHOD(sinh) * call-seq: * CMath.cosh(z) -> numeric * - * Returns the hyperbolic cosine of +z+. + * Returns the hyperbolic cosine of `z`. * * CMath.cosh(0) #=> 1.0 * CMath.cosh(1i) #=> (0.5403023058681398+0.0i) @@ -346,7 +347,7 @@ DEF_CMATH_METHOD(cosh) * call-seq: * CMath.tanh(z) -> numeric * - * Returns the hyperbolic tangent of +z+. + * Returns the hyperbolic tangent of `z`. * * CMath.tanh(0) #=> 0.0 * CMath.tanh(1i) #=> (0.0+1.557407724654902i) @@ -356,7 +357,7 @@ DEF_CMATH_METHOD(tanh) * call-seq: * CMath.asinh(z) -> numeric * - * Returns the inverse hyperbolic sine of +z+. + * Returns the inverse hyperbolic sine of `z`. * * CMath.asinh(0) #=> 0.0 * CMath.asinh(1i) #=> (0.0+1.5707963267948966i) @@ -367,7 +368,7 @@ DEF_CMATH_METHOD(asinh) * call-seq: * CMath.acosh(z) -> numeric * - * Returns the inverse hyperbolic cosine of +z+. + * Returns the inverse hyperbolic cosine of `z`. * Has a branch cut at values less than 1. * * CMath.acosh(1) #=> 0.0 @@ -379,7 +380,7 @@ DEF_CMATH_METHOD(acosh) * call-seq: * CMath.atanh(z) -> numeric * - * Returns the inverse hyperbolic tangent of +z+. + * Returns the inverse hyperbolic tangent of `z`. * Has branch cuts at values less than -1 and greater than 1. * * CMath.atanh(0) #=> 0.0 @@ -392,32 +393,31 @@ DEF_CMATH_METHOD(atanh) void mrb_mruby_cmath_gem_init(mrb_state* mrb) { - struct RClass *cmath; - cmath = mrb_define_module(mrb, "CMath"); + struct RClass *cmath = mrb_define_module_id(mrb, MRB_SYM(CMath)); mrb_include_module(mrb, cmath, mrb_module_get(mrb, "Math")); - mrb_define_module_function(mrb, cmath, "sin", cmath_sin, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "cos", cmath_cos, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "tan", cmath_tan, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(sin), cmath_sin, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(cos), cmath_cos, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(tan), cmath_tan, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "asin", cmath_asin, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "acos", cmath_acos, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "atan", cmath_atan, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(asin), cmath_asin, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(acos), cmath_acos, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(atan), cmath_atan, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "sinh", cmath_sinh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "cosh", cmath_cosh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "tanh", cmath_tanh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(sinh), cmath_sinh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(cosh), cmath_cosh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(tanh), cmath_tanh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "asinh", cmath_asinh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "acosh", cmath_acosh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "atanh", cmath_atanh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(asinh), cmath_asinh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(acosh), cmath_acosh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(atanh), cmath_atanh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "exp", cmath_exp, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "log", cmath_log, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); - mrb_define_module_function(mrb, cmath, "log2", cmath_log2, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "log10", cmath_log10, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, cmath, "sqrt", cmath_sqrt, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(exp), cmath_exp, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(log), cmath_log, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(log2), cmath_log2, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(log10), cmath_log10, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, cmath, MRB_SYM(sqrt), cmath_sqrt, MRB_ARGS_REQ(1)); } void diff --git a/mrbgems/mruby-compar-ext/mrblib/compar.rb b/mrbgems/mruby-compar-ext/mrblib/compar.rb index ba17d84c8..4a82cd448 100644 --- a/mrbgems/mruby-compar-ext/mrblib/compar.rb +++ b/mrbgems/mruby-compar-ext/mrblib/compar.rb @@ -4,9 +4,9 @@ module Comparable # obj.clamp(min, max) -> obj # obj.clamp(range) -> obj # - # In (min, max) form, returns _min_ if _obj_ - # <=> _min_ is less than zero, _max_ if _obj_ - # <=> _max_ is greater than zero, and _obj_ + # In `(min, max)` form, returns _min_ if _obj_ + # `<=>` _min_ is less than zero, _max_ if _obj_ + # `<=>` _max_ is greater than zero, and _obj_ # otherwise. # # 12.clamp(0, 100) #=> 12 @@ -16,9 +16,9 @@ module Comparable # 'd'.clamp('a', 'f') #=> 'd' # 'z'.clamp('a', 'f') #=> 'f' # - # In (range) form, returns _range.begin_ if _obj_ - # <=> _range.begin_ is less than zero, _range.end_ - # if _obj_ <=> _range.end_ is greater than zero, and + # In `(range)` form, returns _range.begin_ if _obj_ + # `<=>` _range.begin_ is less than zero, _range.end_ + # if _obj_ `<=>` _range.end_ is greater than zero, and # _obj_ otherwise. # # 12.clamp(0..100) #=> 12 @@ -28,14 +28,14 @@ module Comparable # 'd'.clamp('a'..'f') #=> 'd' # 'z'.clamp('a'..'f') #=> 'f' # - # If _range.begin_ is +nil+, it is considered smaller than _obj_, - # and if _range.end_ is +nil+, it is considered greater than + # If _range.begin_ is `nil`, it is considered smaller than _obj_, + # and if _range.end_ is `nil`, it is considered greater than # _obj_. # # -20.clamp(0..) #=> 0 # 523.clamp(..100) #=> 100 # - # When _range.end_ is excluded and not +nil+, an exception is + # When _range.end_ is excluded and not `nil`, an exception is # raised. # # 100.clamp(0...100) # ArgumentError diff --git a/mrbgems/mruby-compiler/core/codegen.c b/mrbgems/mruby-compiler/core/codegen.c index 10945854f..1b42c2330 100644 --- a/mrbgems/mruby-compiler/core/codegen.c +++ b/mrbgems/mruby-compiler/core/codegen.c @@ -68,6 +68,12 @@ /* Maximum number of arguments for some opcodes like OP_SUPER or OP_ARGARY. */ #define MAXARG_S (1<<16) +/* Macro to detect (0 . 0) separators in literal arrays */ +#define IS_LITERAL_DELIM(node) \ + ((node) && (node)->car && \ + (node)->car->car == NULL && \ + (node)->car->cdr == NULL) + typedef mrb_ast_node node; typedef struct mrb_parser_state parser_state; @@ -147,12 +153,18 @@ static void loop_pop(codegen_scope *s, int val); static int catch_handler_new(codegen_scope *s); static void catch_handler_set(codegen_scope *s, int ent, enum mrb_catch_type type, uint32_t begin, uint32_t end, uint32_t target); -static void gen_assignment(codegen_scope *s, node *tree, node *rhs, int sp, int val); static void gen_massignment(codegen_scope *s, node *tree, int sp, int val); +static void codegen_masgn(codegen_scope *s, node *varnode, node *rhs, int sp, int val); +static void gen_assignment(codegen_scope *s, node *tree, node *rhs, int sp, int val); +static void codegen_call_assign(codegen_scope *s, node *varnode, node *rhs, int sp, int val); static void codegen(codegen_scope *s, node *tree, int val); static void raise_error(codegen_scope *s, const char *msg); +/* Forward declarations for helper functions */ +static enum node_type get_node_type(node *n); +static struct mrb_ast_var_header* get_var_header(node *n); + /* * Reports a compilation error encountered during code generation. * @@ -1201,6 +1213,7 @@ get_int_operand(codegen_scope *s, struct mrb_insn_data *data, mrb_int *n) static int new_lit_str2(codegen_scope *s, const char *str1, mrb_int len1, const char *str2, mrb_int len2); static int find_pool_str(codegen_scope *s, const char *str1, mrb_int len1, const char *str2, mrb_int len2); +static void gen_string(codegen_scope *s, node *list, int val); /* * Reallocates or allocates memory for a string literal in the IREP's literal pool. @@ -1708,6 +1721,41 @@ lit_pool_extend(codegen_scope *s) return &s->pool[s->irep->plen++]; } +/* Helper functions for simple load operations that follow the pattern: + * if (!val) return; ; genop_X(...); push(); */ +static void +gen_load_op1(codegen_scope *s, mrb_code op, int val) +{ + if (!val) return; + genop_1(s, op, cursp()); + push(); +} + +static void +gen_load_op2(codegen_scope *s, mrb_code op, uint16_t arg, int val) +{ + if (!val) return; + genop_2(s, op, cursp(), arg); + push(); +} + +/* Helper function for conditional nil loading - loads nil only if val is needed */ +static void +gen_load_nil(codegen_scope *s, int val) +{ + if (!val) return; + genop_1(s, OP_LOADNIL, cursp()); + push(); +} + +/* Helper function for loading literal and pushing */ +static void +gen_load_lit(codegen_scope *s, int off) +{ + genop_2(s, OP_LOADL, cursp(), off); + push(); +} + /* * Adds a big integer literal (BigInt) to the IREP's literal pool. * The BigInt is provided as a string `p` in the given `base`. @@ -2155,6 +2203,11 @@ node_len(node *tree) { int n = 0; + /* Validate pointer before using it */ + if (!tree || ((uintptr_t)tree < 0x1000)) { + return 0; + } + while (tree) { n++; tree = tree->cdr; @@ -2163,14 +2216,14 @@ node_len(node *tree) } /* Casts a void* (typically from an AST node part) to an int. */ -#define nint(x) ((int)(intptr_t)(x)) +#define node_to_sym(x) ((mrb_sym)(intptr_t)(x)) +#define node_to_int(x) ((int)(intptr_t)(x)) /* Casts a void* (typically from an AST node part) to a char. */ -#define nchar(x) ((char)(intptr_t)(x)) +#define node_to_char(x) ((char)(intptr_t)(x)) /* Casts a void* (typically from an AST node part) to an mrb_sym. */ -#define nsym(x) ((mrb_sym)(intptr_t)(x)) /* Extracts the symbol (name) of a local variable from its AST node representation. */ -#define lv_name(lv) nsym((lv)->car) +#define lv_name(lv) node_to_sym((lv)->car) /* * Searches for a local variable `id` within the current scope's local variable list (`s->lv`). @@ -2248,80 +2301,6 @@ search_upvar(codegen_scope *s, mrb_sym id, int *idx) return -1; /* not reached */ } -/* - * Generates the bytecode for a `for` loop. - * - * A `for` loop in mruby, like `for x in collection`, is typically syntactic sugar for - * `collection.each { |x| ... }`. This function implements that transformation. - * - * The process involves: - * 1. Generating code for the `collection` (the receiver of the `each` call). - * 2. Creating a new scope for the block that will be passed to `each`. - * 3. Inside this new block scope: - * a. Emitting `OP_ENTER` to set up the block's argument handling. - * The argument specification `0x40000` likely indicates a block that - * takes one mandatory argument. - * b. Generating code to assign the iterated item (passed as a block argument) - * to the loop variable(s) specified in `tree->car`. This can be a simple - * assignment or a multiple assignment (destructuring). - * c. Setting up a `LOOP_FOR` context for handling `break`/`next`/`redo` within the loop. - * d. Generating code for the actual body of the `for` loop (`tree->cdr->cdr->car`). - * e. Emitting `OP_RETURN` for the block's implicit return. - * 4. Finalizing the block scope and obtaining its `mrb_irep`. - * 5. Back in the original scope, generating `OP_BLOCK` to create a closure from the - * block's `mrb_irep`. - * 6. Generating `OP_SENDB` to call the `each` method (by symbol) on the collection, - * passing the newly created block. - * - * @param s The current code generation scope. - * @param tree The AST node representing the `for` loop. - * `tree->car` contains the loop variable(s). - * `tree->cdr->car` is the collection being iterated over. - * `tree->cdr->cdr->car` is the body of the loop. - */ -static void -for_body(codegen_scope *s, node *tree) -{ - codegen_scope *prev = s; - int idx; - struct loopinfo *lp; - node *n2; - - /* generate receiver */ - codegen(s, tree->cdr->car, VAL); - /* generate loop-block */ - s = scope_new(s->mrb, s, NULL); - - push(); /* push for a block parameter */ - - /* generate loop variable */ - n2 = tree->car; - genop_W(s, OP_ENTER, 0x40000); - if (n2->car && !n2->car->cdr && !n2->cdr) { - gen_assignment(s, n2->car->car, NULL, 1, NOVAL); - } - else { - gen_massignment(s, n2, 1, VAL); - } - /* construct loop */ - lp = loop_push(s, LOOP_FOR); - lp->pc1 = new_label(s); - genop_0(s, OP_NOP); /* for redo */ - - /* loop body */ - codegen(s, tree->cdr->cdr->car, VAL); - pop(); - gen_return(s, OP_RETURN, cursp()); - loop_pop(s, NOVAL); - scope_finish(s); - s = prev; - genop_2(s, OP_BLOCK, cursp(), s->irep->rlen-1); - push();pop(); /* space for a block */ - pop(); - idx = new_sym(s, MRB_SYM_2(s->mrb, each)); - genop_3(s, OP_SENDB, cursp(), idx, 0); -} - /* * Generates the bytecode for the body of a lambda or a block. * This function is responsible for creating a new scope, handling arguments @@ -2337,11 +2316,11 @@ for_body(codegen_scope *s, node *tree) * @return The index of the newly created `mrb_irep` in the parent scope's `reps` array. */ static int -lambda_body(codegen_scope *s, node *tree, int blk) +lambda_body(codegen_scope *s, node *locals, struct mrb_ast_args *args, node *body, int blk) { codegen_scope *parent = s; /* Create a new scope for the lambda/block body. */ - s = scope_new(s->mrb, s, tree->car); + s = scope_new(s->mrb, s, locals); /* `mscope` is false for blocks, true for lambdas/methods. */ s->mscope = !blk; @@ -2351,10 +2330,9 @@ lambda_body(codegen_scope *s, node *tree, int blk) struct loopinfo *lp = loop_push(s, LOOP_BLOCK); lp->pc0 = new_label(s); /* Mark entry point for potential retry/redo. */ } - tree = tree->cdr; /* Argument processing */ - if (tree->car == NULL) { /* No arguments */ + if (args == NULL) { /* No arguments */ genop_W(s, OP_ENTER, 0); /* Generate OP_ENTER with no argument specification. */ s->ainfo = 0; } @@ -2364,26 +2342,25 @@ lambda_body(codegen_scope *s, node *tree, int blk) uint32_t pos; node *opt; node *margs, *pargs; - node *tail; + + /* args is already struct mrb_ast_args * */ /* mandatory arguments */ - ma = node_len(tree->car->car); - margs = tree->car->car; - tail = tree->car->cdr->cdr->cdr->cdr; + ma = node_len(args->mandatory_args); + margs = args->mandatory_args; /* optional arguments */ - oa = node_len(tree->car->cdr->car); + oa = node_len(args->optional_args); /* rest argument? */ - ra = tree->car->cdr->cdr->car ? 1 : 0; + ra = args->rest_arg ? 1 : 0; /* mandatory arguments after rest argument */ - pa = node_len(tree->car->cdr->cdr->cdr->car); - pargs = tree->car->cdr->cdr->cdr->car; + pa = node_len(args->post_mandatory_args); + pargs = args->post_mandatory_args; + /* keyword arguments */ - ka = tail ? node_len(tail->cdr->car) : 0; - /* keyword dictionary? */ - kd = tail && tail->cdr->cdr->car? 1 : 0; - /* block argument? */ - ba = tail && tail->cdr->cdr->cdr->car ? 1 : 0; + ka = args->keyword_args ? node_len(args->keyword_args) : 0; + kd = args->kwrest_arg ? 1 : 0; + ba = args->block_arg ? 1 : 0; if (ma > 0x1f || oa > 0x1f || pa > 0x1f || ka > 0x1f) { codegen_error(s, "too many formal arguments"); @@ -2396,11 +2373,12 @@ lambda_body(codegen_scope *s, node *tree, int blk) | MRB_ARGS_KEY(ka, kd) | (ba ? MRB_ARGS_BLOCK() : 0); genop_W(s, OP_ENTER, a); - /* (12bits = 5:1:5:1) - Store argument counts for block argument passing (OP_BLKPUSH) */ + /* (13bits = 6:1:5:1:1) - Store argument counts for block argument passing (OP_BLKPUSH) */ s->ainfo = (((ma+oa) & 0x3f) << 7) | ((ra & 0x1) << 6) | ((pa & 0x1f) << 1) - | (ka || kd); + | (ka || kd) + | ((ba & 0x1) << 13); /* Optional argument default value initialization */ pos = new_label(s); /* Start of the optional argument jump table. */ @@ -2411,11 +2389,11 @@ lambda_body(codegen_scope *s, node *tree, int blk) if (oa > 0) { genjmp_0(s, OP_JMP); /* Jump to skip all default assignments if all optional args are provided. */ } - opt = tree->car->cdr->car; /* AST node for optional arguments. */ + opt = args->optional_args; /* AST node for optional arguments. */ i = 0; while (opt) { /* Iterate through optional arguments. */ int idx; - mrb_sym id = nsym(opt->car->car); /* Symbol of the optional argument. */ + mrb_sym id = node_to_sym(opt->car->car); /* Symbol of the optional argument. */ dispatch(s, pos+i*3+1); /* Patch the jump to this argument's default value code. */ codegen(s, opt->car->cdr, VAL); /* Generate code for the default value expression. */ @@ -2435,22 +2413,17 @@ lambda_body(codegen_scope *s, node *tree, int blk) } /* Keyword argument processing */ - if (tail) { /* `tail` contains keyword arguments and block argument */ - node *kwds = tail->cdr->car; /* AST node for keyword arguments. */ - int kwrest = 0; /* Flag for keyword rest argument (e.g., **kwargs) */ + if (ka > 0 || kd > 0) { /* Has keyword arguments or keyword rest */ + node *kwds; + int kwrest = kd; /* Flag for keyword rest argument (e.g., **kwargs) */ - if (tail->cdr->cdr->car) { /* Check if a keyword rest argument exists. */ - kwrest = 1; - } - mrb_assert(nint(tail->car) == NODE_ARGS_TAIL); - mrb_assert(node_len(tail) == 4); + kwds = args->keyword_args; while (kwds) { int jmpif_key_p, jmp_def_set = -1; - node *kwd = kwds->car, *def_arg = kwd->cdr->cdr->car; - mrb_sym kwd_sym = nsym(kwd->cdr->car); - - mrb_assert(nint(kwd->car) == NODE_KW_ARG); + node *kwd = kwds->car; + mrb_sym kwd_sym = node_to_sym(kwd->car); /* Direct access to key */ + node *def_arg = kwd->cdr; /* Direct access to value */ if (def_arg) { int idx; @@ -2476,17 +2449,60 @@ lambda_body(codegen_scope *s, node *tree, int blk) kwds = kwds->cdr; } - if (tail->cdr->car && !kwrest) { /* If there are keyword args but no keyword rest. */ + /* Check if there are keyword args but no keyword rest */ + int has_keywords = args->keyword_args != NULL; + + if (has_keywords && !kwrest) { /* If there are keyword args but no keyword rest. */ genop_0(s, OP_KEYEND); /* Signal end of keyword arguments. */ - } - /* Block argument processing */ - if (ba) { /* If a block argument (e.g., &blk) is present. */ - mrb_sym bparam = nsym(tail->cdr->cdr->cdr->car); /* Symbol of the block parameter. */ - pos = ma+oa+ra+pa+(ka||kd); /* Calculate register offset for the block parameter. */ - if (bparam) { /* If it's a named block parameter. */ - int idx = lv_idx(s, bparam); - genop_2(s, OP_MOVE, idx, pos+1); /* Move the block from its argument slot to the local variable. */ + + /* Reconstruct keyword hash for super to use */ + /* After KEYEND, the hash at kw_pos is empty (all keys deleted by KARG) */ + /* Build a fresh hash from the extracted keyword local variables */ + int kw_dict_pos = ma + oa + ra + pa + 1; + int sp_save = cursp(); + + /* Load key-value pairs for each keyword argument starting at stack position */ + node *kw_list = args->keyword_args; + int num_pairs = 0; + while (kw_list) { + node *kw = kw_list->car; + mrb_sym kw_sym = node_to_sym(kw->car); + + /* Load symbol (key) */ + genop_2(s, OP_LOADSYM, cursp(), new_sym(s, kw_sym)); + push(); + + /* Load keyword local variable value */ + genop_2(s, OP_MOVE, cursp(), lv_idx(s, kw_sym)); + push(); + + num_pairs++; + kw_list = kw_list->cdr; } + + /* Create hash at current stack position, then move to keyword dict position */ + if (num_pairs > 0) { + genop_2(s, OP_HASH, sp_save, num_pairs); + genop_2(s, OP_MOVE, kw_dict_pos, sp_save); + } + else { + /* No keyword args, create empty hash */ + genop_2(s, OP_HASH, sp_save, 0); + genop_2(s, OP_MOVE, kw_dict_pos, sp_save); + } + + /* Restore stack pointer */ + s->sp = sp_save; + } + } + + /* Block argument processing */ + if (ba) { /* If a block argument (e.g., &blk) is present. */ + mrb_sym bparam = args->block_arg; + pos = ma+oa+ra+pa+(ka||kd); /* Calculate register offset for the block parameter. */ + if (bparam) { /* If it's a named block parameter. */ + int idx = lv_idx(s, bparam); + genop_2(s, OP_MOVE, idx, pos+1); /* Move the block from its argument slot to the local variable. */ } } @@ -2495,8 +2511,24 @@ lambda_body(codegen_scope *s, node *tree, int blk) node *n = margs; pos = 1; /* Start from register 1 (after self). */ while (n) { - if (nint(n->car->car) == NODE_MASGN) { /* If the argument is a mass assignment (e.g., |(a,b)| ). */ - gen_massignment(s, n->car->cdr->car, pos, NOVAL); + if (get_node_type(n->car) == NODE_MARG) { /* If the argument is a mass assignment (e.g., |(a,b)| ). */ + struct mrb_ast_masgn_node *masgn_n = (struct mrb_ast_masgn_node*)n->car; + /* Use dedicated parameter destructuring logic instead of general codegen_masgn */ + int nn = 0; + /* Handle pre variables */ + if (masgn_n->pre) { + node *pre = masgn_n->pre; + while (pre) { + int sp = cursp(); + genop_3(s, OP_AREF, sp, pos, nn); + push(); + gen_assignment(s, pre->car, NULL, sp, NOVAL); + pop(); + nn++; + pre = pre->cdr; + } + } + /* For now, only handle simple pre variables - rest/post would need more complex logic */ } pos++; n = n->cdr; @@ -2506,8 +2538,24 @@ lambda_body(codegen_scope *s, node *tree, int blk) node *n = pargs; pos = ma+oa+ra+1; /* Calculate starting register for post-mandatory args. */ while (n) { - if (nint(n->car->car) == NODE_MASGN) { /* If argument is a mass assignment. */ - gen_massignment(s, n->car->cdr->car, pos, NOVAL); + if (get_node_type(n->car) == NODE_MARG) { /* If argument is a mass assignment. */ + struct mrb_ast_masgn_node *masgn_n = (struct mrb_ast_masgn_node*)n->car; + /* Use dedicated parameter destructuring logic instead of general codegen_masgn */ + int nn = 0; + /* Handle pre variables */ + if (masgn_n->pre) { + node *pre = masgn_n->pre; + while (pre) { + int sp = cursp(); + genop_3(s, OP_AREF, sp, pos, nn); + push(); + gen_assignment(s, pre->car, NULL, sp, NOVAL); + pop(); + nn++; + pre = pre->cdr; + } + } + /* For now, only handle simple pre variables - rest/post would need more complex logic */ } pos++; n = n->cdr; @@ -2516,7 +2564,7 @@ lambda_body(codegen_scope *s, node *tree, int blk) } /* Generate code for the actual body of the lambda/block. */ - codegen(s, tree->cdr->car, VAL); + codegen(s, body, VAL); pop(); /* Pop the result of the body. */ /* Implicit return of the last evaluated expression. */ @@ -2549,13 +2597,13 @@ lambda_body(codegen_scope *s, node *tree, int blk) * Returns 0 if `s->irep` is NULL (should not happen in normal operation). */ static int -scope_body(codegen_scope *s, node *tree, int val) +scope_body(codegen_scope *s, node *locals, node *body, int val) { - /* Create a new scope, inheriting from `s`, with local variables from `tree->car`. */ - codegen_scope *scope = scope_new(s->mrb, s, tree->car); + /* Create a new scope, inheriting from `s`, with local variables from `locals`. */ + codegen_scope *scope = scope_new(s->mrb, s, locals); /* Generate code for the body of the scope. */ - codegen(scope, tree->cdr, VAL); + codegen(scope, body, VAL); /* Ensure the scope returns the value of its last expression. */ gen_return(scope, OP_RETURN, scope->sp-1); @@ -2575,11 +2623,39 @@ scope_body(codegen_scope *s, node *tree, int val) return s->irep->rlen - 1; } +/* Helper functions for node type checking - works with variable-sized nodes */ +static enum node_type +get_node_type(node *n) +{ + if (!n) return (enum node_type)0; + + /* Try to interpret as variable-sized node first */ + struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; + return (enum node_type)header->node_type; +} + +static struct mrb_ast_var_header* +get_var_header(node *n) +{ + if (!n) return NULL; + + /* Try to interpret as variable-sized node */ + struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; + return header; +} + +/* Helper to detect splat nodes in variable-sized format */ +static mrb_bool +is_splat_node(node *n) +{ + return (get_node_type(n) == NODE_SPLAT); +} + static mrb_bool nosplat(node *t) { while (t) { - if (nint(t->car->car) == NODE_SPLAT) return FALSE; + if (is_splat_node(t->car)) return FALSE; t = t->cdr; } return TRUE; @@ -2588,16 +2664,13 @@ nosplat(node *t) static mrb_sym attrsym(codegen_scope *s, mrb_sym a) { - const char *name; mrb_int len; - char *name2; - - name = mrb_sym_name_len(s->mrb, a, &len); - name2 = (char*)codegen_palloc(s, - (size_t)len - + 1 /* '=' */ - + 1 /* '\0' */ - ); + const char *name = mrb_sym_name_len(s->mrb, a, &len); + char *name2 = (char*)codegen_palloc(s, + (size_t)len + + 1 /* '=' */ + + 1 /* '\0' */ + ); mrb_assert_int_fit(mrb_int, len, size_t, SIZE_MAX); memcpy(name2, name, (size_t)len); name2[len] = '='; @@ -2633,7 +2706,45 @@ gen_values(codegen_scope *s, node *t, int val, int limit) } while (t) { - int is_splat = nint(t->car->car) == NODE_SPLAT; + int is_splat = is_splat_node(t->car); + + /* Optimization: skip or inline literal splat arrays + * - Empty splat (`*[]`/`*zarray`): contributes nothing; skip. + * - Non-empty literal array with no inner splat (`*[a,b]`): inline + * as normal positional args to avoid building/concatenating arrays. + */ + if (is_splat) { + struct mrb_ast_splat_node *splat = splat_node(t->car); + node *sv = splat->value; + if (sv) { + enum node_type nt = get_node_type(sv); + if (nt == NODE_ARRAY) { + struct mrb_ast_array_node *an = array_node(sv); + if (an->elements == NULL) { + /* empty splat; contributes nothing */ + t = t->cdr; + continue; + } + else if (nosplat(an->elements)) { + /* Inline non-empty literal array elements as regular args */ + node *e = an->elements; + while (e) { + /* Honor evaluation order */ + codegen(s, e->car, val); + n++; + e = e->cdr; + } + t = t->cdr; + continue; + } + } + else if (nt == NODE_ZARRAY) { + /* explicit empty array literal */ + t = t->cdr; + continue; + } + } + } if (is_splat || cursp() >= slimit) { /* flush stack */ pop_n(n); @@ -2692,7 +2803,7 @@ gen_hash(codegen_scope *s, node *tree, int val, int limit) mrb_bool first = TRUE; while (tree) { - if (nint(tree->car->car->car) == NODE_KW_REST_ARGS) { + if (node_to_sym(tree->car->car) == MRB_OPSYM_2(s->mrb, pow)) { if (val && first) { genop_2(s, OP_HASH, cursp(), 0); push(); @@ -2756,306 +2867,143 @@ gen_hash(codegen_scope *s, node *tree, int val, int limit) return len; } -static void -gen_call(codegen_scope *s, node *tree, int val, int safe) -{ - mrb_sym sym = nsym(tree->cdr->car); - int skip = 0, n = 0, nk = 0, noop = no_optimize(s), noself = 0, blk = 0, sp_save = cursp(); - enum mrb_insn opt_op = OP_NOP; - if (!noop) { - if (sym == MRB_OPSYM_2(s->mrb, add)) opt_op = OP_ADD; - else if (sym == MRB_OPSYM_2(s->mrb, sub)) opt_op = OP_SUB; - else if (sym == MRB_OPSYM_2(s->mrb, mul)) opt_op = OP_MUL; - else if (sym == MRB_OPSYM_2(s->mrb, div)) opt_op = OP_DIV; - else if (sym == MRB_OPSYM_2(s->mrb, lt)) opt_op = OP_LT; - else if (sym == MRB_OPSYM_2(s->mrb, le)) opt_op = OP_LE; - else if (sym == MRB_OPSYM_2(s->mrb, gt)) opt_op = OP_GT; - else if (sym == MRB_OPSYM_2(s->mrb, ge)) opt_op = OP_GE; - else if (sym == MRB_OPSYM_2(s->mrb, eq)) opt_op = OP_EQ; - else if (sym == MRB_OPSYM_2(s->mrb, aref)) opt_op = OP_GETIDX; - else if (sym == MRB_OPSYM_2(s->mrb, aset)) opt_op = OP_SETIDX; - } - if (!tree->car || (opt_op == OP_NOP && nint(tree->car->car) == NODE_SELF)) { - noself = 1; - push(); - } - else { - codegen(s, tree->car, VAL); /* receiver */ - } - if (safe) { - int recv = cursp()-1; - gen_move(s, cursp(), recv, 1); - skip = genjmp2_0(s, OP_JMPNIL, cursp(), val); - } - tree = tree->cdr->cdr->car; - if (tree) { - if (tree->car) { /* positional arguments */ - n = gen_values(s, tree->car, VAL, 14); - if (n < 0) { /* variable length */ - noop = 1; /* not operator */ - n = 15; - push(); - } - } - if (tree->cdr->car) { /* keyword arguments */ - noop = 1; - nk = gen_hash(s, tree->cdr->car->cdr, VAL, 14); - if (nk < 0) nk = 15; - } - } - if (tree && tree->cdr && tree->cdr->cdr) { - codegen(s, tree->cdr->cdr, VAL); +static void +gen_colon_assign_common(codegen_scope *s, node *rhs, int sp, int val, int idx, int final_op) +{ + if (rhs) { + codegen(s, rhs, VAL); pop(); - noop = 1; - blk = 1; + gen_move(s, sp, cursp(), 0); } + pop(); pop(); + genop_2(s, final_op, cursp(), idx); + if (val) push(); +} + +static void +gen_colon2_assign(codegen_scope *s, node *varnode, node *rhs, int sp, int val) +{ + struct mrb_ast_colon2_node *n = (struct mrb_ast_colon2_node*)varnode; + int idx; + + if (sp) { + gen_move(s, cursp(), sp, 0); + } + sp = cursp(); push(); - s->sp = sp_save; - if (opt_op == OP_ADD && n == 1) { - gen_addsub(s, OP_ADD, cursp()); + codegen(s, n->base, VAL); + idx = new_sym(s, n->name); + gen_colon_assign_common(s, rhs, sp, val, idx, OP_SETMCNST); +} + +static void +gen_colon3_assign(codegen_scope *s, node *varnode, node *rhs, int sp, int val) +{ + struct mrb_ast_colon3_node *n = (struct mrb_ast_colon3_node*)varnode; + int idx; + + if (sp) { + gen_move(s, cursp(), sp, 0); } - else if (opt_op == OP_SUB && n == 1) { - gen_addsub(s, OP_SUB, cursp()); - } - else if (opt_op == OP_MUL && n == 1) { - gen_muldiv(s, OP_MUL, cursp()); - } - else if (opt_op == OP_DIV && n == 1) { - gen_muldiv(s, OP_DIV, cursp()); - } - else if (opt_op == OP_LT && n == 1) { - genop_1(s, OP_LT, cursp()); - } - else if (opt_op == OP_LE && n == 1) { - genop_1(s, OP_LE, cursp()); - } - else if (opt_op == OP_GT && n == 1) { - genop_1(s, OP_GT, cursp()); - } - else if (opt_op == OP_GE && n == 1) { - genop_1(s, OP_GE, cursp()); - } - else if (opt_op == OP_EQ && n == 1) { - genop_1(s, OP_EQ, cursp()); - } - else if (opt_op == OP_SETIDX && n == 2) { - genop_1(s, OP_SETIDX, cursp()); - } - else if (!noop && n == 0 && gen_uniop(s, sym, cursp())) { - /* constant folding succeeded */ - } - else if (!noop && n == 1 && gen_binop(s, sym, cursp())) { - /* constant folding succeeded */ - } - else if (noself) { - genop_3(s, blk ? OP_SSENDB : OP_SSEND, cursp(), new_sym(s, sym), n|(nk<<4)); - } - else { - genop_3(s, blk ? OP_SENDB : OP_SEND, cursp(), new_sym(s, sym), n|(nk<<4)); - } - if (safe) { - dispatch(s, skip); - } - if (val) { - push(); + sp = cursp(); + push(); + genop_1(s, OP_OCLASS, cursp()); + push(); + idx = new_sym(s, n->name); + gen_colon_assign_common(s, rhs, sp, val, idx, OP_SETCONST); +} + +static void +gen_xvar_assignment(codegen_scope *s, node *tree, node *rhs, int sp, int val, uint8_t op) +{ + struct mrb_ast_var_node *var = (struct mrb_ast_var_node*)tree; + if (rhs) { + codegen(s, rhs, VAL); + pop(); + sp = cursp(); } + gen_setxv(s, op, sp, var->symbol, val); +} + +static void +gen_xvar(codegen_scope *s, mrb_sym sym, int val, uint8_t op) +{ + if (!val) return; + int i = new_sym(s, sym); + + genop_2(s, op, cursp(), i); + push(); } static void gen_assignment(codegen_scope *s, node *tree, node *rhs, int sp, int val) { int idx; - int type = nint(tree->car); - switch (type) { - case NODE_GVAR: - case NODE_ARG: - case NODE_LVAR: - case NODE_IVAR: - case NODE_CVAR: - case NODE_CONST: + /* Check if this is a variable-sized node first */ + enum node_type var_type = get_node_type(tree); + switch (var_type) { case NODE_NIL: - case NODE_MASGN: if (rhs) { codegen(s, rhs, VAL); pop(); sp = cursp(); } + /* NODE_NIL assignment is complete - just break (splat without assignment) */ break; - case NODE_COLON2: + gen_colon2_assign(s, tree, rhs, sp, val); + return; case NODE_COLON3: - case NODE_CALL: - case NODE_SCALL: - /* keep evaluation order */ - break; - - case NODE_NVAR: - /* never happens; should have already checked in the parser */ - codegen_error(s, "Can't assign to numbered parameter"); - break; - - default: - codegen_error(s, "unknown lhs"); - break; - } - - tree = tree->cdr; - switch (type) { + gen_colon3_assign(s, tree, rhs, sp, val); + return; case NODE_GVAR: - gen_setxv(s, OP_SETGV, sp, nsym(tree), val); - break; - case NODE_ARG: - case NODE_LVAR: - idx = lv_idx(s, nsym(tree)); - if (idx > 0) { - if (idx != sp) { - gen_move(s, idx, sp, val); - } - break; - } - else { /* upvar */ - gen_setupvar(s, sp, nsym(tree)); - } + gen_xvar_assignment(s, tree, rhs, sp, val, OP_SETGV); break; case NODE_IVAR: - gen_setxv(s, OP_SETIV, sp, nsym(tree), val); + gen_xvar_assignment(s, tree, rhs, sp, val, OP_SETIV); break; case NODE_CVAR: - gen_setxv(s, OP_SETCV, sp, nsym(tree), val); + gen_xvar_assignment(s, tree, rhs, sp, val, OP_SETCV); break; case NODE_CONST: - gen_setxv(s, OP_SETCONST, sp, nsym(tree), val); + gen_xvar_assignment(s, tree, rhs, sp, val, OP_SETCONST); break; - case NODE_COLON2: - case NODE_COLON3: - if (sp) { - gen_move(s, cursp(), sp, 0); - } - sp = cursp(); - push(); - if (type == NODE_COLON2) { - codegen(s, tree->car, VAL); - idx = new_sym(s, nsym(tree->cdr)); - } - else { /* NODE_COLON3 */ - genop_1(s, OP_OCLASS, cursp()); - push(); - idx = new_sym(s, nsym(tree)); - } - if (rhs) { - codegen(s, rhs, VAL); pop(); - gen_move(s, sp, cursp(), 0); - } - pop_n(2); - genop_2(s, OP_SETMCNST, sp, idx); - break; - - case NODE_CALL: - case NODE_SCALL: + case NODE_MASGN: + case NODE_MARG: + /* Multiple assignment: expressions (MASGN) and parameter destructuring (MARG) */ + codegen_masgn(s, tree, rhs, sp, val); + return; + case NODE_LVAR: { - int noself = 0, safe = (type == NODE_SCALL), skip = 0, top, call, n = 0; - mrb_sym mid = nsym(tree->cdr->car); - - top = cursp(); - if (val || sp == cursp()) { - push(); /* room for retval */ - } - call = cursp(); - if (!tree->car) { - noself = 1; - push(); - } - else { - codegen(s, tree->car, VAL); /* receiver */ - } - if (safe) { - int recv = cursp()-1; - gen_move(s, cursp(), recv, 1); - skip = genjmp2_0(s, OP_JMPNIL, cursp(), val); - } - tree = tree->cdr->cdr->car; - if (tree) { - if (tree->car) { /* positional arguments */ - n = gen_values(s, tree->car, VAL, (tree->cdr->car)?13:14); - if (n < 0) { /* variable length */ - n = 15; - push(); - } - } - if (tree->cdr->car) { /* keyword arguments */ - if (n == 13 || n == 14) { - pop_n(n); - genop_2(s, OP_ARRAY, cursp(), n); - push(); - n = 15; - } - gen_hash(s, tree->cdr->car->cdr, VAL, 0); - if (n < 14) { - n++; - } - else { - pop_n(2); - genop_2(s, OP_ARYPUSH, cursp(), 1); - } - push(); - } - } + mrb_sym sym = var_node(tree)->symbol; if (rhs) { codegen(s, rhs, VAL); pop(); + sp = cursp(); } - else { - gen_move(s, cursp(), sp, 0); - } - if (val) { - gen_move(s, top, cursp(), 1); - } - if (n < 15) { - n++; - if (n == 15) { - pop_n(14); - genop_2(s, OP_ARRAY, cursp(), 15); + idx = lv_idx(s, sym); + if (idx > 0) { + if (idx != sp) { + gen_move(s, idx, sp, val); } + break; } else { - pop(); - genop_2(s, OP_ARYPUSH, cursp(), 1); + gen_setupvar(s, sp, sym); } - push(); pop(); - s->sp = call; - if (mid == MRB_OPSYM_2(s->mrb, aref) && n == 2) { - push_n(4); pop_n(4); /* self + idx + value + (invisible block for OP_SEND) */ - genop_1(s, OP_SETIDX, cursp()); - } - else { - int st = 2 /* self + block */ + - (((n >> 0) & 0x0f) < 15 ? ((n >> 0) & 0x0f) : 1) + - (((n >> 4) & 0x0f) < 15 ? ((n >> 4) & 0x0f) * 2 : 1); - push_n(st); pop_n(st); - genop_3(s, noself ? OP_SSEND : OP_SEND, cursp(), new_sym(s, attrsym(s, mid)), n); - } - if (safe) { - dispatch(s, skip); - } - s->sp = top; } break; - - case NODE_MASGN: - gen_massignment(s, tree->car, sp, val); - break; - - /* splat without assignment */ - case NODE_NIL: - break; - + case NODE_CALL: + codegen_call_assign(s, tree, rhs, sp, val); + return; default: - codegen_error(s, "unknown lhs"); + codegen_error(s, "unsupported variable-sized lhs"); break; } if (val) push(); + return; } static void @@ -3131,72 +3079,108 @@ static void gen_literal_array(codegen_scope *s, node *tree, mrb_bool sym, int val) { if (val) { - int i = 0, j = 0, gen = 0; + int array_size = 0; + node *current = tree; - while (tree) { - switch (nint(tree->car->car)) { - case NODE_STR: - if ((tree->cdr == NULL) && (nint(tree->car->cdr->cdr) == 0)) - break; - /* fall through */ - case NODE_STMTS: - case NODE_BEGIN: - codegen(s, tree->car, VAL); - j++; - break; + /* Process each segment separated by NODE_LITERAL_DELIM */ + while (current) { + /* Find the segment boundaries without allocating */ + node *segment_start = current; + node *segment_prev = NULL; - case NODE_LITERAL_DELIM: - if (j > 0) { - j = 0; - i++; - if (sym) + /* Find end of segment (delimiter or end of list) */ + while (current && !IS_LITERAL_DELIM(current)) { + segment_prev = current; + current = current->cdr; + } + + /* Process the segment if it has content */ + if (segment_start != current) { + /* Check if this is an empty string segment (for %w[] case) */ + mrb_bool is_empty_segment = TRUE; + node *check = segment_start; + while (check != current) { + if (check->car) { + mrb_int len = node_to_int(check->car->car); + if (len > 0) { + is_empty_segment = FALSE; + break; + } + else if (len < 0) { + /* Expression node - not empty */ + is_empty_segment = FALSE; + break; + } + /* len == 0 means empty string, continue checking */ + } + check = check->cdr; + } + + /* Only process non-empty segments */ + if (!is_empty_segment) { + /* Temporarily terminate the segment by saving and clearing the cdr */ + node *saved_cdr = NULL; + if (segment_prev) { + saved_cdr = segment_prev->cdr; + segment_prev->cdr = NULL; + } + + /* Use gen_string for this segment */ + gen_string(s, segment_start, VAL); + + /* Restore the original cdr */ + if (segment_prev) { + segment_prev->cdr = saved_cdr; + } + + /* Apply symbol conversion if needed */ + if (sym) { gen_intern(s); + } + + array_size++; } - break; } - while (j >= 2) { - pop(); pop(); - genop_1(s, OP_STRCAT, cursp()); - push(); - j--; + + /* Skip the delimiter if present */ + if (current && IS_LITERAL_DELIM(current)) { + current = current->cdr; } - if (i > GEN_LIT_ARY_MAX) { - pop_n(i); - if (gen) { - pop(); - genop_2(s, OP_ARYPUSH, cursp(), i); - } - else { - genop_2(s, OP_ARRAY, cursp(), i); - gen = 1; - } - push(); - i = 0; - } - tree = tree->cdr; } - if (j > 0) { - i++; - if (sym) - gen_intern(s); - } - pop_n(i); - if (gen) { - pop(); - genop_2(s, OP_ARYPUSH, cursp(), i); + + /* Generate the array from pushed elements */ + if (array_size > 0) { + pop_n(array_size); + genop_2(s, OP_ARRAY, cursp(), array_size); } else { - genop_2(s, OP_ARRAY, cursp(), i); + genop_2(s, OP_ARRAY, cursp(), 0); } push(); } else { - while (tree) { - switch (nint(tree->car->car)) { - case NODE_STMTS: case NODE_BEGIN: case NODE_BLOCK: - codegen(s, tree->car, NOVAL); + /* NOVAL case: only evaluate expressions for side effects */ + node *current = tree; + + while (current) { + /* Process nodes until delimiter */ + while (current && !IS_LITERAL_DELIM(current)) { + node *elem = current->car; + if (elem) { + mrb_int len = node_to_int(elem->car); + if (len < 0) { + /* Expression: (-1 . node) - evaluate for side effects */ + codegen(s, (node*)elem->cdr, NOVAL); + } + /* String literals: (len . str) - no side effects, skip */ + } + current = current->cdr; + } + + /* Skip delimiter */ + if (current && IS_LITERAL_DELIM(current)) { + current = current->cdr; } - tree = tree->cdr; } } } @@ -3209,56 +3193,10 @@ raise_error(codegen_scope *s, const char *msg) genop_1(s, OP_ERR, idx); } -static mrb_int -readint(codegen_scope *s, const char *p, int base, mrb_bool neg, mrb_bool *overflow) -{ - const char *e = p + strlen(p); - mrb_int result = 0; - - mrb_assert(base >= 2 && base <= 16); - if (*p == '+') p++; - while (p < e) { - int n; - char c = *p; - switch (c) { - case '0': case '1': case '2': case '3': - case '4': case '5': case '6': case '7': - n = c - '0'; break; - case '8': case '9': - n = c - '0'; break; - case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': - n = c - 'a' + 10; break; - case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': - n = c - 'A' + 10; break; - default: - codegen_error(s, "malformed readint input"); - *overflow = TRUE; - /* not reached */ - return result; - } - if (mrb_int_mul_overflow(result, base, &result)) { - overflow: - *overflow = TRUE; - return 0; - } - mrb_uint tmp = ((mrb_uint)result)+n; - if (neg && tmp == (mrb_uint)MRB_INT_MAX+1) { - *overflow = FALSE; - return MRB_INT_MIN; - } - if (tmp > MRB_INT_MAX) goto overflow; - result = (mrb_int)tmp; - p++; - } - *overflow = FALSE; - if (neg) return -result; - return result; -} - static void gen_retval(codegen_scope *s, node *tree) { - if (nint(tree->car) == NODE_SPLAT) { + if (is_splat_node(tree)) { codegen(s, tree, VAL); pop(); genop_1(s, OP_ARYSPLAT, cursp()); @@ -3272,11 +3210,13 @@ gen_retval(codegen_scope *s, node *tree) static mrb_bool true_always(node *tree) { - switch (nint(tree->car)) { - case NODE_TRUE: + /* Check if this is a variable-sized node first */ + enum node_type var_type = get_node_type(tree); + switch (var_type) { case NODE_INT: - case NODE_STR: - case NODE_SYM: + case NODE_BIGINT: + case NODE_FLOAT: + case NODE_TRUE: return TRUE; default: return FALSE; @@ -3286,7 +3226,8 @@ true_always(node *tree) static mrb_bool false_always(node *tree) { - switch (nint(tree->car)) { + /* Check variable-sized nodes that are always false */ + switch (get_node_type(tree)) { case NODE_FALSE: case NODE_NIL: return TRUE; @@ -3312,10 +3253,2369 @@ gen_blkmove(codegen_scope *s, uint16_t ainfo, int lv) push(); } +static void +gen_lvar(codegen_scope *s, mrb_sym sym, int val) +{ + if (!val) return; + int idx = lv_idx(s, sym); + + if (idx > 0) { + gen_move(s, cursp(), idx, val); + } + else { + gen_getupvar(s, cursp(), sym); + } + push(); +} + +static void +codegen_hash(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_hash_node *hash = hash_node(varnode); + node *pairs = hash->pairs; + int regular_pairs = 0; + mrb_bool update = FALSE; + mrb_bool first = TRUE; + + if (!val) return; + + if (!pairs) { + genop_2(s, OP_HASH, cursp(), 0); + push(); + return; + } + + /* Process each key-value pair using cons-list iteration, handling double-splat (**) cases */ + node *current = pairs; + while (current) { + /* Each current->car is a cons (key . value) */ + node *pair = current->car; + struct mrb_ast_node *key = pair->car; + struct mrb_ast_node *value = pair->cdr; + + /* Check if this is a double-splat (**kwargs) */ + if (node_to_sym(key) == MRB_OPSYM_2(s->mrb, pow)) { + /* Flush any accumulated regular pairs first */ + if (val && first && regular_pairs == 0) { + /* First element is splat - create empty hash */ + genop_2(s, OP_HASH, cursp(), 0); + push(); + update = TRUE; + } + else if (val && regular_pairs > 0) { + /* Create/add hash from accumulated pairs */ + pop_n(regular_pairs * 2); + if (!update) { + genop_2(s, OP_HASH, cursp(), regular_pairs); + } + else { + pop(); + genop_2(s, OP_HASHADD, cursp(), regular_pairs); + } + push(); + } + + /* Generate the splat hash */ + codegen(s, value, val); + + /* Merge the splat hash */ + if (val && (regular_pairs > 0 || update)) { + pop(); pop(); + genop_1(s, OP_HASHCAT, cursp()); + push(); + } + + update = TRUE; + regular_pairs = 0; + } + else { + /* Regular key-value pair */ + codegen(s, key, val); + codegen(s, value, val); + regular_pairs++; + } + first = FALSE; + + current = current->cdr; + } + + /* Handle any remaining regular pairs */ + if (val) { + if (!update && regular_pairs > 0) { + /* Simple case: no splats, just create hash */ + pop_n(regular_pairs * 2); + genop_2(s, OP_HASH, cursp(), regular_pairs); + push(); + } + else if (update && regular_pairs > 0) { + /* Add remaining pairs to existing hash */ + pop_n(regular_pairs * 2 + 1); + genop_2(s, OP_HASHADD, cursp(), regular_pairs); + push(); + } + } +} + + + +/* Common function to generate bytecode for cons list string representation + * Handles list of elements where each element is either: + * - (len . str) for string literals + * - (-1 . node) for expressions that need evaluation + */ +/* Common function to generate bytecode for cons list string representation + * Handles list of elements where each element is either: + * - (len . str) for string literals + * - (-1 . node) for expressions that need evaluation + */ +/* Common function to generate bytecode for cons list string representation + * Handles list of elements where each element is either: + * - (len . str) for string literals + * - (-1 . node) for expressions that need evaluation + */ +/* Common function to generate bytecode for cons list string representation + * Handles list of elements where each element is either: + * - (len . str) for string literals + * - (-1 . node) for expressions that need evaluation + */ +static void +gen_string(codegen_scope *s, node *list, int val) +{ + if (val) { + /* Handle as cons list of string parts with safety checks */ + node *n = list; + mrb_bool first = TRUE; + + while (n) { + node *elem = n->car; + if (!elem) break; + + mrb_int len = node_to_int(elem->car); + + if (len >= 0) { + /* String literal: (len . str) */ + const char *str = (char*)elem->cdr; + if (!str) {str = ""; len = 0;} + int off = new_lit_str(s, str, len); + genop_2(s, OP_STRING, cursp(), off); + push(); + } + else { + /* Expression: (-1 . node) */ + codegen(s, (node*)elem->cdr, VAL); + } + + /* Concatenate with previous parts (except for first element) */ + if (!first) { + pop(); pop(); + genop_1(s, OP_STRCAT, cursp()); + push(); + } + else { + first = FALSE; + } + + n = n->cdr; + } + + /* Handle empty list case */ + if (first) { + gen_load_nil(s, 1); + } + } + else { + /* NOVAL case: only evaluate expressions for side effects */ + node *n = list; + while (n) { + node *elem = n->car; + if (!elem) break; + if (node_to_int(elem->car) < 0) { + /* Expression: (-1 . node) - evaluate for side effects */ + codegen(s, (node*)elem->cdr, NOVAL); + } + /* String literals: (len . str) - no side effects, skip */ + n = n->cdr; + } + } +} + + +/* Handle variable-sized node types */ +static void +codegen_call(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_call_node *call = call_node(varnode); + mrb_sym sym = call->method_name; + int skip = 0, n = 0, nk = 0, noop = no_optimize(s), noself = 0, blk = 0, sp_save = cursp(); + enum mrb_insn opt_op = OP_NOP; + int safe = call->safe_call; + node *args = call->args; + + if (!noop) { + if (sym == MRB_OPSYM_2(s->mrb, add)) opt_op = OP_ADD; + else if (sym == MRB_OPSYM_2(s->mrb, sub)) opt_op = OP_SUB; + else if (sym == MRB_OPSYM_2(s->mrb, mul)) opt_op = OP_MUL; + else if (sym == MRB_OPSYM_2(s->mrb, div)) opt_op = OP_DIV; + else if (sym == MRB_OPSYM_2(s->mrb, lt)) opt_op = OP_LT; + else if (sym == MRB_OPSYM_2(s->mrb, le)) opt_op = OP_LE; + else if (sym == MRB_OPSYM_2(s->mrb, gt)) opt_op = OP_GT; + else if (sym == MRB_OPSYM_2(s->mrb, ge)) opt_op = OP_GE; + else if (sym == MRB_OPSYM_2(s->mrb, eq)) opt_op = OP_EQ; + else if (sym == MRB_OPSYM_2(s->mrb, aref)) opt_op = OP_GETIDX; + else if (sym == MRB_OPSYM_2(s->mrb, aset)) opt_op = OP_SETIDX; + } + + if (!call->receiver || (opt_op == OP_NOP && get_node_type(call->receiver) == NODE_SELF)) { + noself = 1; + push(); + } + else { + codegen(s, call->receiver, VAL); /* receiver */ + } + + if (safe) { + int recv = cursp()-1; + gen_move(s, cursp(), recv, 1); + skip = genjmp2_0(s, OP_JMPNIL, cursp(), val); + } + + /* Generate arguments - use gen_values to properly handle splat */ + if (args) { + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)args; + if (callargs->regular_args) { + n = gen_values(s, callargs->regular_args, VAL, 14); + if (n < 0) { /* variable length (contains splat) */ + n = 15; + push(); + noop = 1; + } + } + + /* Handle keyword arguments if present */ + if (callargs->keyword_args) { + nk = gen_hash(s, callargs->keyword_args, VAL, 14); + if (nk < 0) { + nk = 15; + } + noop = 1; + } + + /* Handle block if present */ + if (callargs->block_arg) { + codegen(s, callargs->block_arg, VAL); + pop(); + blk = 1; + noop = 1; + } + } + + push(); + s->sp = sp_save; + + /* Apply optimizations */ + if (opt_op == OP_ADD && n == 1) { + gen_addsub(s, OP_ADD, cursp()); + } + else if (opt_op == OP_SUB && n == 1) { + gen_addsub(s, OP_SUB, cursp()); + } + else if (opt_op == OP_MUL && n == 1) { + gen_muldiv(s, OP_MUL, cursp()); + } + else if (opt_op == OP_DIV && n == 1) { + gen_muldiv(s, OP_DIV, cursp()); + } + else if (opt_op == OP_LT && n == 1) { + genop_1(s, OP_LT, cursp()); + } + else if (opt_op == OP_LE && n == 1) { + genop_1(s, OP_LE, cursp()); + } + else if (opt_op == OP_GT && n == 1) { + genop_1(s, OP_GT, cursp()); + } + else if (opt_op == OP_GE && n == 1) { + genop_1(s, OP_GE, cursp()); + } + else if (opt_op == OP_EQ && n == 1) { + genop_1(s, OP_EQ, cursp()); + } + else if (opt_op == OP_SETIDX && n == 2) { + genop_1(s, OP_SETIDX, cursp()); + } + else if (!noop && n == 0 && gen_uniop(s, sym, cursp())) { + /* constant folding succeeded */ + } + else if (!noop && n == 1 && gen_binop(s, sym, cursp())) { + /* constant folding succeeded */ + } + else if (noself) { + genop_3(s, blk ? OP_SSENDB : OP_SSEND, cursp(), new_sym(s, sym), n|(nk<<4)); + } + else { + genop_3(s, blk ? OP_SENDB : OP_SEND, cursp(), new_sym(s, sym), n|(nk<<4)); + } + + if (safe) { + dispatch(s, skip); + } + if (!val) return; + push(); +} + +static void +codegen_call_assign(codegen_scope *s, node *varnode, node *rhs, int sp, int val) +{ + enum node_type var_type = VAR_NODE_TYPE(varnode); + int noself = 0, safe = 0, skip = 0, top, callsp, n = 0, nk = 0; + mrb_sym mid = 0; + node *args = NULL; + node *receiver = NULL; + enum mrb_insn opt_op = OP_NOP; + int noop = no_optimize(s); + + /* Extract information based on node type */ + if (var_type == NODE_CALL) { + struct mrb_ast_call_node *call = call_node(varnode); + mid = call->method_name; + args = call->args; + receiver = call->receiver; + safe = call->safe_call; + } + else { + codegen_error(s, "unsupported call type in assignment"); + return; + } + + /* Convert method name to assignment form (e.g., [] -> []=) */ + mrb_sym assign_mid = attrsym(s, mid); + + /* Check for optimizable operations */ + if (!noop) { + if (mid == MRB_OPSYM_2(s->mrb, aref)) opt_op = OP_SETIDX; + } + + top = cursp(); + if (val || sp == cursp()) { + push(); /* room for retval */ + } + callsp = cursp(); + + /* Generate receiver */ + if (!receiver) { + noself = 1; + push(); + } + else { + codegen(s, receiver, VAL); /* receiver */ + } + + /* Handle safe navigation */ + if (safe) { + int recv = cursp()-1; + gen_move(s, cursp(), recv, 1); + skip = genjmp2_0(s, OP_JMPNIL, cursp(), val); + } + + /* Generate arguments from original call */ + if (args) { + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)args; + if (callargs->regular_args) { + node *regular_args = callargs->regular_args; + node *arg_iter = regular_args; + while (arg_iter) { + codegen(s, arg_iter->car, VAL); + n++; + arg_iter = arg_iter->cdr; + } + if (n > 13) { /* leave room for rhs */ + pop_n(n); + genop_2(s, OP_ARRAY, cursp(), n); + push(); + n = 15; + noop = 1; + } + } + + /* Handle keyword arguments if present */ + if (callargs->keyword_args) { + node *kwargs = callargs->keyword_args; + if (n == 13 || n == 14) { + pop_n(n); + genop_2(s, OP_ARRAY, cursp(), n); + push(); + n = 15; + } + gen_hash(s, kwargs->cdr, VAL, 0); + if (n < 14) { + n++; + } + else { + pop_n(2); + genop_2(s, OP_ARYPUSH, cursp(), 1); + } + push(); + noop = 1; + } + } + + /* Generate rhs (the assigned value) */ + if (rhs) { + codegen(s, rhs, VAL); + pop(); + } + else { + /* For compound assignments, move the computed value from sp to cursp() */ + gen_move(s, cursp(), sp, 0); + } + if (val) { + gen_move(s, top, cursp(), 1); + } + /* Account for the value being assigned (either from rhs or already on stack) */ + if (n < 14) { + n++; + } + else { + if (rhs) { + pop_n(2); + genop_2(s, OP_ARYPUSH, cursp(), 1); + push(); + } + } + + /* Generate the optimized instruction or method call */ + push(); push(); + s->sp = callsp; + + if (opt_op == OP_SETIDX && n == 2) { + /* Always preserve return value for SETIDX - assignments return the assigned value */ + genop_1(s, OP_SETIDX, cursp()); + } + else if (noself) { + genop_3(s, OP_SSEND, cursp(), new_sym(s, assign_mid), n|(nk<<4)); + } + else { + genop_3(s, OP_SEND, cursp(), new_sym(s, assign_mid), n|(nk<<4)); + } + + if (safe) { + dispatch(s, skip); + } + + /* Restore stack pointer like legacy code */ + s->sp = top; + + if (val) { + push(); + } +} + +static void +codegen_array(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_array_node *array = array_node(varnode); + node *elements = array->elements; + int regular_elements = 0; + int first = 1; + int slimit = GEN_VAL_STACK_MAX; + + if (!val) return; + + if (!elements) { + genop_2(s, OP_ARRAY, cursp(), 0); + push(); + return; + } + + if (cursp() >= GEN_LIT_ARY_MAX) slimit = INT16_MAX; + + /* Process each element using cons-list iteration, handling splats */ + node *current = elements; + while (current) { + struct mrb_ast_node *element = current->car; + int is_splat = is_splat_node(element); + + /* Skip splat of an empty literal array: [*[]] => [] without ARYCAT noise */ + if (is_splat) { + struct mrb_ast_splat_node *splat = splat_node(element); + node *sv = splat->value; + if (sv) { + enum node_type nt = get_node_type(sv); + if (nt == NODE_ARRAY) { + struct mrb_ast_array_node *an = array_node(sv); + if (an->elements == NULL) { + current = current->cdr; + continue; + } + } + else if (nt == NODE_ZARRAY) { + current = current->cdr; + continue; + } + } + } + + if (is_splat || cursp() >= slimit) { /* flush accumulated elements */ + if (regular_elements > 0) { + pop_n(regular_elements); + if (first) { + genop_2(s, OP_ARRAY, cursp(), regular_elements); + push(); + first = 0; + } + else { + pop(); + genop_2(s, OP_ARYPUSH, cursp(), regular_elements); + push(); + } + regular_elements = 0; + } + else if (first && is_splat) { + /* First element is splat - create empty array */ + genop_1(s, OP_LOADNIL, cursp()); + genop_2(s, OP_ARRAY, cursp(), 0); + push(); + first = 0; + } + } + + codegen(s, element, val); + + if (is_splat) { + /* Concatenate splat array */ + pop(); pop(); + genop_1(s, OP_ARYCAT, cursp()); + push(); + } + else { + regular_elements++; + } + + current = current->cdr; + } + + /* Handle any remaining regular elements */ + if (!first) { + /* Variable length - we have an array from splats */ + if (regular_elements > 0) { + pop_n(regular_elements + 1); + genop_2(s, OP_ARYPUSH, cursp(), regular_elements); + push(); + } + } + else { + /* Simple case: no splats, just create array */ + pop_n(regular_elements); + genop_2(s, OP_ARRAY, cursp(), regular_elements); + push(); + } +} + +/* Control flow and definition node codegen functions */ +static mrb_bool +callargs_empty(node *n) +{ + if (!n) return TRUE; + return (callargs_node(n)->regular_args == 0 && callargs_node(n)->keyword_args == 0 && callargs_node(n)->block_arg == 0); +} + +static void +codegen_if(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_if_node *if_n = if_node(varnode); + node *condition = if_n->condition; + node *then_body = if_n->then_body; + node *else_body = if_n->else_body; + uint32_t pos1, pos2; + mrb_bool nil_p = FALSE; + + if (!condition) { + codegen(s, else_body, val); + return; + } + if (true_always(condition)) { + codegen(s, then_body, val); + return; + } + if (false_always(condition)) { + codegen(s, else_body, val); + return; + } + + /* Check for nil? optimization */ + if (get_node_type(condition) == NODE_CALL) { + /* Variable-sized NODE_CALL */ + struct mrb_ast_call_node *call_n = (struct mrb_ast_call_node*)condition; + mrb_sym sym_nil_p = MRB_SYM_Q_2(s->mrb, nil); + if (call_n->method_name == sym_nil_p && callargs_empty(call_n->args)) { + nil_p = TRUE; + codegen(s, call_n->receiver, VAL); + } + } + + if (!nil_p) { + /* Generate condition code */ + codegen(s, condition, VAL); + } + pop(); + + if (val || then_body) { + if (nil_p) { + pos2 = genjmp2_0(s, OP_JMPNIL, cursp(), val); + pos1 = genjmp_0(s, OP_JMP); + dispatch(s, pos2); + } + else { + pos1 = genjmp2_0(s, OP_JMPNOT, cursp(), val); + } + codegen(s, then_body, val); + if (val) pop(); + if (else_body || val) { + pos2 = genjmp_0(s, OP_JMP); + dispatch(s, pos1); + codegen(s, else_body, val); + dispatch(s, pos2); + } + else { + dispatch(s, pos1); + } + } + else { /* empty then-part */ + if (else_body) { + if (nil_p) { + pos1 = genjmp2_0(s, OP_JMPNIL, cursp(), val); + } + else { + pos1 = genjmp2_0(s, OP_JMPIF, cursp(), val); + } + codegen(s, else_body, val); + dispatch(s, pos1); + } + else if (val && !nil_p) { + gen_load_nil(s, 1); + } + } +} + +static void +codegen_while(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; + + /* Check for constant conditions first */ + if (true_always(condition)) { + /* while true - 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; + } + 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 (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; + + if (!val) lp->reg = -1; + 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); + 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); +} + +static void +codegen_until_mod(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; + + /* Handle special constant cases for post-tested loops */ + if (true_always(condition)) { + /* begin...end until true - execute once then exit */ + codegen(s, body, val); + if (val) push(); + return; + } + if (false_always(condition)) { + /* begin...end until false - 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 until 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_JMPIF, 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); +} + +static void +codegen_for(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_for_node *for_n = for_node(varnode); + node *var = for_n->var; + node *iterable = for_n->iterable; + node *body = for_n->body; + + codegen_scope *prev = s; + int idx; + struct loopinfo *lp; + + /* generate receiver */ + codegen(s, iterable, VAL); + /* generate loop-block */ + s = scope_new(s->mrb, s, NULL); + + push(); /* push for a block parameter */ + + /* generate loop variable */ + genop_W(s, OP_ENTER, 0x40000); + if (var->car && !var->car->cdr && !var->cdr) { + gen_assignment(s, var->car->car, NULL, 1, NOVAL); + } + else { + gen_massignment(s, var, 1, VAL); + } + /* construct loop */ + lp = loop_push(s, LOOP_FOR); + lp->pc1 = new_label(s); + genop_0(s, OP_NOP); /* for redo */ + + /* loop body */ + codegen(s, body, VAL); + pop(); + gen_return(s, OP_RETURN, cursp()); + loop_pop(s, NOVAL); + scope_finish(s); + s = prev; + genop_2(s, OP_BLOCK, cursp(), s->irep->rlen-1); + push();pop(); /* space for a block */ + pop(); + idx = new_sym(s, MRB_SYM_2(s->mrb, each)); + genop_3(s, OP_SENDB, cursp(), idx, 0); + if (val) push(); +} + +static void +codegen_case(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_case_node *case_n = case_node(varnode); + node *value = case_n->value; + node *body = case_n->body; + + int head = 0; + uint32_t case_end_jumps, tmp; + uint32_t next_when_pos = JMPLINK_START; + node *n; + + case_end_jumps = JMPLINK_START; + + /* Handle case value exactly like original */ + if (value) { + head = cursp(); + codegen(s, value, VAL); + } + + /* Iterate through when clauses list with JMPNOT optimization */ + node *current_when = body; + while (current_when) { + node *when_clause = current_when->car; + + /* Dispatch previous when's "next" jump to this location */ + if (next_when_pos != JMPLINK_START) { + dispatch_linked(s, next_when_pos); + next_when_pos = JMPLINK_START; + } + + /* when_clause is (condition . body) cons node */ + node *args = when_clause->car; /* when conditions */ + node *when_body = when_clause->cdr; /* when body */ + + /* Process when conditions with JMPNOT optimization */ + n = args; + uint32_t condition_success_pos = JMPLINK_START; + + while (n) { + codegen(s, n->car, VAL); + if (head) { + gen_move(s, cursp(), head, 0); + push(); push(); pop(); pop(); pop(); + if (is_splat_node(n->car)) { + genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_SYM_2(s->mrb, __case_eqq)), 1); + } + else { + genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, eqq)), 1); + } + } + else { + pop(); + } + + if (n->cdr) { + /* More conditions in this when - use JMPIF to success handler */ + tmp = genjmp2(s, OP_JMPIF, cursp(), condition_success_pos, !head); + condition_success_pos = tmp; + } + else { + /* Last condition - use JMPNOT to next when clause */ + tmp = genjmp2(s, OP_JMPNOT, cursp(), next_when_pos, !head); + next_when_pos = tmp; + } + n = n->cdr; + } + + /* Dispatch multiple condition success jumps to body */ + if (condition_success_pos != JMPLINK_START) { + dispatch_linked(s, condition_success_pos); + } + + /* Generate when body */ + codegen(s, when_body, val); + if (val) pop(); + + /* Check if this is the last when clause before else, or if there's no else clause */ + node *next_node = current_when->cdr; + + tmp = genjmp(s, OP_JMP, case_end_jumps); + case_end_jumps = tmp; + + current_when = next_node; + } + + /* Handle case where no else clause was found */ + if (next_when_pos != JMPLINK_START) { + dispatch_linked(s, next_when_pos); + /* No else clause, generate LOADNIL for VAL case */ + if (val) { + genop_1(s, OP_LOADNIL, cursp()); + } + } + + /* Apply stack management strategy for cases without else clause */ + if (val) { + /* Dispatch remaining case_end_jumps */ + if (case_end_jumps != JMPLINK_START) { + dispatch_linked(s, case_end_jumps); + } + if (head) { + /* Move result to original case value position */ + gen_move(s, head, cursp(), 0); + pop(); + } + /* Always push to maintain stack alignment */ + push(); + } + else { + /* NOVAL case */ + if (case_end_jumps != JMPLINK_START) { + dispatch_linked(s, case_end_jumps); + } + if (head) { + pop(); + } + } +} + +/* Definition node codegen functions */ + +static void +codegen_def(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_def_node *def_n = def_node(varnode); + int sym = new_sym(s, def_n->name); + + /* Call lambda_body directly with individual parameters */ + /* For NODE_DEF, args should contain the full locals structure from defn_setup */ + int idx = lambda_body(s, def_n->locals, def_n->args, def_n->body, 0); + + genop_1(s, OP_TCLASS, cursp()); + push(); + genop_2(s, OP_METHOD, cursp(), idx); + push(); pop(); + pop(); + genop_2(s, OP_DEF, cursp(), sym); + if (val) push(); +} + +/* Helper function for generating class/module/singleton class body */ +/* Forward declaration */ +static mrb_bool is_empty_stmts(node *stmt_node); + +static void +gen_class_body(codegen_scope *s, node *body, int val) +{ + int idx; + + if (body && body->cdr) { + /* Extract locals and body from the cons structure: (locals . body) */ + node *locals = body->car; + node *body_stmts = body->cdr; + + /* Check for empty body case */ + if (is_empty_stmts(body_stmts)) { + genop_1(s, OP_LOADNIL, cursp()); + } + else { + /* Generate proper scope with locals and body */ + idx = scope_body(s, locals, body_stmts, val); + genop_2(s, OP_EXEC, cursp(), idx); + } + } + else { + /* No body - load nil */ + genop_1(s, OP_LOADNIL, cursp()); + } +} + +/* Helper function for generating namespace/parent for class/module */ +static void +gen_namespace(codegen_scope *s, node *name) +{ + if (name->car == (node*)0) { + genop_1(s, OP_LOADNIL, cursp()); + push(); + } + else if (name->car == (node*)1) { + genop_1(s, OP_OCLASS, cursp()); + push(); + } + else { + codegen(s, name->car, VAL); + } +} + +static void +codegen_class(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_class_node *class_n = class_node(varnode); + node *name = class_n->name; + node *superclass = class_n->superclass; + node *body = class_n->body; + int idx; + + /* Handle class namespace */ + gen_namespace(s, name); + + /* Handle superclass */ + if (superclass) { + codegen(s, superclass, VAL); + } + else { + genop_1(s, OP_LOADNIL, cursp()); + push(); + } + + pop(); pop(); + + /* Create class with name symbol */ + idx = new_sym(s, node_to_sym(name->cdr)); + genop_2(s, OP_CLASS, cursp(), idx); + + /* Generate class body */ + gen_class_body(s, body, val); + + if (val) { + push(); + } +} + +static void +codegen_module(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_module_node *module_n = module_node(varnode); + node *name = module_n->name; + node *body = module_n->body; + int idx; + + /* Handle module namespace */ + gen_namespace(s, name); + pop(); + + /* Create module with name symbol */ + idx = new_sym(s, node_to_sym(name->cdr)); + genop_2(s, OP_MODULE, cursp(), idx); + + /* Generate module body */ + gen_class_body(s, body, val); + + if (val) { + push(); + } +} + +static void +codegen_sclass(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_sclass_node *sclass_n = sclass_node(varnode); + node *obj = sclass_n->obj; + node *body = sclass_n->body; + + /* Generate code for the singleton object */ + codegen(s, obj, VAL); + pop(); + + /* Enter singleton class scope */ + genop_1(s, OP_SCLASS, cursp()); + + /* Generate singleton class body */ + gen_class_body(s, body, val); + + if (val) { + push(); + } +} + +/* Variable-sized assignment codegen functions */ +static void +codegen_asgn(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_asgn_node *asgn_n = asgn_node(varnode); + node *lhs = asgn_n->lhs; + node *rhs = asgn_n->rhs; + + gen_assignment(s, lhs, rhs, 0, val); +} + +static void +codegen_masgn(codegen_scope *s, node *varnode, node *rhs, int sp, int val) +{ + struct mrb_ast_masgn_node *masgn_n = (struct mrb_ast_masgn_node*)varnode; + + /* If called from codegen_variable_node context, use the embedded rhs */ + if (!rhs && sp == 0) { + rhs = masgn_n->rhs; + sp = 0; /* Use register 0 as base for standalone assignment */ + } + + int len = 0, n = 0, post = 0; + node *t = rhs ? rhs : masgn_n->rhs, *p; + int rhs_reg = sp; + + if (!val && t && get_node_type(t) == NODE_ARRAY) { + struct mrb_ast_array_node *an = array_node(t); + if (an->elements && nosplat(an->elements)) { + /* fixed rhs */ + t = an->elements; + rhs_reg = cursp(); /* Save register where values will be pushed */ + while (t) { + codegen(s, t->car, VAL); + len++; + t = t->cdr; + } + if (masgn_n->pre) { /* pre */ + t = masgn_n->pre; + n = 0; + while (t) { + if (n < len) { + gen_assignment(s, t->car, NULL, rhs_reg+n, NOVAL); + n++; + } + else { + genop_1(s, OP_LOADNIL, rhs_reg+n); + gen_assignment(s, t->car, NULL, rhs_reg+n, NOVAL); + } + t = t->cdr; + } + } + /* Count post variables */ + if (masgn_n->post) { + p = masgn_n->post; + while (p) { + post++; + p = p->cdr; + } + } + /* Handle rest variable */ + if (masgn_n->rest && (intptr_t)masgn_n->rest != -1) { + int rn; + + if (len < post + n) { + rn = 0; + } + else { + rn = len - post - n; + } + if (cursp() == rhs_reg+n) { + genop_2(s, OP_ARRAY, cursp(), rn); + } + else { + genop_3(s, OP_ARRAY2, cursp(), rhs_reg+n, rn); + } + gen_assignment(s, masgn_n->rest, NULL, cursp(), NOVAL); + n += rn; + } + /* Handle post variables */ + if (masgn_n->post) { + t = masgn_n->post; + while (t) { + if (ncar, NULL, rhs_reg+n, NOVAL); + } + else { + genop_1(s, OP_LOADNIL, cursp()); + gen_assignment(s, t->car, NULL, cursp(), NOVAL); + } + t = t->cdr; + n++; + } + } + pop_n(len); + return; + } + } + + { + /* variable rhs - implement gen_massignment logic directly for variable-sized nodes */ + + /* Check if this is parameter destructuring (called from lambda_body) */ + if (!rhs && sp > 0) { + /* Parameter destructuring: value is already in register sp */ + rhs_reg = sp; + } + else if (t) { + codegen(s, t, VAL); + rhs_reg = cursp() - 1; /* rhs is now at cursp()-1 */ + } + else { + /* No rhs and no sp value - should not happen in normal cases */ + return; + } + + /* Handle the lhs structure directly */ + n = 0; + post = 0; + + if (masgn_n->pre) { /* pre */ + node *pre = masgn_n->pre; + n = 0; + while (pre) { + int sp = cursp(); + genop_3(s, OP_AREF, sp, rhs_reg, n); + push(); + gen_assignment(s, pre->car, NULL, sp, NOVAL); + pop(); + n++; + pre = pre->cdr; + } + } + + /* Count post variables */ + if (masgn_n->post) { + node *p = masgn_n->post; + while (p) { + post++; + p = p->cdr; + } + } + + gen_move(s, cursp(), rhs_reg, val); + push_n(post+1); + pop_n(post+1); + genop_3(s, OP_APOST, cursp(), n, post); + int nn = 1; + if (masgn_n->rest && (intptr_t)masgn_n->rest != -1) { /* rest */ + gen_assignment(s, masgn_n->rest, NULL, cursp(), NOVAL); + } + if (masgn_n->post) { + node *post_part = masgn_n->post; + while (post_part) { + gen_assignment(s, post_part->car, NULL, cursp()+nn, NOVAL); + post_part = post_part->cdr; + nn++; + } + } + if (val) { + gen_move(s, cursp(), rhs_reg, 0); + } + + if (!val && t) { + pop(); /* pop the rhs value */ + } + } +} + +static void +codegen_op_asgn(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_op_asgn_node *op_asgn_n = op_asgn_node(varnode); + node *lhs = op_asgn_n->lhs; + node *rhs = op_asgn_n->rhs; + mrb_sym sym = op_asgn_n->op; + mrb_int len; + const char *name = mrb_sym_name_len(s->mrb, sym, &len); + int vsp = -1; + + /* Handle ||= and &&= operators */ + if (len == 2 && + ((name[0] == '|' && name[1] == '|') || + (name[0] == '&' && name[1] == '&'))) { + uint32_t pos; + enum node_type lhs_type = get_node_type(lhs); + + /* For ||= on class variables and constants, wrap read in exception handling */ + if (name[0] == '|' && (lhs_type == NODE_CVAR || lhs_type == NODE_CONST)) { + int catch_entry, begin, end; + int noexc, exc; + struct loopinfo *lp; + + lp = loop_push(s, LOOP_BEGIN); + lp->pc0 = new_label(s); + catch_entry = catch_handler_new(s); + begin = s->pc; + exc = cursp(); + codegen(s, lhs, VAL); + end = s->pc; + noexc = genjmp_0(s, OP_JMP); + lp->type = LOOP_RESCUE; + catch_handler_set(s, catch_entry, MRB_CATCH_RESCUE, begin, end, s->pc); + genop_1(s, OP_EXCEPT, exc); + genop_1(s, OP_LOADF, exc); + dispatch(s, noexc); + loop_pop(s, NOVAL); + } + else { + /* Generate code to get current value of LHS */ + codegen(s, lhs, VAL); + } + + pop(); + if (val) { + if (vsp >= 0) { + gen_move(s, vsp, cursp(), 1); + } + pos = genjmp2_0(s, name[0]=='|'?OP_JMPIF:OP_JMPNOT, cursp(), val); + } + else { + pos = genjmp2_0(s, name[0]=='|'?OP_JMPIF:OP_JMPNOT, cursp(), val); + } + codegen(s, rhs, VAL); + pop(); + if (val && vsp >= 0) { + gen_move(s, vsp, cursp(), 1); + } + gen_assignment(s, lhs, NULL, cursp(), val); + dispatch(s, pos); + return; + } + + /* For other operators, generate: lhs = lhs op rhs */ + codegen(s, lhs, VAL); + codegen(s, rhs, VAL); + push(); pop(); + pop(); pop(); + + /* Apply the operator */ + if (len == 1 && name[0] == '+') { + gen_addsub(s, OP_ADD, cursp()); + } + else if (len == 1 && name[0] == '-') { + gen_addsub(s, OP_SUB, cursp()); + } + else if (len == 1 && name[0] == '*') { + genop_1(s, OP_MUL, cursp()); + } + else if (len == 1 && name[0] == '/') { + genop_1(s, OP_DIV, cursp()); + } + else if (len == 1 && name[0] == '<') { + genop_1(s, OP_LT, cursp()); + } + else if (len == 2 && name[0] == '<' && name[1] == '=') { + genop_1(s, OP_LE, cursp()); + } + else if (len == 1 && name[0] == '>') { + genop_1(s, OP_GT, cursp()); + } + else if (len == 2 && name[0] == '>' && name[1] == '=') { + genop_1(s, OP_GE, cursp()); + } + else { + int idx = new_sym(s, sym); + genop_3(s, OP_SEND, cursp(), idx, 1); + } + + /* Assign the result back to LHS */ + gen_assignment(s, lhs, NULL, cursp(), val); +} + +/* Variable-sized expression codegen functions */ +static void +codegen_and(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_and_node *and_n = (struct mrb_ast_and_node*)varnode; + node *left = and_n->left; + node *right = and_n->right; + uint32_t pos; + + if (true_always(left)) { + codegen(s, right, val); + return; + } + if (false_always(left)) { + codegen(s, left, val); + return; + } + codegen(s, left, VAL); + pop(); + pos = genjmp2_0(s, OP_JMPNOT, cursp(), val); + codegen(s, right, val); + dispatch(s, pos); +} + +static void +codegen_or(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_or_node *or_n = (struct mrb_ast_or_node*)varnode; + node *left = or_n->left; + node *right = or_n->right; + uint32_t pos; + + if (true_always(left)) { + codegen(s, left, val); + return; + } + if (false_always(left)) { + codegen(s, right, val); + return; + } + codegen(s, left, VAL); + pop(); + pos = genjmp2_0(s, OP_JMPIF, cursp(), val); + codegen(s, right, val); + dispatch(s, pos); +} + +static void +codegen_return(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_return_node *return_n = return_node(varnode); + node *args = return_n->args; + + if (args) { + gen_retval(s, args); + } + else { + genop_1(s, OP_LOADNIL, cursp()); + } + if (s->loop) { + gen_return(s, OP_RETURN_BLK, cursp()); + } + else { + gen_return(s, OP_RETURN, cursp()); + } + if (!val) return; + push(); +} + +static void +codegen_yield(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_yield_node *yield_n = yield_node(varnode); + node *args = yield_n->args; + codegen_scope *s2 = s; + int lv = 0, ainfo = -1; + int n = 0, nk = 0, sendv = 0; + + while (!s2->mscope) { + lv++; + s2 = s2->prev; + if (!s2) break; + } + if (s2) { + ainfo = (int)s2->ainfo; + } + if (ainfo < 0) codegen_error(s, "invalid yield (SyntaxError)"); + if (lv > 0xf) codegen_error(s, "too deep nesting"); + push(); + if (args) { + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)args; + if (callargs->regular_args) { + n = gen_values(s, callargs->regular_args, VAL, 14); + if (n < 0) { + n = sendv = 1; + push(); + } + } + if (callargs->keyword_args) { + nk = gen_hash(s, callargs->keyword_args->cdr, VAL, 14); + if (nk < 0) { + nk = 15; + } + } + } + push();pop(); /* space for a block */ + pop_n(n + (nk == 15 ? 1 : nk * 2) + 1); + genop_2S(s, OP_BLKPUSH, cursp(), (ainfo<<4)|(lv & 0xf)); + if (sendv) n = CALL_MAXARGS; + genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_SYM_2(s->mrb, call)), n|(nk<<4)); + if (val) push(); +} + +static void +codegen_super(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_super_node *super_n = super_node(varnode); + node *tree = super_n->args; + + codegen_scope *s2 = s; + int lv = 0; + int n = 0, nk = 0, st = 0; + + push(); + while (!s2->mscope) { + lv++; + s2 = s2->prev; + if (!s2) break; + } + if (tree) { + /* Handle callargs structure - direct casting like new_args() */ + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)tree; + + /* Regular arguments */ + if (callargs->regular_args) { + st = n = gen_values(s, callargs->regular_args, VAL, 14); + if (n < 0) { + st = 1; n = 15; + push(); + } + } + + /* Keyword arguments */ + if (callargs->keyword_args) { + nk = gen_hash(s, callargs->keyword_args->cdr, VAL, 14); + if (nk < 0) {st++; nk = 15;} + else st += nk*2; + n |= nk<<4; + } + + /* Block arguments */ + if (callargs->block_arg) { + codegen(s, callargs->block_arg, VAL); + } + else if (s2) gen_blkmove(s, s2->ainfo, lv); + else { + genop_1(s, OP_LOADNIL, cursp()); + push(); + } + } + else { + if (s2) gen_blkmove(s, s2->ainfo, lv); + else { + genop_1(s, OP_LOADNIL, cursp()); + push(); + } + } + st++; + pop_n(st+1); + genop_2(s, OP_SUPER, cursp(), n); + if (val) push(); +} + +/* Variable-sized literal node generation functions */ +static void +codegen_str(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_str_node *str_n = str_node(varnode); + node *list = str_n->list; + + /* Use common cons list string codegen */ + gen_string(s, list, val); +} + +static void +codegen_dot2(codegen_scope *s, node *varnode, int val) +{ + node *left = dot2_node(varnode)->left; + node *right = dot2_node(varnode)->right; + + codegen(s, left, 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()); + push(); +} + +static void +codegen_float(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_float_node *float_n = (struct mrb_ast_float_node*)varnode; + const char *value = float_n->value; + double f; + + mrb_read_float(value, NULL, &f); + int off = new_lit_float(s, (mrb_float)f); + + gen_load_op2(s, OP_LOADL, off, val); +} + +/* Variable-sized simple node generation functions */ +static void +codegen_self(codegen_scope *s, node *varnode, int val) +{ + /* Use traditional self codegen logic */ + gen_load_op1(s, OP_LOADSELF, val); +} + +static void +codegen_nil(codegen_scope *s, node *varnode, int val) +{ + /* Use traditional nil codegen logic */ + gen_load_op1(s, OP_LOADNIL, val); +} + +static void +codegen_true(codegen_scope *s, node *varnode, int val) +{ + /* Generate OP_LOADT instruction for true literal */ + gen_load_op1(s, OP_LOADT, val); +} + +static void +codegen_false(codegen_scope *s, node *varnode, int val) +{ + /* Generate OP_LOADF instruction for false literal */ + gen_load_op1(s, OP_LOADF, val); +} + +static void +codegen_const(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_const_node *const_n = const_node(varnode); + mrb_sym symbol = const_n->symbol; + + int i = new_sym(s, symbol); + genop_2(s, OP_GETCONST, cursp(), i); + if (val) push(); +} + +static void +codegen_rescue(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_rescue_node *rescue = rescue_node(varnode); + node *body = rescue->body; + node *rescue_clauses = rescue->rescue_clauses; + node *else_clause = rescue->else_clause; + + int noexc; + uint32_t exend, pos1, pos2, tmp; + struct loopinfo *lp; + int catch_entry, begin, end; + + if (body == NULL) return; + lp = loop_push(s, LOOP_BEGIN); + lp->pc0 = new_label(s); + catch_entry = catch_handler_new(s); + begin = s->pc; + codegen(s, body, VAL); + pop(); + lp->type = LOOP_RESCUE; + end = s->pc; + noexc = genjmp_0(s, OP_JMP); + catch_handler_set(s, catch_entry, MRB_CATCH_RESCUE, begin, end, s->pc); + exend = JMPLINK_START; + pos1 = JMPLINK_START; + if (rescue_clauses) { + node *n2 = rescue_clauses; + int exc = cursp(); + + genop_1(s, OP_EXCEPT, exc); + push(); + while (n2) { + node *n3 = n2->car; + node *n4 = n3->car; + + dispatch(s, pos1); + pos2 = JMPLINK_START; + do { + if (n4 && n4->car && is_splat_node(n4->car)) { + codegen(s, n4->car, VAL); + gen_move(s, cursp(), exc, 0); + push_n(2); pop_n(2); /* space for one arg and a block */ + pop(); + genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_SYM_2(s->mrb, __case_eqq)), 1); + } + else { + if (n4) { + codegen(s, n4->car, VAL); + } + else { + genop_2(s, OP_GETCONST, cursp(), new_sym(s, MRB_SYM_2(s->mrb, StandardError))); + push(); + } + pop(); + genop_2(s, OP_RESCUE, exc, cursp()); + } + tmp = genjmp2(s, OP_JMPIF, cursp(), pos2, val); + pos2 = tmp; + if (n4) { + n4 = n4->cdr; + } + } while (n4); + pos1 = genjmp_0(s, OP_JMP); + dispatch_linked(s, pos2); + + pop(); + if (n3->cdr->car) { + gen_assignment(s, n3->cdr->car, NULL, exc, NOVAL); + } + if (n3->cdr->cdr->car) { + codegen(s, n3->cdr->cdr->car, val); + if (val) pop(); + } + tmp = genjmp(s, OP_JMP, exend); + exend = tmp; + n2 = n2->cdr; + push(); + } + if (pos1 != JMPLINK_START) { + dispatch(s, pos1); + genop_1(s, OP_RAISEIF, exc); + } + } + pop(); + dispatch(s, noexc); + if (else_clause) { + codegen(s, else_clause, val); + } + else if (val) { + push(); + } + dispatch_linked(s, exend); + loop_pop(s, NOVAL); +} + +static void +codegen_block(codegen_scope *s, node *varnode, int val) +{ + if (!val) return; + + struct mrb_ast_block_node *n = block_node(varnode); + + /* Call lambda_body directly with individual parameters */ + int idx = lambda_body(s, n->locals, n->args, n->body, 1); + genop_2(s, OP_BLOCK, cursp(), idx); + push(); +} + +static void +codegen_break(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_break_node *n = (struct mrb_ast_break_node*)varnode; + loop_break(s, n->value); + if (!val) return; + push(); +} + +static void +codegen_next(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_next_node *n = (struct mrb_ast_next_node*)varnode; + if (!s->loop) { + raise_error(s, "unexpected next"); + } + else if (s->loop->type == LOOP_NORMAL) { + codegen(s, n->value, NOVAL); + genjmp(s, OP_JMPUW, s->loop->pc0); + } + else { + if (n->value) { + codegen(s, n->value, VAL); + pop(); + } + else { + genop_1(s, OP_LOADNIL, cursp()); + } + gen_return(s, OP_RETURN, cursp()); + } + if (!val) return; + push(); +} + +static void +codegen_redo(codegen_scope *s, node *varnode, int val) +{ + for (const struct loopinfo *lp = s->loop; ; lp = lp->prev) { + if (!lp) { + raise_error(s, "unexpected redo"); + break; + } + if (lp->type != LOOP_BEGIN && lp->type != LOOP_RESCUE) { + genjmp(s, OP_JMPUW, lp->pc1); + break; + } + } + if (!val) return; + push(); +} + +static void +codegen_retry(codegen_scope *s, node *varnode, int val) +{ + const struct loopinfo *lp = s->loop; + + while (lp && lp->type != LOOP_RESCUE) { + lp = lp->prev; + } + if (!lp) { + raise_error(s, "unexpected retry"); + } + else { + genjmp(s, OP_JMPUW, lp->pc0); + } + if (!val) return; + push(); +} + +static void +codegen_xstr(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_xstr_node *n = xstr_node(varnode); + node *list = n->list; + int sym; + + /* Always execute backtick command for side effects, even in NOVAL mode */ + push(); + /* Generate string using common function */ + gen_string(s, list, VAL); + + push(); /* for block */ + pop_n(3); + sym = new_sym(s, MRB_OPSYM_2(s->mrb, tick)); /* ` */ + genop_3(s, OP_SSEND, cursp(), sym, 1); + + if (val) { + push(); /* Keep result on stack if needed */ + } + /* If val=0, the result is discarded but the method was still called */ +} + +static void +codegen_regx(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_regx_node *n = regx_node(varnode); + + if (val) { + int sym = new_sym(s, mrb_intern_lit(s->mrb, REGEXP_CLASS)); + int argc = 1; + int off; + + genop_1(s, OP_OCLASS, cursp()); + genop_2(s, OP_GETMCNST, cursp(), sym); + push(); + + /* Generate regex pattern using common cons list function */ + gen_string(s, n->list, VAL); + + /* Add flags and/or encoding if present */ + if ((n->flags && *n->flags) || (n->encoding && *n->encoding)) { + /* Add flags (or nil if not present but encoding is) */ + if (n->flags && *n->flags) { + off = new_lit_cstr(s, n->flags); + genop_2(s, OP_STRING, cursp(), off); + } + else { + genop_1(s, OP_LOADNIL, cursp()); + } + push(); + argc++; + + /* Add encoding if present */ + if (n->encoding && *n->encoding) { + off = new_lit_cstr(s, n->encoding); + genop_2(s, OP_STRING, cursp(), off); + push(); + argc++; + } + } + + push(); /* space for a block */ + pop_n(argc+2); + sym = new_sym(s, MRB_SYM_2(s->mrb, compile)); + genop_3(s, OP_SEND, cursp(), sym, argc); + push(); + } + else { + /* NOVAL case: still need to evaluate expressions for side effects */ + gen_string(s, n->list, NOVAL); + } +} + +static void +codegen_heredoc(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_heredoc_node *n = heredoc_node(varnode); + // Process heredoc doc field as cons list string + gen_string(s, n->info.doc, val); +} + +static void +codegen_dsym(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_str_node *n = dsym_node(varnode); + // Generate the list content, then intern to symbol + gen_string(s, n->list, val); + if (val) { + gen_intern(s); + } +} + +static void +codegen_nth_ref(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_nth_ref_node *n = (struct mrb_ast_nth_ref_node*)varnode; + mrb_state *mrb = s->mrb; + mrb_value str; + int sym; + + str = mrb_format(mrb, "$%d", n->nth); + sym = new_sym(s, mrb_intern_str(mrb, str)); + gen_load_op2(s, OP_GETGV, sym, val); +} + +static void +codegen_back_ref(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_back_ref_node *n = (struct mrb_ast_back_ref_node*)varnode; + char buf[] = {'$', (char)n->type}; + int sym = new_sym(s, mrb_intern(s->mrb, buf, sizeof(buf))); + gen_load_op2(s, OP_GETGV, sym, val); +} + +static void +codegen_nvar(codegen_scope *s, node *varnode, int val) +{ + if (!val) return; + struct mrb_ast_nvar_node *n = (struct mrb_ast_nvar_node*)varnode; + + gen_move(s, cursp(), n->num, val); + push(); +} + +static void +codegen_dvar(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_dvar_node *n = (struct mrb_ast_dvar_node*)varnode; + // DVAR nodes are not currently used in mruby, but provide basic implementation + if (val) { + gen_lvar(s, n->name, val); + } +} + +/* Unary operator codegen functions */ +static void +codegen_not(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_not_node *n = (struct mrb_ast_not_node*)varnode; + // NOT nodes are rarely used - generate method call to ! + if (val) { + codegen(s, n->operand, TRUE); + pop(); + mrb_sym sym = new_sym(s, mrb_intern_lit(s->mrb, "!")); + genop_3(s, OP_SEND, cursp(), sym, 0); + push(); + } +} + +static void +codegen_negate(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_negate_node *n = (struct mrb_ast_negate_node*)varnode; + node *tree = n->operand; + + /* Check if the operand is a variable-sized node */ + enum node_type vnt = get_node_type(tree); + switch (vnt) { +#ifndef MRB_NO_FLOAT + case NODE_FLOAT: + if (val) { + struct mrb_ast_float_node *float_n = (struct mrb_ast_float_node*)tree; + const char *value = float_n->value; + double f; + + mrb_read_float(value, NULL, &f); + int off = new_lit_float(s, (mrb_float)-f); + + gen_load_lit(s, off); + } + break; +#endif + + case NODE_INT: + if (val) { + int32_t value = int_node(tree)->value; + if (value == INT32_MIN) { + /* -INT32_MIN overflows, use bigint */ + int off = new_litbint(s, "2147483648", -10); + genop_2(s, OP_LOADL, cursp(), off); + } + else { + gen_int(s, cursp(), -value); + } + push(); + } + break; + + case NODE_BIGINT: + if (val) { + char *str = bigint_node(tree)->string; + int base = bigint_node(tree)->base; + /* Negate base to indicate negative number */ + int off = new_litbint(s, str, -base); + genop_2(s, OP_LOADL, cursp(), off); + push(); + } + break; + + default: + codegen(s, tree, VAL); + pop(); + push_n(2);pop_n(2); /* space for receiver&block */ + mrb_sym minus = MRB_OPSYM_2(s->mrb, minus); + if (!gen_uniop(s, minus, cursp())) { + genop_3(s, OP_SEND, cursp(), new_sym(s, minus), 0); + } + if (val) push(); + break; + } +} + +static void +codegen_colon2(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_colon2_node *n = (struct mrb_ast_colon2_node*)varnode; + // Generate COLON2 (::) access manually + int sym = new_sym(s, n->name); + codegen(s, n->base, VAL); + pop(); + genop_2(s, OP_GETMCNST, cursp(), sym); + if (val) push(); +} + +static void +codegen_colon3(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_colon3_node *n = (struct mrb_ast_colon3_node*)varnode; + int sym = new_sym(s, n->name); + genop_1(s, OP_OCLASS, cursp()); + genop_2(s, OP_GETMCNST, cursp(), sym); + if (val) push(); +} + +static void +codegen_defined(codegen_scope *s, node *varnode, int val) +{ + // DEFINED nodes are rarely used - generate basic implementation + (void)varnode; // suppress unused warning + if (val) { + // For now, just return nil (defined? is complex to implement correctly) + genop_1(s, OP_LOADNIL, cursp()); + push(); + } +} + +static void +codegen_zsuper(codegen_scope *s, node *varnode, int val) +{ + /* NODE_ZSUPER now uses mrb_ast_super_node, which may have args */ + struct mrb_ast_super_node *zsuper_n = super_node(varnode); + node *tree = zsuper_n->args; /* May be NULL or args added by call_with_block */ + + codegen_scope *s2 = s; + int lv = 0; + uint16_t ainfo = 0; + int n = CALL_MAXARGS; + int sp = cursp(); + mrb_bool has_block_arg = FALSE; + + push(); /* room for receiver */ + int argary_pos = cursp(); + while (!s2->mscope) { + lv++; + s2 = s2->prev; + if (!s2) break; + } + if (s2 && s2->ainfo > 0) { + ainfo = s2->ainfo; + has_block_arg = (ainfo >> 13) & 0x1; + } + if (lv > 0xf) codegen_error(s, "too deep nesting"); + if (ainfo > 0) { + genop_2S(s, OP_ARGARY, argary_pos, (ainfo<<4)|(lv & 0xf)); + push(); push(); push(); /* ARGARY pushes 3 values at most */ + pop(); pop(); pop(); + /* keyword arguments */ + if (ainfo & 0x1) { + n |= CALL_MAXARGS<<4; + push(); + /* If parent has keywords but no block parameter, ARGARY reads garbage for block */ + if (!has_block_arg) { + genop_1(s, OP_LOADNIL, argary_pos+2); + } + } + /* block argument - tree here is args, so check for block */ + if (tree) { + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)tree; + if (callargs->block_arg) { + push(); + codegen(s, callargs->block_arg, VAL); + } + } + } + else { + /* block argument */ + if (tree) { + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)tree; + if (callargs->block_arg) { + codegen(s, callargs->block_arg, VAL); + } + } + else if (s2) { + gen_blkmove(s, 0, lv); + } + else { + genop_1(s, OP_LOADNIL, cursp()); + } + n = 0; + } + s->sp = sp; + genop_2(s, OP_SUPER, cursp(), n); + if (val) push(); +} + +static void +codegen_lambda(codegen_scope *s, node *varnode, int val) +{ + if (!val) return; + + struct mrb_ast_lambda_node *n = lambda_node(varnode); + + /* Call lambda_body directly with individual parameters */ + int idx = lambda_body(s, n->locals, n->args, n->body, 1); + genop_2(s, OP_LAMBDA, cursp(), idx); + push(); +} + +static void +codegen_words(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_words_node *n = words_node(varnode); + gen_literal_array(s, n->args, FALSE, val); +} + +static void +codegen_symbols(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_symbols_node *n = symbols_node(varnode); + gen_literal_array(s, n->args, TRUE, val); +} + +static void +codegen_splat(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_splat_node *n = splat_node(varnode); + // Generate code for the splat value directly + codegen(s, n->value, val); +} + +static void +codegen_block_arg(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_block_arg_node *n = block_arg_node(varnode); + + if (!n->value) { + int idx = lv_idx(s, MRB_OPSYM_2(s->mrb, and)); + + if (idx == 0) { + gen_getupvar(s, cursp(), MRB_OPSYM_2(s->mrb, and)); + } + else { + gen_move(s, cursp(), idx, val); + } + if (val) push(); + } + else { + codegen(s, n->value, val); + } +} + +static void +codegen_scope_node(codegen_scope *s, const node *varnode, int val) +{ + struct mrb_ast_scope_node *scope = scope_node(varnode); + + /* Pass locals and body directly to scope_body() */ + scope_body(s, scope->locals, scope->body, NOVAL); +} + +static void +codegen_begin(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_begin_node *begin = begin_node(varnode); + node *body = begin->body; + + codegen(s, body, val); +} + +static void +codegen_ensure(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_ensure_node *ensure = ensure_node(varnode); + node *body = ensure->body; + node *ensure_clause = ensure->ensure_clause; + + if (!ensure_clause || !is_empty_stmts(ensure_clause)) { + int catch_entry, begin, end, target; + int idx; + + catch_entry = catch_handler_new(s); + begin = s->pc; + codegen(s, body, val); + end = target = s->pc; + push(); + idx = cursp(); + genop_1(s, OP_EXCEPT, idx); + push(); + codegen(s, ensure_clause, NOVAL); + pop(); + genop_1(s, OP_RAISEIF, idx); + pop(); + catch_handler_set(s, catch_entry, MRB_CATCH_ENSURE, begin, end, target); + } + else { /* empty ensure ignored */ + codegen(s, body, val); + } +} + +static void +codegen_stmts(codegen_scope *s, node *varnode, int val) +{ + struct mrb_ast_stmts_node *stmts = stmts_node(varnode); + node *tree = stmts_node(stmts)->stmts; + + if (val && !tree) { + gen_load_nil(s, 1); + } + while (tree) { + codegen(s, tree->car, tree->cdr ? NOVAL : val); + tree = tree->cdr; + } +} + +static mrb_bool +is_empty_stmts(node *stmt_node) +{ + if (!stmt_node) return TRUE; + + if (get_node_type(stmt_node) == NODE_STMTS) { + /* Variable-sized NODE_STMTS with internal cons-list */ + struct mrb_ast_stmts_node *stmts = (struct mrb_ast_stmts_node*)stmt_node; + return stmts->stmts == NULL; + } + + return FALSE; +} + +/* Declaration codegen functions */ + +static void +codegen_alias(codegen_scope *s, const node *varnode, int val) +{ + struct mrb_ast_alias_node *alias = alias_node(varnode); + + int a = new_sym(s, alias->new_name); + int b = new_sym(s, alias->old_name); + + genop_2(s, OP_ALIAS, a, b); + gen_load_nil(s, val); +} + +static void +codegen_undef(codegen_scope *s, const node *varnode, int val) +{ + struct mrb_ast_undef_node *undef = undef_node(varnode); + node *t = undef->syms; + + while (t) { + int symbol = new_sym(s, node_to_sym(t->car)); + genop_1(s, OP_UNDEF, symbol); + t = t->cdr; + } + gen_load_nil(s, val); +} + +static void +codegen_sdef(codegen_scope *s, const node *varnode, int val) +{ + struct mrb_ast_sdef_node *sdef = sdef_node(varnode); + node *recv = sdef->obj; + int sym = new_sym(s, sdef->name); + + /* Call lambda_body directly with individual parameters */ + /* For NODE_SDEF, args should contain the full locals structure from defs_setup */ + int idx = lambda_body(s, sdef->locals, sdef->args, sdef->body, 0); + + codegen(s, recv, VAL); + pop(); + genop_1(s, OP_SCLASS, cursp()); + push(); + genop_2(s, OP_METHOD, cursp(), idx); + push(); pop(); + pop(); + genop_2(s, OP_DEF, cursp(), sym); + if (val) push(); +} + + static void codegen(codegen_scope *s, node *tree, int val) { - int nt; int rlev = s->rlev; if (!tree) { @@ -3330,1572 +5630,337 @@ codegen(codegen_scope *s, node *tree, int val) if (s->rlev > MRB_CODEGEN_LEVEL_MAX) { codegen_error(s, "too complex expression"); } - if (s->irep && s->filename_index != tree->filename_index) { + + /* Check if this is a variable-sized node */ + /* For variable-sized nodes, get filename/lineno from the variable node header */ + struct mrb_ast_var_header *var_head = get_var_header(tree); + + if (s->irep && s->filename_index != var_head->filename_index) { mrb_sym fname = mrb_parser_get_filename(s->parser, s->filename_index); const char *filename = mrb_sym_name_len(s->mrb, fname, NULL); - mrb_debug_info_append_file(s->mrb, s->irep->debug_info, - filename, s->lines, s->debug_start_pos, s->pc); + if (filename) { + mrb_debug_info_append_file(s->mrb, s->irep->debug_info, + filename, s->lines, s->debug_start_pos, s->pc); + } s->debug_start_pos = s->pc; - s->filename_index = tree->filename_index; - s->filename_sym = mrb_parser_get_filename(s->parser, tree->filename_index); + s->filename_index = var_head->filename_index; + s->filename_sym = mrb_parser_get_filename(s->parser, var_head->filename_index); } + s->lineno = var_head->lineno; - nt = nint(tree->car); - s->lineno = tree->lineno; - tree = tree->cdr; - switch (nt) { - case NODE_STMTS: - if (val && !tree) { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - while (tree) { - codegen(s, tree->car, tree->cdr ? NOVAL : val); - tree = tree->cdr; - } - break; - - case NODE_BEGIN: - /* NODE_BEGIN contains a single body node directly in cdr */ - codegen(s, tree, val); - break; - - case NODE_RESCUE: - { - int noexc; - uint32_t exend, pos1, pos2, tmp; - struct loopinfo *lp; - int catch_entry, begin, end; - - if (tree->car == NULL) goto exit; - lp = loop_push(s, LOOP_BEGIN); - lp->pc0 = new_label(s); - catch_entry = catch_handler_new(s); - begin = s->pc; - codegen(s, tree->car, VAL); - pop(); - lp->type = LOOP_RESCUE; - end = s->pc; - noexc = genjmp_0(s, OP_JMP); - catch_handler_set(s, catch_entry, MRB_CATCH_RESCUE, begin, end, s->pc); - tree = tree->cdr; - exend = JMPLINK_START; - pos1 = JMPLINK_START; - if (tree->car) { - node *n2 = tree->car; - int exc = cursp(); - - genop_1(s, OP_EXCEPT, exc); - push(); - while (n2) { - node *n3 = n2->car; - node *n4 = n3->car; - - dispatch(s, pos1); - pos2 = JMPLINK_START; - do { - if (n4 && n4->car && nint(n4->car->car) == NODE_SPLAT) { - codegen(s, n4->car, VAL); - gen_move(s, cursp(), exc, 0); - push_n(2); pop_n(2); /* space for one arg and a block */ - pop(); - genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_SYM_2(s->mrb, __case_eqq)), 1); - } - else { - if (n4) { - codegen(s, n4->car, VAL); - } - else { - genop_2(s, OP_GETCONST, cursp(), new_sym(s, MRB_SYM_2(s->mrb, StandardError))); - push(); - } - pop(); - genop_2(s, OP_RESCUE, exc, cursp()); - } - tmp = genjmp2(s, OP_JMPIF, cursp(), pos2, val); - pos2 = tmp; - if (n4) { - n4 = n4->cdr; - } - } while (n4); - pos1 = genjmp_0(s, OP_JMP); - dispatch_linked(s, pos2); - - pop(); - if (n3->cdr->car) { - gen_assignment(s, n3->cdr->car, NULL, exc, NOVAL); - } - if (n3->cdr->cdr->car) { - codegen(s, n3->cdr->cdr->car, val); - if (val) pop(); - } - tmp = genjmp(s, OP_JMP, exend); - exend = tmp; - n2 = n2->cdr; - push(); - } - if (pos1 != JMPLINK_START) { - dispatch(s, pos1); - genop_1(s, OP_RAISEIF, exc); - } - } - pop(); - tree = tree->cdr; - dispatch(s, noexc); - if (tree->car) { - codegen(s, tree->car, val); - } - else if (val) { - push(); - } - dispatch_linked(s, exend); - loop_pop(s, NOVAL); - } - break; - - case NODE_ENSURE: - if (!tree->cdr || !tree->cdr->cdr || - (nint(tree->cdr->cdr->car) == NODE_STMTS && - tree->cdr->cdr->cdr)) { - int catch_entry, begin, end, target; - int idx; - - catch_entry = catch_handler_new(s); - begin = s->pc; - codegen(s, tree->car, val); - end = target = s->pc; - push(); - idx = cursp(); - genop_1(s, OP_EXCEPT, idx); - push(); - codegen(s, tree->cdr->cdr, NOVAL); - pop(); - genop_1(s, OP_RAISEIF, idx); - pop(); - catch_handler_set(s, catch_entry, MRB_CATCH_ENSURE, begin, end, target); - } - else { /* empty ensure ignored */ - codegen(s, tree->car, val); - } - break; - - case NODE_LAMBDA: - if (val) { - int idx = lambda_body(s, tree, 1); - - genop_2(s, OP_LAMBDA, cursp(), idx); - push(); - } - break; - - case NODE_BLOCK: - if (val) { - int idx = lambda_body(s, tree, 1); - - genop_2(s, OP_BLOCK, cursp(), idx); - push(); - } - break; - - case NODE_IF: - { - uint32_t pos1, pos2; - mrb_bool nil_p = FALSE; - node *elsepart = tree->cdr->cdr->car; - - if (!tree->car) { - codegen(s, elsepart, val); - goto exit; - } - if (true_always(tree->car)) { - codegen(s, tree->cdr->car, val); - goto exit; - } - if (false_always(tree->car)) { - codegen(s, elsepart, val); - goto exit; - } - if (nint(tree->car->car) == NODE_CALL) { - node *n = tree->car->cdr; - mrb_sym mid = nsym(n->cdr->car); - mrb_sym sym_nil_p = MRB_SYM_Q_2(s->mrb, nil); - if (mid == sym_nil_p && n->cdr->cdr->car == NULL) { - nil_p = TRUE; - codegen(s, n->car, VAL); - } - } - if (!nil_p) { - codegen(s, tree->car, VAL); - } - pop(); - if (val || tree->cdr->car) { - if (nil_p) { - pos2 = genjmp2_0(s, OP_JMPNIL, cursp(), val); - pos1 = genjmp_0(s, OP_JMP); - dispatch(s, pos2); - } - else { - pos1 = genjmp2_0(s, OP_JMPNOT, cursp(), val); - } - codegen(s, tree->cdr->car, val); - if (val) pop(); - if (elsepart || val) { - pos2 = genjmp_0(s, OP_JMP); - dispatch(s, pos1); - codegen(s, elsepart, val); - dispatch(s, pos2); - } - else { - dispatch(s, pos1); - } - } - else { /* empty then-part */ - if (elsepart) { - if (nil_p) { - pos1 = genjmp2_0(s, OP_JMPNIL, cursp(), val); - } - else { - pos1 = genjmp2_0(s, OP_JMPIF, cursp(), val); - } - codegen(s, elsepart, val); - dispatch(s, pos1); - } - else if (val && !nil_p) { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - } - } - break; - - case NODE_AND: - { - uint32_t pos; - - if (true_always(tree->car)) { - codegen(s, tree->cdr, val); - goto exit; - } - if (false_always(tree->car)) { - codegen(s, tree->car, val); - goto exit; - } - codegen(s, tree->car, VAL); - pop(); - pos = genjmp2_0(s, OP_JMPNOT, cursp(), val); - codegen(s, tree->cdr, val); - dispatch(s, pos); - } - break; - - case NODE_OR: - { - uint32_t pos; - - if (true_always(tree->car)) { - codegen(s, tree->car, val); - goto exit; - } - if (false_always(tree->car)) { - codegen(s, tree->cdr, val); - goto exit; - } - codegen(s, tree->car, VAL); - pop(); - pos = genjmp2_0(s, OP_JMPIF, cursp(), val); - codegen(s, tree->cdr, val); - dispatch(s, pos); - } - break; - - case NODE_WHILE_MOD: - case NODE_UNTIL_MOD: - /* Post-tested loops: execute body first, then check condition */ - if (false_always(tree->car)) { - if (nt == NODE_WHILE_MOD) { - /* begin...end while false - execute once then exit */ - codegen(s, tree->cdr, val); - if (val) push(); - goto exit; - } - } - else if (true_always(tree->car)) { - if (nt == NODE_UNTIL_MOD) { - /* begin...end until true - execute once then exit */ - codegen(s, tree->cdr, val); - if (val) push(); - goto exit; - } - } - genjmp_0(s, OP_JMP); - /* fall through */ - case NODE_WHILE: - case NODE_UNTIL: - { - uint32_t pos0 = JMPLINK_START; - if (nt == NODE_WHILE_MOD || nt == NODE_UNTIL_MOD) { - pos0 = s->pc - mrb_insn_size[OP_JMP] + 1; - } - - if (true_always(tree->car)) { - if (nt == NODE_UNTIL) { - if (val) { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - goto exit; - } - } - else if (false_always(tree->car)) { - if (nt == NODE_WHILE) { - if (val) { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - goto exit; - } - } - - struct loopinfo *lp = loop_push(s, LOOP_NORMAL); - - if (!val) lp->reg = -1; - lp->pc0 = new_label(s); - codegen(s, tree->car, VAL); - pop(); - - uint32_t pos; - if (nt == NODE_WHILE || nt == NODE_WHILE_MOD) { - pos = genjmp2_0(s, OP_JMPNOT, cursp(), NOVAL); - } - else { /* UNTIL */ - pos = genjmp2_0(s, OP_JMPIF, cursp(), NOVAL); - } - lp->pc1 = new_label(s); - genop_0(s, OP_NOP); /* for redo */ - dispatch(s, pos0); - codegen(s, tree->cdr, NOVAL); - genjmp(s, OP_JMP, lp->pc0); - dispatch(s, pos); - loop_pop(s, val); - } - break; - - case NODE_FOR: - for_body(s, tree); - if (val) push(); - break; - - case NODE_CASE: - { - int head = 0; - uint32_t pos1, pos2, pos3, tmp; - node *n; - - pos3 = JMPLINK_START; - if (tree->car) { - head = cursp(); - codegen(s, tree->car, VAL); - } - tree = tree->cdr; - while (tree) { - n = tree->car->car; - pos1 = pos2 = JMPLINK_START; - while (n) { - codegen(s, n->car, VAL); - if (head) { - gen_move(s, cursp(), head, 0); - push(); push(); pop(); pop(); pop(); - if (nint(n->car->car) == NODE_SPLAT) { - genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_SYM_2(s->mrb, __case_eqq)), 1); - } - else { - genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, eqq)), 1); - } - } - else { - pop(); - } - tmp = genjmp2(s, OP_JMPIF, cursp(), pos2, !head); - pos2 = tmp; - n = n->cdr; - } - if (tree->car->car) { - pos1 = genjmp_0(s, OP_JMP); - dispatch_linked(s, pos2); - } - codegen(s, tree->car->cdr, val); - if (val) pop(); - tmp = genjmp(s, OP_JMP, pos3); - pos3 = tmp; - dispatch(s, pos1); - tree = tree->cdr; - } - if (val) { - uint32_t pos = cursp(); - genop_1(s, OP_LOADNIL, cursp()); - if (pos3 != JMPLINK_START) dispatch_linked(s, pos3); - if (head) pop(); - if (cursp() != pos) { - gen_move(s, cursp(), pos, 0); - } - push(); - } - else { - if (pos3 != JMPLINK_START) { - dispatch_linked(s, pos3); - } - if (head) { - pop(); - } - } - } - break; - - case NODE_SCOPE: - scope_body(s, tree, NOVAL); - break; - - case NODE_CALL: - case NODE_FCALL: - gen_call(s, tree, val, 0); - break; - case NODE_SCALL: - gen_call(s, tree, val, 1); - break; - - case NODE_DOT2: - codegen(s, tree->car, val); - codegen(s, tree->cdr, val); - if (val) { - pop(); pop(); - genop_1(s, OP_RANGE_INC, cursp()); - push(); - } - break; - - case NODE_DOT3: - codegen(s, tree->car, val); - codegen(s, tree->cdr, val); - if (val) { - pop(); pop(); - genop_1(s, OP_RANGE_EXC, cursp()); - push(); - } - break; - - case NODE_COLON2: - { - int sym = new_sym(s, nsym(tree->cdr)); - - codegen(s, tree->car, VAL); - pop(); - genop_2(s, OP_GETMCNST, cursp(), sym); - if (val) push(); - } - break; - - case NODE_COLON3: - { - int sym = new_sym(s, nsym(tree)); - - genop_1(s, OP_OCLASS, cursp()); - genop_2(s, OP_GETMCNST, cursp(), sym); - if (val) push(); - } - break; - - case NODE_ARRAY: - { - int n; - - n = gen_values(s, tree, val, 0); - if (val) { - if (n >= 0) { - pop_n(n); - genop_2(s, OP_ARRAY, cursp(), n); - } - push(); - } - } - break; - - case NODE_HASH: - case NODE_KW_HASH: - { - int nk = gen_hash(s, tree, val, GEN_LIT_ARY_MAX); - if (val && nk >= 0) { - pop_n(nk*2); - genop_2(s, OP_HASH, cursp(), nk); - push(); - } - } - break; - - case NODE_SPLAT: - codegen(s, tree, val); - break; - - case NODE_ASGN: - gen_assignment(s, tree->car, tree->cdr, 0, val); - break; - - case NODE_MASGN: - { - int len = 0, n = 0, post = 0; - node *t = tree->cdr, *p; - int rhs = cursp(); - - if (!val && nint(t->car) == NODE_ARRAY && t->cdr && nosplat(t->cdr)) { - /* fixed rhs */ - t = t->cdr; - while (t) { - codegen(s, t->car, VAL); - len++; - t = t->cdr; - } - tree = tree->car; - if (tree->car) { /* pre */ - t = tree->car; - n = 0; - while (t) { - if (n < len) { - gen_assignment(s, t->car, NULL, rhs+n, NOVAL); - n++; - } - else { - genop_1(s, OP_LOADNIL, rhs+n); - gen_assignment(s, t->car, NULL, rhs+n, NOVAL); - } - t = t->cdr; - } - } - t = tree->cdr; - if (t) { - if (t->cdr) { /* post count */ - p = t->cdr->car; - while (p) { - post++; - p = p->cdr; - } - } - if (t->car) { /* rest (len - pre - post) */ - int rn; - - if (len < post + n) { - rn = 0; - } - else { - rn = len - post - n; - } - if (cursp() == rhs+n) { - genop_2(s, OP_ARRAY, cursp(), rn); - } - else { - genop_3(s, OP_ARRAY2, cursp(), rhs+n, rn); - } - gen_assignment(s, t->car, NULL, cursp(), NOVAL); - n += rn; - } - if (t->cdr && t->cdr->car) { - t = t->cdr->car; - while (t) { - if (ncar, NULL, rhs+n, NOVAL); - } - else { - genop_1(s, OP_LOADNIL, cursp()); - gen_assignment(s, t->car, NULL, cursp(), NOVAL); - } - t = t->cdr; - n++; - } - } - } - pop_n(len); - } - else { - /* variable rhs */ - codegen(s, t, VAL); - gen_massignment(s, tree->car, rhs, val); - if (!val) { - pop(); - } - } - } - break; - - case NODE_OP_ASGN: - { - mrb_sym sym = nsym(tree->cdr->car); - mrb_int len; - const char *name = mrb_sym_name_len(s->mrb, sym, &len); - int idx, callargs = -1, vsp = -1; - - if ((len == 2 && name[0] == '|' && name[1] == '|') && - (nint(tree->car->car) == NODE_CONST || - nint(tree->car->car) == NODE_CVAR)) { - int catch_entry, begin, end; - int noexc, exc; - struct loopinfo *lp; - - lp = loop_push(s, LOOP_BEGIN); - lp->pc0 = new_label(s); - catch_entry = catch_handler_new(s); - begin = s->pc; - exc = cursp(); - codegen(s, tree->car, VAL); - end = s->pc; - noexc = genjmp_0(s, OP_JMP); - lp->type = LOOP_RESCUE; - catch_handler_set(s, catch_entry, MRB_CATCH_RESCUE, begin, end, s->pc); - genop_1(s, OP_EXCEPT, exc); - genop_1(s, OP_LOADF, exc); - dispatch(s, noexc); - loop_pop(s, NOVAL); - } - else if (nint(tree->car->car) == NODE_CALL) { - node *n = tree->car->cdr; - int base, i, nargs = 0; - callargs = 0; - - if (val) { - vsp = cursp(); - push(); - } - codegen(s, n->car, VAL); /* receiver */ - idx = new_sym(s, nsym(n->cdr->car)); - base = cursp()-1; - if (n->cdr->cdr->car) { - nargs = gen_values(s, n->cdr->cdr->car->car, VAL, 13); - if (nargs >= 0) { - callargs = nargs; - } - else { /* varargs */ - push(); - nargs = 1; - callargs = CALL_MAXARGS; - } - } - /* copy receiver and arguments */ - gen_move(s, cursp(), base, 1); - for (i=0; icar, VAL); - } - if (len == 2 && - ((name[0] == '|' && name[1] == '|') || - (name[0] == '&' && name[1] == '&'))) { - uint32_t pos; - - pop(); - if (val) { - if (vsp >= 0) { - gen_move(s, vsp, cursp(), 1); - } - pos = genjmp2_0(s, name[0]=='|'?OP_JMPIF:OP_JMPNOT, cursp(), val); - } - else { - pos = genjmp2_0(s, name[0]=='|'?OP_JMPIF:OP_JMPNOT, cursp(), val); - } - codegen(s, tree->cdr->cdr->car, VAL); - pop(); - if (val && vsp >= 0) { - gen_move(s, vsp, cursp(), 1); - } - if (nint(tree->car->car) == NODE_CALL) { - if (callargs == CALL_MAXARGS) { - pop(); - genop_2(s, OP_ARYPUSH, cursp(), 1); - } - else { - pop_n(callargs); - callargs++; - } - pop(); - idx = new_sym(s, attrsym(s, nsym(tree->car->cdr->cdr->car))); - genop_3(s, OP_SEND, cursp(), idx, callargs); - } - else { - gen_assignment(s, tree->car, NULL, cursp(), val); - } - dispatch(s, pos); - goto exit; - } - codegen(s, tree->cdr->cdr->car, VAL); - push(); pop(); - pop(); pop(); - - if (len == 1 && name[0] == '+') { - gen_addsub(s, OP_ADD, cursp()); - } - else if (len == 1 && name[0] == '-') { - gen_addsub(s, OP_SUB, cursp()); - } - else if (len == 1 && name[0] == '*') { - genop_1(s, OP_MUL, cursp()); - } - else if (len == 1 && name[0] == '/') { - genop_1(s, OP_DIV, cursp()); - } - else if (len == 1 && name[0] == '<') { - genop_1(s, OP_LT, cursp()); - } - else if (len == 2 && name[0] == '<' && name[1] == '=') { - genop_1(s, OP_LE, cursp()); - } - else if (len == 1 && name[0] == '>') { - genop_1(s, OP_GT, cursp()); - } - else if (len == 2 && name[0] == '>' && name[1] == '=') { - genop_1(s, OP_GE, cursp()); - } - else { - idx = new_sym(s, sym); - genop_3(s, OP_SEND, cursp(), idx, 1); - } - if (callargs < 0) { - gen_assignment(s, tree->car, NULL, cursp(), val); - } - else { - if (val && vsp >= 0) { - gen_move(s, vsp, cursp(), 0); - } - if (callargs == CALL_MAXARGS) { - pop(); - genop_2(s, OP_ARYPUSH, cursp(), 1); - } - else { - pop_n(callargs); - callargs++; - } - pop(); - idx = new_sym(s, attrsym(s,nsym(tree->car->cdr->cdr->car))); - genop_3(s, OP_SEND, cursp(), idx, callargs); - } - } - break; - - case NODE_SUPER: - { - codegen_scope *s2 = s; - int lv = 0; - int n = 0, nk = 0, st = 0; - - push(); - while (!s2->mscope) { - lv++; - s2 = s2->prev; - if (!s2) break; - } - if (tree) { - node *args = tree->car; - if (args) { - st = n = gen_values(s, args, VAL, 14); - if (n < 0) { - st = 1; n = 15; - push(); - } - } - /* keyword arguments */ - if (tree->cdr->car) { - nk = gen_hash(s, tree->cdr->car->cdr, VAL, 14); - if (nk < 0) {st++; nk = 15;} - else st += nk*2; - n |= nk<<4; - } - /* block arguments */ - if (tree->cdr->cdr) { - codegen(s, tree->cdr->cdr, VAL); - } - else if (s2) gen_blkmove(s, s2->ainfo, lv); - else { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - } - else { - if (s2) gen_blkmove(s, s2->ainfo, lv); - else { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - } - st++; - pop_n(st+1); - genop_2(s, OP_SUPER, cursp(), n); - if (val) push(); - } - break; - - case NODE_ZSUPER: - { - codegen_scope *s2 = s; - int lv = 0; - uint16_t ainfo = 0; - int n = CALL_MAXARGS; - int sp = cursp(); - - push(); /* room for receiver */ - while (!s2->mscope) { - lv++; - s2 = s2->prev; - if (!s2) break; - } - if (s2 && s2->ainfo > 0) { - ainfo = s2->ainfo; - } - if (lv > 0xf) codegen_error(s, "too deep nesting"); - if (ainfo > 0) { - genop_2S(s, OP_ARGARY, cursp(), (ainfo<<4)|(lv & 0xf)); - push(); push(); push(); /* ARGARY pushes 3 values at most */ - pop(); pop(); pop(); - /* keyword arguments */ - if (ainfo & 0x1) { - n |= CALL_MAXARGS<<4; - push(); - } - /* block argument */ - if (tree && tree->cdr && tree->cdr->cdr) { - push(); - codegen(s, tree->cdr->cdr, VAL); - } - } - else { - /* block argument */ - if (tree && tree->cdr && tree->cdr->cdr) { - codegen(s, tree->cdr->cdr, VAL); - } - else if (s2) { - gen_blkmove(s, 0, lv); - } - else { - genop_1(s, OP_LOADNIL, cursp()); - } - n = 0; - } - s->sp = sp; - genop_2(s, OP_SUPER, cursp(), n); - if (val) push(); - } - break; - - case NODE_RETURN: - if (tree) { - gen_retval(s, tree); - } - else { - genop_1(s, OP_LOADNIL, cursp()); - } - if (s->loop) { - gen_return(s, OP_RETURN_BLK, cursp()); - } - else { - gen_return(s, OP_RETURN, cursp()); - } - if (val) push(); - break; - - case NODE_YIELD: - { - codegen_scope *s2 = s; - int lv = 0, ainfo = -1; - int n = 0, nk = 0, sendv = 0; - - while (!s2->mscope) { - lv++; - s2 = s2->prev; - if (!s2) break; - } - if (s2) { - ainfo = (int)s2->ainfo; - } - if (ainfo < 0) codegen_error(s, "invalid yield (SyntaxError)"); - if (lv > 0xf) codegen_error(s, "too deep nesting"); - push(); - if (tree) { - if (tree->car) { - n = gen_values(s, tree->car, VAL, 14); - if (n < 0) { - n = sendv = 1; - push(); - } - } - - if (tree->cdr->car) { - nk = gen_hash(s, tree->cdr->car->cdr, VAL, 14); - if (nk < 0) { - nk = 15; - } - } - } - push();pop(); /* space for a block */ - pop_n(n + (nk == 15 ? 1 : nk * 2) + 1); - genop_2S(s, OP_BLKPUSH, cursp(), (ainfo<<4)|(lv & 0xf)); - if (sendv) n = CALL_MAXARGS; - genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_SYM_2(s->mrb, call)), n|(nk<<4)); - if (val) push(); - } - break; - - case NODE_BREAK: - loop_break(s, tree); - if (val) push(); - break; - - case NODE_NEXT: - if (!s->loop) { - raise_error(s, "unexpected next"); - } - else if (s->loop->type == LOOP_NORMAL) { - codegen(s, tree, NOVAL); - genjmp(s, OP_JMPUW, s->loop->pc0); - } - else { - if (tree) { - codegen(s, tree, VAL); - pop(); - } - else { - genop_1(s, OP_LOADNIL, cursp()); - } - gen_return(s, OP_RETURN, cursp()); - } - if (val) push(); - break; - - case NODE_REDO: - for (const struct loopinfo *lp = s->loop; ; lp = lp->prev) { - if (!lp) { - raise_error(s, "unexpected redo"); - break; - } - if (lp->type != LOOP_BEGIN && lp->type != LOOP_RESCUE) { - genjmp(s, OP_JMPUW, lp->pc1); - break; - } - } - if (val) push(); - break; - - case NODE_RETRY: - { - const struct loopinfo *lp = s->loop; - - while (lp && lp->type != LOOP_RESCUE) { - lp = lp->prev; - } - if (!lp) { - raise_error(s, "unexpected retry"); - break; - } - else { - genjmp(s, OP_JMPUW, lp->pc0); - } - if (val) push(); - } - break; - - case NODE_LVAR: - if (val) { - int idx = lv_idx(s, nsym(tree)); - - if (idx > 0) { - gen_move(s, cursp(), idx, val); - } - else { - gen_getupvar(s, cursp(), nsym(tree)); - } - push(); - } - break; - - case NODE_NVAR: - if (val) { - int idx = nint(tree); - - gen_move(s, cursp(), idx, val); - - push(); - } - break; - - case NODE_GVAR: - { - int sym = new_sym(s, nsym(tree)); - - genop_2(s, OP_GETGV, cursp(), sym); - if (val) push(); - } - break; - - case NODE_IVAR: - { - int sym = new_sym(s, nsym(tree)); - - genop_2(s, OP_GETIV, cursp(), sym); - if (val) push(); - } - break; - - case NODE_CVAR: - { - int sym = new_sym(s, nsym(tree)); - - genop_2(s, OP_GETCV, cursp(), sym); - if (val) push(); - } - break; - - case NODE_CONST: - { - int sym = new_sym(s, nsym(tree)); - - genop_2(s, OP_GETCONST, cursp(), sym); - if (val) push(); - } - break; - - case NODE_BACK_REF: - if (val) { - char buf[] = {'$', nchar(tree)}; - int sym = new_sym(s, mrb_intern(s->mrb, buf, sizeof(buf))); - - genop_2(s, OP_GETGV, cursp(), sym); - push(); - } - break; - - case NODE_NTH_REF: - if (val) { - mrb_state *mrb = s->mrb; - mrb_value str; - int sym; - - str = mrb_format(mrb, "$%d", nint(tree)); - sym = new_sym(s, mrb_intern_str(mrb, str)); - genop_2(s, OP_GETGV, cursp(), sym); - push(); - } - break; - - case NODE_ARG: - /* should not happen */ - break; - - case NODE_BLOCK_ARG: - if (!tree) { - int idx = lv_idx(s, MRB_OPSYM_2(s->mrb, and)); - - if (idx == 0) { - gen_getupvar(s, cursp(), MRB_OPSYM_2(s->mrb, and)); - } - else { - gen_move(s, cursp(), idx, val); - } - if (val) push(); - } - else { - codegen(s, tree, val); - } - break; + /* Process variable-sized node directly */ + enum node_type var_type = (enum node_type)var_head->node_type; + switch (var_type) { case NODE_INT: if (val) { - char *p = (char*)tree->car; - int base = nint(tree->cdr->car); - mrb_int i; - mrb_bool overflow; - - i = readint(s, p, base, FALSE, &overflow); - if (overflow) { - int off = new_litbint(s, p, base); - genop_2(s, OP_LOADL, cursp(), off); - } - else { - gen_int(s, cursp(), i); - } + gen_int(s, cursp(), int_node(tree)->value); push(); } break; -#ifndef MRB_NO_FLOAT - case NODE_FLOAT: + case NODE_BIGINT: if (val) { - char *p = (char*)tree; - double f; - mrb_read_float(p, NULL, &f); - int off = new_lit_float(s, (mrb_float)f); - + char *str = bigint_node(tree)->string; + int base = bigint_node(tree)->base; + int off = new_litbint(s, str, base); genop_2(s, OP_LOADL, cursp(), off); push(); } break; -#endif - - case NODE_NEGATE: - { - nt = nint(tree->car); - switch (nt) { -#ifndef MRB_NO_FLOAT - case NODE_FLOAT: - if (val) { - char *p = (char*)tree->cdr; - double f; - mrb_read_float(p, NULL, &f); - int off = new_lit_float(s, (mrb_float)-f); - - genop_2(s, OP_LOADL, cursp(), off); - push(); - } - break; -#endif - - case NODE_INT: - if (val) { - char *p = (char*)tree->cdr->car; - int base = nint(tree->cdr->cdr->car); - mrb_int i; - mrb_bool overflow; - - i = readint(s, p, base, TRUE, &overflow); - if (overflow) { - base = -base; - int off = new_litbint(s, p, base); - genop_2(s, OP_LOADL, cursp(), off); - } - else { - gen_int(s, cursp(), i); - } - push(); - } - break; - - default: - codegen(s, tree, VAL); - pop(); - push_n(2);pop_n(2); /* space for receiver&block */ - mrb_sym minus = MRB_OPSYM_2(s->mrb, minus); - if (!gen_uniop(s, minus, cursp())) { - genop_3(s, OP_SEND, cursp(), new_sym(s, minus), 0); - } - if (val) push(); - break; - } - } - break; - - case NODE_STR: - if (val) { - char *p = (char*)tree->car; - mrb_int len = nint(tree->cdr); - int off = new_lit_str(s, p, len); - - genop_2(s, OP_STRING, cursp(), off); - push(); - } - break; - - case NODE_HEREDOC: - tree = ((struct mrb_parser_heredoc_info*)tree)->doc; - /* fall through */ - case NODE_DSTR: - if (val) { - node *n = tree; - - if (!n) { - genop_1(s, OP_LOADNIL, cursp()); - push(); - break; - } - codegen(s, n->car, VAL); - n = n->cdr; - while (n) { - codegen(s, n->car, VAL); - pop(); pop(); - genop_1(s, OP_STRCAT, cursp()); - push(); - n = n->cdr; - } - } - else { - node *n = tree; - - while (n) { - if (nint(n->car->car) != NODE_STR) { - codegen(s, n->car, NOVAL); - } - n = n->cdr; - } - } - break; - - case NODE_WORDS: - gen_literal_array(s, tree, FALSE, val); - break; - - case NODE_SYMBOLS: - gen_literal_array(s, tree, TRUE, val); - break; - - case NODE_DXSTR: - { - node *n; - int sym = new_sym(s, MRB_SYM_2(s->mrb, Kernel)); - - push(); - codegen(s, tree->car, VAL); - n = tree->cdr; - while (n) { - if (nint(n->car->car) == NODE_XSTR) { - n->car->car = (struct mrb_ast_node*)(intptr_t)NODE_STR; - mrb_assert(!n->cdr); /* must be the end */ - } - codegen(s, n->car, VAL); - pop(); pop(); - genop_1(s, OP_STRCAT, cursp()); - push(); - n = n->cdr; - } - push(); /* for block */ - pop_n(3); - sym = new_sym(s, MRB_OPSYM_2(s->mrb, tick)); /* ` */ - genop_3(s, OP_SSEND, cursp(), sym, 1); - if (val) push(); - } - break; - - case NODE_XSTR: - { - char *p = (char*)tree->car; - mrb_int len = nint(tree->cdr); - int off = new_lit_str(s, p, len); - int sym; - - push(); - genop_2(s, OP_STRING, cursp(), off); - push(); push(); - pop_n(3); - sym = new_sym(s, MRB_OPSYM_2(s->mrb, tick)); /* ` */ - genop_3(s, OP_SSEND, cursp(), sym, 1); - if (val) push(); - } - break; - - case NODE_REGX: - if (val) { - char *p1 = (char*)tree->car; - char *p2 = (char*)tree->cdr->car; - char *p3 = (char*)tree->cdr->cdr; - int sym = new_sym(s, mrb_intern_lit(s->mrb, REGEXP_CLASS)); - int off = new_lit_cstr(s, p1); - int argc = 1; - - genop_1(s, OP_OCLASS, cursp()); - genop_2(s, OP_GETMCNST, cursp(), sym); - push(); - genop_2(s, OP_STRING, cursp(), off); - push(); - if (p2 || p3) { - if (p2) { /* opt */ - off = new_lit_cstr(s, p2); - genop_2(s, OP_STRING, cursp(), off); - } - else { - genop_1(s, OP_LOADNIL, cursp()); - } - push(); - argc++; - if (p3) { /* enc */ - off = new_lit_str(s, p3, 1); - genop_2(s, OP_STRING, cursp(), off); - push(); - argc++; - } - } - push(); /* space for a block */ - pop_n(argc+2); - sym = new_sym(s, MRB_SYM_2(s->mrb, compile)); - genop_3(s, OP_SEND, cursp(), sym, argc); - push(); - } - break; - - case NODE_DREGX: - if (val) { - node *n = tree->car; - int sym = new_sym(s, mrb_intern_lit(s->mrb, REGEXP_CLASS)); - int argc = 1; - int off; - char *p; - - genop_1(s, OP_OCLASS, cursp()); - genop_2(s, OP_GETMCNST, cursp(), sym); - push(); - codegen(s, n->car, VAL); - n = n->cdr; - while (n) { - codegen(s, n->car, VAL); - pop(); pop(); - genop_1(s, OP_STRCAT, cursp()); - push(); - n = n->cdr; - } - n = tree->cdr->cdr; - if (n->car) { /* tail */ - p = (char*)n->car; - off = new_lit_cstr(s, p); - codegen(s, tree->car, VAL); - genop_2(s, OP_STRING, cursp(), off); - pop(); - genop_1(s, OP_STRCAT, cursp()); - push(); - } - if (n->cdr->car) { /* opt */ - char *p2 = (char*)n->cdr->car; - off = new_lit_cstr(s, p2); - genop_2(s, OP_STRING, cursp(), off); - push(); - argc++; - } - if (n->cdr->cdr) { /* enc */ - char *p2 = (char*)n->cdr->cdr; - off = new_lit_cstr(s, p2); - genop_2(s, OP_STRING, cursp(), off); - push(); - argc++; - } - push(); /* space for a block */ - pop_n(argc+2); - sym = new_sym(s, MRB_SYM_2(s->mrb, compile)); - genop_3(s, OP_SEND, cursp(), sym, argc); - push(); - } - else { - node *n = tree->car; - - while (n) { - if (nint(n->car->car) != NODE_STR) { - codegen(s, n->car, NOVAL); - } - n = n->cdr; - } - } - break; case NODE_SYM: - if (val) { - int sym = new_sym(s, nsym(tree)); - - genop_2(s, OP_LOADSYM, cursp(), sym); - push(); - } - break; - - case NODE_DSYM: - codegen(s, tree, val); - if (val) { - gen_intern(s); - } - break; - - case NODE_SELF: - if (val) { - genop_1(s, OP_LOADSELF, cursp()); - push(); - } - break; - - case NODE_NIL: - if (val) { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - break; - - case NODE_TRUE: - if (val) { - genop_1(s, OP_LOADT, cursp()); - push(); - } - break; - - case NODE_FALSE: - if (val) { - genop_1(s, OP_LOADF, cursp()); - push(); - } - break; - - case NODE_ALIAS: { - int a = new_sym(s, nsym(tree->car)); - int b = new_sym(s, nsym(tree->cdr)); - - genop_2(s, OP_ALIAS, a, b); - if (val) { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - } - break; - - case NODE_UNDEF: - { - node *t = tree; - - while (t) { - int symbol = new_sym(s, nsym(t->car)); - genop_1(s, OP_UNDEF, symbol); - t = t->cdr; - } - if (val) { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } + int i = new_sym(s, sym_node(tree)->symbol); + gen_load_op2(s, OP_LOADSYM, i, val); } break; - case NODE_CLASS: - { - int idx; - node *body; - - if (tree->car->car == (node*)0) { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - else if (tree->car->car == (node*)1) { - genop_1(s, OP_OCLASS, cursp()); - push(); - } - else { - codegen(s, tree->car->car, VAL); - } - if (tree->cdr->car) { - codegen(s, tree->cdr->car, VAL); - } - else { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - pop(); pop(); - idx = new_sym(s, nsym(tree->car->cdr)); - genop_2(s, OP_CLASS, cursp(), idx); - body = tree->cdr->cdr->car; - if (nint(body->cdr->car) == NODE_STMTS && body->cdr->cdr == NULL) { - genop_1(s, OP_LOADNIL, cursp()); - } - else { - idx = scope_body(s, body, val); - genop_2(s, OP_EXEC, cursp(), idx); - } - if (val) { - push(); - } - } + case NODE_LVAR: + gen_lvar(s, var_node(tree)->symbol, val); break; - case NODE_MODULE: - { - int idx; - - if (tree->car->car == (node*)0) { - genop_1(s, OP_LOADNIL, cursp()); - push(); - } - else if (tree->car->car == (node*)1) { - genop_1(s, OP_OCLASS, cursp()); - push(); - } - else { - codegen(s, tree->car->car, VAL); - } - pop(); - idx = new_sym(s, nsym(tree->car->cdr)); - genop_2(s, OP_MODULE, cursp(), idx); - if (nint(tree->cdr->car->cdr->car) == NODE_STMTS && - tree->cdr->car->cdr->cdr == NULL) { - genop_1(s, OP_LOADNIL, cursp()); - } - else { - idx = scope_body(s, tree->cdr->car, val); - genop_2(s, OP_EXEC, cursp(), idx); - } - if (val) { - push(); - } - } + case NODE_GVAR: + gen_xvar(s, var_node(tree)->symbol, val, OP_GETGV); break; - case NODE_SCLASS: - { - int idx; + case NODE_IVAR: + gen_xvar(s, var_node(tree)->symbol, val, OP_GETIV); + break; - codegen(s, tree->car, VAL); - pop(); - genop_1(s, OP_SCLASS, cursp()); - if (nint(tree->cdr->car->cdr->car) == NODE_STMTS && - tree->cdr->car->cdr->cdr == NULL) { - genop_1(s, OP_LOADNIL, cursp()); - } - else { - idx = scope_body(s, tree->cdr->car, val); - genop_2(s, OP_EXEC, cursp(), idx); - } - if (val) { - push(); - } - } + case NODE_CVAR: + gen_xvar(s, var_node(tree)->symbol, val, OP_GETCV); + break; + + case NODE_CALL: + codegen_call(s, tree, val); + break; + + case NODE_ARRAY: + codegen_array(s, tree, val); + break; + + case NODE_HASH: + codegen_hash(s, tree, val); + break; + + case NODE_IF: + codegen_if(s, tree, val); + break; + + case NODE_WHILE: + codegen_while(s, tree, val); + break; + + case NODE_UNTIL: + codegen_until(s, tree, val); + break; + + case NODE_FOR: + codegen_for(s, tree, val); + break; + + case NODE_CASE: + codegen_case(s, tree, val); break; case NODE_DEF: - { - int sym = new_sym(s, nsym(tree->car)); - int idx = lambda_body(s, tree->cdr, 0); + codegen_def(s, tree, val); + break; - genop_1(s, OP_TCLASS, cursp()); - push(); - genop_2(s, OP_METHOD, cursp(), idx); - push(); pop(); - pop(); - genop_2(s, OP_DEF, cursp(), sym); - if (val) push(); + case NODE_CLASS: + codegen_class(s, tree, val); + break; + + case NODE_MODULE: + codegen_module(s, tree, val); + break; + + case NODE_SCLASS: + codegen_sclass(s, tree, val); + break; + + case NODE_ASGN: + codegen_asgn(s, tree, val); + break; + + case NODE_MASGN: + codegen_masgn(s, tree, NULL, 0, val); + break; + + case NODE_MARG: + /* Parameter destructuring should be handled inline by lambda_body */ + /* This case should not be reached in normal execution */ + break; + + case NODE_OP_ASGN: + codegen_op_asgn(s, tree, val); + break; + + case NODE_AND: + codegen_and(s, tree, val); + break; + + case NODE_OR: + codegen_or(s, tree, val); + break; + + case NODE_RETURN: + codegen_return(s, tree, val); + break; + + case NODE_YIELD: + codegen_yield(s, tree, val); + break; + + case NODE_SUPER: + codegen_super(s, tree, val); + break; + + case NODE_STR: + codegen_str(s, tree, val); + break; + + case NODE_DOT2: + codegen_dot2(s, tree, val); + break; + + case NODE_DOT3: + codegen_dot3(s, tree, val); + break; + + case NODE_FLOAT: + codegen_float(s, tree, val); + break; + + case NODE_SELF: + codegen_self(s, tree, val); + break; + + case NODE_NIL: + codegen_nil(s, tree, val); + break; + + case NODE_TRUE: + codegen_true(s, tree, val); + break; + + case NODE_FALSE: + codegen_false(s, tree, val); + break; + + case NODE_CONST: + codegen_const(s, tree, val); + break; + + case NODE_RESCUE: + codegen_rescue(s, tree, val); + break; + + case NODE_BLOCK: + codegen_block(s, tree, val); + break; + + case NODE_BREAK: + codegen_break(s, tree, val); + break; + + case NODE_NEXT: + codegen_next(s, tree, val); + break; + + case NODE_REDO: + codegen_redo(s, tree, val); + break; + + case NODE_RETRY: + codegen_retry(s, tree, val); + break; + + case NODE_WHILE_MOD: + codegen_while_mod(s, tree, val); + break; + + case NODE_UNTIL_MOD: + codegen_until_mod(s, tree, val); + break; + + case NODE_XSTR: + codegen_xstr(s, tree, val); + break; + + case NODE_REGX: + codegen_regx(s, tree, val); + break; + + case NODE_HEREDOC: + codegen_heredoc(s, tree, val); + break; + + case NODE_DSYM: + codegen_dsym(s, tree, val); + break; + + case NODE_NTH_REF: + codegen_nth_ref(s, tree, val); + break; + + case NODE_BACK_REF: + codegen_back_ref(s, tree, val); + break; + + case NODE_NVAR: + codegen_nvar(s, tree, val); + break; + + case NODE_DVAR: + codegen_dvar(s, tree, val); + break; + + case NODE_NOT: + codegen_not(s, tree, val); + break; + + case NODE_NEGATE: + codegen_negate(s, tree, val); + break; + + case NODE_COLON2: + codegen_colon2(s, tree, val); + break; + + case NODE_COLON3: + codegen_colon3(s, tree, val); + break; + + case NODE_DEFINED: + codegen_defined(s, tree, val); + break; + + case NODE_ZSUPER: + codegen_zsuper(s, tree, val); + break; + + case NODE_LAMBDA: + codegen_lambda(s, tree, val); + break; + + case NODE_WORDS: + codegen_words(s, tree, val); + break; + + case NODE_SYMBOLS: + codegen_symbols(s, tree, val); + break; + + case NODE_SPLAT: + codegen_splat(s, tree, val); + break; + + case NODE_BLOCK_ARG: + codegen_block_arg(s, tree, val); + break; + + case NODE_SCOPE: + codegen_scope_node(s, tree, val); + break; + + case NODE_BEGIN: + codegen_begin(s, tree, val); + break; + + case NODE_ENSURE: + codegen_ensure(s, tree, val); + break; + + case NODE_STMTS: + codegen_stmts(s, tree, val); + break; + + case NODE_ALIAS: + codegen_alias(s, tree, val); + break; + + case NODE_UNDEF: + codegen_undef(s, tree, val); + break; + + case NODE_POSTEXE: + { + struct mrb_ast_postexe_node *postexe = postexe_node(tree); + codegen(s, postexe->body, NOVAL); } break; case NODE_SDEF: - { - node *recv = tree->car; - int sym = new_sym(s, nsym(tree->cdr->car)); - int idx = lambda_body(s, tree->cdr->cdr, 0); - - codegen(s, recv, VAL); - pop(); - genop_1(s, OP_SCLASS, cursp()); - push(); - genop_2(s, OP_METHOD, cursp(), idx); - push(); pop(); - pop(); - genop_2(s, OP_DEF, cursp(), sym); - if (val) push(); - } - break; - - case NODE_POSTEXE: - codegen(s, tree, NOVAL); + codegen_sdef(s, tree, val); break; default: + /* Unhandled variable-sized node type - should not occur with current AST */ break; } - exit: s->rlev = rlev; } @@ -4959,7 +6024,7 @@ scope_new(mrb_state *mrb, codegen_scope *prev, node *nlv) s->syms = (mrb_sym*)mrbc_malloc(sizeof(mrb_sym)*s->scapa); s->lv = nlv; - s->sp += node_len(nlv)+1; /* add self */ + s->sp += (nlv ? node_len(nlv) : 0) + 1; /* add self */ s->nlocals = s->nregs = s->sp; if (nlv) { mrb_sym *lv; @@ -5062,7 +6127,6 @@ loop_break(codegen_scope *s, node *tree) else { struct loopinfo *loop; - loop = s->loop; if (tree) { if (loop->reg < 0) { @@ -5181,6 +6245,7 @@ generate_code(mrb_state *mrb, parser_state *p, int val) MRB_END_EXC(mrb->jmp); } + MRB_API struct RProc* mrb_generate_code(mrb_state *mrb, parser_state *p) { diff --git a/mrbgems/mruby-compiler/core/node.h b/mrbgems/mruby-compiler/core/node.h index ddcb29d50..91add5a4d 100644 --- a/mrbgems/mruby-compiler/core/node.h +++ b/mrbgems/mruby-compiler/core/node.h @@ -8,17 +8,14 @@ #define MRUBY_COMPILER_NODE_H enum node_type { - NODE_METHOD, NODE_SCOPE, NODE_BLOCK, NODE_IF, NODE_CASE, - NODE_WHEN, NODE_WHILE, NODE_UNTIL, NODE_WHILE_MOD, NODE_UNTIL_MOD, - NODE_ITER, NODE_FOR, NODE_BREAK, NODE_NEXT, @@ -32,20 +29,15 @@ enum node_type { NODE_OR, NODE_NOT, NODE_MASGN, + NODE_MARG, NODE_ASGN, - NODE_CDECL, - NODE_CVASGN, - NODE_CVDECL, NODE_OP_ASGN, NODE_CALL, - NODE_SCALL, - NODE_FCALL, NODE_SUPER, NODE_ZSUPER, NODE_ARRAY, NODE_ZARRAY, NODE_HASH, - NODE_KW_HASH, NODE_RETURN, NODE_YIELD, NODE_LVAR, @@ -57,26 +49,16 @@ enum node_type { NODE_NVAR, NODE_NTH_REF, NODE_BACK_REF, - NODE_MATCH, NODE_INT, + NODE_BIGINT, NODE_FLOAT, NODE_NEGATE, NODE_LAMBDA, NODE_SYM, NODE_STR, - NODE_DSTR, NODE_XSTR, - NODE_DXSTR, NODE_REGX, - NODE_DREGX, - NODE_DREGX_ONCE, - NODE_ARG, - NODE_ARGS_TAIL, - NODE_KW_ARG, - NODE_KW_REST_ARGS, NODE_SPLAT, - NODE_TO_ARY, - NODE_SVALUE, NODE_BLOCK_ARG, NODE_DEF, NODE_SDEF, @@ -97,10 +79,604 @@ enum node_type { NODE_POSTEXE, NODE_DSYM, NODE_HEREDOC, - NODE_LITERAL_DELIM, NODE_WORDS, NODE_SYMBOLS, NODE_LAST }; +#define STR_FUNC_PARSING 0x01 +#define STR_FUNC_EXPAND 0x02 +#define STR_FUNC_REGEXP 0x04 +#define STR_FUNC_WORD 0x08 +#define STR_FUNC_SYMBOL 0x10 +#define STR_FUNC_ARRAY 0x20 +#define STR_FUNC_HEREDOC 0x40 +#define STR_FUNC_XQUOTE 0x80 + +enum mrb_string_type { + str_not_parsing = (0), + str_squote = (STR_FUNC_PARSING), + str_dquote = (STR_FUNC_PARSING|STR_FUNC_EXPAND), + str_regexp = (STR_FUNC_PARSING|STR_FUNC_REGEXP|STR_FUNC_EXPAND), + str_sword = (STR_FUNC_PARSING|STR_FUNC_WORD|STR_FUNC_ARRAY), + str_dword = (STR_FUNC_PARSING|STR_FUNC_WORD|STR_FUNC_ARRAY|STR_FUNC_EXPAND), + str_ssym = (STR_FUNC_PARSING|STR_FUNC_SYMBOL), + str_ssymbols = (STR_FUNC_PARSING|STR_FUNC_SYMBOL|STR_FUNC_ARRAY), + str_dsymbols = (STR_FUNC_PARSING|STR_FUNC_SYMBOL|STR_FUNC_ARRAY|STR_FUNC_EXPAND), + str_heredoc = (STR_FUNC_PARSING|STR_FUNC_HEREDOC), + str_xquote = (STR_FUNC_PARSING|STR_FUNC_XQUOTE|STR_FUNC_EXPAND), +}; + +/* heredoc structure */ +struct mrb_parser_heredoc_info { + mrb_bool allow_indent:1; + mrb_bool remove_indent:1; + mrb_bool line_head:1; + size_t indent; + mrb_ast_node *indented; + enum mrb_string_type type; + const char *term; + int term_len; + mrb_ast_node *doc; +}; + +/* AST node structures - Head-only location optimization */ + +/* Structure nodes - only car/cdr, ignores location fields */ +struct mrb_ast_node { + struct mrb_ast_node *car, *cdr; + /* No location fields - saves 4 bytes per structure node */ +}; + +/* Variable-sized AST nodes */ + +/* Variable node header - common to all variable-sized nodes */ +struct mrb_ast_var_header { + uint16_t lineno; /* Line number information */ + uint16_t filename_index; /* File index information */ + uint8_t node_type; /* NODE_INT, NODE_SYM, NODE_STR, etc. */ + /* Total: 6 bytes header for all variable nodes */ +}; + +/* Literal value nodes */ + +/* Variable-sized symbol node */ +struct mrb_ast_sym_node { + struct mrb_ast_var_header header; /* 8 bytes */ + mrb_sym symbol; /* Direct symbol reference */ + /* Total: 12-16 bytes vs previous 20+ bytes + indirection */ +}; + +/* Variable-sized string node with cons list */ +struct mrb_ast_str_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *list; +}; + +/* Variable-sized integer node */ +struct mrb_ast_int_node { + struct mrb_ast_var_header header; /* 8 bytes */ + int32_t value; /* Direct 32-bit integer storage */ +}; + +/* Variable-sized big integer node */ +struct mrb_ast_bigint_node { + struct mrb_ast_var_header header; /* 8 bytes */ + char *string; /* String representation of big number */ + int base; /* Number base (8, 10, 16) */ +}; + +/* Variable-sized node for variables (lvar, ivar, etc.) */ +struct mrb_ast_var_node { + struct mrb_ast_var_header header; + mrb_sym symbol; +}; + +/* Expression and operation nodes */ + +/* Variable-sized call node with inline argument storage */ +struct mrb_ast_call_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *receiver; /* Receiver object */ + mrb_sym method_name; /* Method name symbol */ + uint8_t safe_call:1; /* Safe navigation (&.) */ + struct mrb_ast_node *args; /* Arguments Information */ +}; + +/* Variable-sized array node */ +struct mrb_ast_array_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *elements; /* Elements list (cons list) */ +}; + +/* Variable-sized hash node */ +struct mrb_ast_hash_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *pairs; /* Key-value pairs (cons list) */ +}; + +/* Control flow and definition nodes */ + +/* Variable-sized method definition node */ +struct mrb_ast_def_node { + struct mrb_ast_var_header header; /* 8 bytes */ + mrb_sym name; /* Method name */ + struct mrb_ast_args *args; /* Method arguments */ + struct mrb_ast_node *body; /* Method body */ + struct mrb_ast_node *locals; /* Local Variables */ +} ; + +/* Variable-sized singleton method definition node */ +struct mrb_ast_sdef_node { + struct mrb_ast_var_header header; /* 8 bytes */ + mrb_sym name; /* Method name */ + struct mrb_ast_args *args; /* Method arguments */ + struct mrb_ast_node *body; /* Method body */ + struct mrb_ast_node *locals; /* Local Variables */ + struct mrb_ast_node *obj; /* receiver */ +}; + +/* variable-sized class definition node */ +struct mrb_ast_class_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *name; /* Class name (NODE_CONST or NODE_COLON2) */ + struct mrb_ast_node *superclass; /* Superclass (can be NULL) */ + struct mrb_ast_node *body; /* Class body */ +}; + +/* Variable-sized module definition node */ +struct mrb_ast_module_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *name; /* Module name (NODE_CONST or NODE_COLON2) */ + struct mrb_ast_node *body; /* Module body */ +}; + +/* Variable-sized singleton class definition node */ +struct mrb_ast_sclass_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *obj; /* Object for singleton class */ + struct mrb_ast_node *body; /* Singleton class body */ +}; + +/* Variable-sized if node */ +struct mrb_ast_if_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *condition; /* Condition expression */ + struct mrb_ast_node *then_body; /* Then branch */ + struct mrb_ast_node *else_body; /* Else branch (can be NULL) */ +}; + +/* Variable-sized while node */ +struct mrb_ast_while_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *condition; /* Loop condition */ + struct mrb_ast_node *body; /* Loop body */ +}; + +/* Variable-sized until node */ +struct mrb_ast_until_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *condition; /* Loop condition */ + struct mrb_ast_node *body; /* Loop body */ +}; + +/* Variable-sized case node */ +struct mrb_ast_case_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *value; /* Case value expression */ + struct mrb_ast_node *body; /* When/else clauses (cons list) */ +}; + +/* Variable-sized for node */ +struct mrb_ast_for_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *var; /* Loop variable */ + struct mrb_ast_node *iterable; /* Object to iterate over */ + struct mrb_ast_node *body; /* Loop body */ +}; + +/* Assignment Node Structures */ + +/* Variable-sized assignment node */ +struct mrb_ast_asgn_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *lhs; /* Left-hand side (target) */ + struct mrb_ast_node *rhs; /* Right-hand side (value) */ +}; + +/* Variable-sized multiple assignment node */ +struct mrb_ast_masgn_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *pre; /* Pre-splat variables (cons list) */ + struct mrb_ast_node *rest; /* Splat variable (single node or -1) */ + struct mrb_ast_node *post; /* Post-splat variables (cons list) */ + struct mrb_ast_node *rhs; /* Right-hand side (values) */ +}; + +/* Variable-sized operator assignment node */ +struct mrb_ast_op_asgn_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *lhs; /* Left-hand side (target) */ + mrb_sym op; /* Assignment operator (e.g., +=, -=, etc.) */ + struct mrb_ast_node *rhs; /* Right-hand side (value) */ +}; + +/* Expression Node Structures */ + +/* Variable-sized AND node */ +struct mrb_ast_and_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *left; /* Left operand */ + struct mrb_ast_node *right; /* Right operand */ +}; + +/* Variable-sized OR node */ +struct mrb_ast_or_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *left; /* Left operand */ + struct mrb_ast_node *right; /* Right operand */ +}; + +/* Variable-sized RETURN node */ +struct mrb_ast_return_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *args; /* Return arguments (can be NULL) */ +}; + +/* Variable-sized YIELD node */ +struct mrb_ast_yield_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *args; /* Yield arguments (can be NULL) */ +}; + +/* Variable-sized SUPER node */ +struct mrb_ast_super_node { + struct mrb_ast_var_header header; /* 8 bytes */ + struct mrb_ast_node *args; /* Super arguments (can be NULL) */ +}; + +#define VAR_NODE_TYPE(n) ((enum node_type)(((struct mrb_ast_var_header*)(n))->node_type)) + +/* Type-safe casting macros */ +#define var_header(n) ((struct mrb_ast_var_header*)(n)) + +/* Common type casting macros used by parser and codegen */ +#define node_to_sym(x) ((mrb_sym)(intptr_t)(x)) +#define sym_to_node(x) ((node*)(intptr_t)(x)) +#define int_to_node(x) ((node*)(intptr_t)(x)) +#define node_to_int(x) ((int)(intptr_t)(x)) +#define node_to_type(x) ((enum node_type)(intptr_t)(x)) +#define node_to_char(x) ((char)(intptr_t)(x)) + +/* Literal value node casting macros */ +#define sym_node(n) ((struct mrb_ast_sym_node*)(n)) +#define str_node(n) ((struct mrb_ast_str_node*)(n)) +#define int_node(n) ((struct mrb_ast_int_node*)(n)) +#define bigint_node(n) ((struct mrb_ast_bigint_node*)(n)) +#define var_node(n) ((struct mrb_ast_var_node*)(n)) + +/* Expression and operation node casting macros */ +#define call_node(n) ((struct mrb_ast_call_node*)(n)) +#define array_node(n) ((struct mrb_ast_array_node*)(n)) +#define hash_node(n) ((struct mrb_ast_hash_node*)(n)) + +/* Control flow and definition node casting macros */ +#define def_node(n) ((struct mrb_ast_def_node*)(n)) +#define class_node(n) ((struct mrb_ast_class_node*)(n)) +#define module_node(n) ((struct mrb_ast_module_node*)(n)) +#define sclass_node(n) ((struct mrb_ast_sclass_node*)(n)) +#define if_node(n) ((struct mrb_ast_if_node*)(n)) +#define while_node(n) ((struct mrb_ast_while_node*)(n)) +#define until_node(n) ((struct mrb_ast_until_node*)(n)) +#define case_node(n) ((struct mrb_ast_case_node*)(n)) +#define for_node(n) ((struct mrb_ast_for_node*)(n)) +#define asgn_node(n) ((struct mrb_ast_asgn_node*)(n)) +#define masgn_node(n) ((struct mrb_ast_masgn_node*)(n)) +#define op_asgn_node(n) ((struct mrb_ast_op_asgn_node*)(n)) +#define and_node(n) ((struct mrb_ast_and_node*)(n)) +#define or_node(n) ((struct mrb_ast_or_node*)(n)) +#define return_node(n) ((struct mrb_ast_return_node*)(n)) +#define yield_node(n) ((struct mrb_ast_yield_node*)(n)) +#define super_node(n) ((struct mrb_ast_super_node*)(n)) + +/* Variable-sized literal node structures */ + +struct mrb_ast_dot2_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *left; + struct mrb_ast_node *right; +}; + +struct mrb_ast_dot3_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *left; + struct mrb_ast_node *right; +}; + +struct mrb_ast_float_node { + struct mrb_ast_var_header header; + const char *value; +}; + +/* Literal node casting macros */ +#define dot2_node(n) ((struct mrb_ast_dot2_node*)(n)) +#define dot3_node(n) ((struct mrb_ast_dot3_node*)(n)) +#define float_node(n) ((struct mrb_ast_float_node*)(n)) + +/* Variable-sized simple node structures */ +struct mrb_ast_self_node { + struct mrb_ast_var_header header; +}; + +struct mrb_ast_nil_node { + struct mrb_ast_var_header header; +}; + +struct mrb_ast_true_node { + struct mrb_ast_var_header header; +}; + +struct mrb_ast_false_node { + struct mrb_ast_var_header header; +}; + +struct mrb_ast_const_node { + struct mrb_ast_var_header header; + mrb_sym symbol; +}; + +/* Simple node casting macros */ +#define self_node(n) ((struct mrb_ast_self_node*)(n)) +#define nil_node(n) ((struct mrb_ast_nil_node*)(n)) +#define true_node(n) ((struct mrb_ast_true_node*)(n)) +#define false_node(n) ((struct mrb_ast_false_node*)(n)) +#define const_node(n) ((struct mrb_ast_const_node*)(n)) + +/* Variable-sized advanced node structures */ +struct mrb_ast_rescue_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *body; + struct mrb_ast_node *rescue_clauses; + struct mrb_ast_node *else_clause; +}; + +struct mrb_ast_block_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *locals; + struct mrb_ast_args *args; + struct mrb_ast_node *body; +}; + +/* Unified argument structure - eliminates args_tail_node allocation */ +struct mrb_ast_args { + /* Core argument lists (parser builds these naturally) */ + struct mrb_ast_node *mandatory_args; /* Cons list of mandatory arguments */ + struct mrb_ast_node *optional_args; /* Cons list of optional arguments */ + struct mrb_ast_node *post_mandatory_args; /* Cons list of post-mandatory arguments */ + struct mrb_ast_node *keyword_args; /* Cons list of keyword arguments */ + + /* Special arguments (directly embedded) */ + mrb_sym rest_arg; /* Rest argument symbol (0 = none) */ + mrb_sym kwrest_arg; /* Keyword rest argument (0 = none) */ + mrb_sym block_arg; /* Block argument symbol (0 = none) */ +}; + +/* Call arguments structure - replaces cons-based new_callargs */ +struct mrb_ast_callargs { + struct mrb_ast_node *regular_args; /* Cons list of regular arguments (preserves splat compatibility) */ + struct mrb_ast_node *keyword_args; /* Keyword arguments hash node */ + struct mrb_ast_node *block_arg; /* Block argument node */ +}; + +/* Advanced node casting macros */ +#define rescue_node(n) ((struct mrb_ast_rescue_node*)(n)) +#define block_node(n) ((struct mrb_ast_block_node*)(n)) +#define callargs_node(n) ((struct mrb_ast_callargs*)(n)) + +/* Control flow statement nodes */ +struct mrb_ast_break_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *value; +}; + +struct mrb_ast_next_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *value; +}; + +struct mrb_ast_redo_node { + struct mrb_ast_var_header header; +}; + +struct mrb_ast_retry_node { + struct mrb_ast_var_header header; +}; + +#define break_node(n) ((struct mrb_ast_break_node*)(n)) +#define next_node(n) ((struct mrb_ast_next_node*)(n)) +#define redo_node(n) ((struct mrb_ast_redo_node*)(n)) +#define retry_node(n) ((struct mrb_ast_retry_node*)(n)) + +/* String and regex variant nodes */ +struct mrb_ast_xstr_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *list; +}; + +struct mrb_ast_regx_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *list; + const char *flags; + const char *encoding; +}; + +struct mrb_ast_heredoc_node { + struct mrb_ast_var_header header; + struct mrb_parser_heredoc_info info; +}; + +#define xstr_node(n) ((struct mrb_ast_xstr_node*)(n)) +#define regx_node(n) ((struct mrb_ast_regx_node*)(n)) +#define heredoc_node(n) ((struct mrb_ast_heredoc_node*)(n)) +#define dsym_node(n) ((struct mrb_ast_str_node*)(n)) + +/* Reference and special variable nodes */ +struct mrb_ast_nth_ref_node { + struct mrb_ast_var_header header; + int nth; +}; + +struct mrb_ast_back_ref_node { + struct mrb_ast_var_header header; + int type; +}; + +struct mrb_ast_nvar_node { + struct mrb_ast_var_header header; + int num; +}; + +struct mrb_ast_dvar_node { + struct mrb_ast_var_header header; + mrb_sym name; +}; + +#define nth_ref_node(n) ((struct mrb_ast_nth_ref_node*)(n)) +#define back_ref_node(n) ((struct mrb_ast_back_ref_node*)(n)) +#define nvar_node(n) ((struct mrb_ast_nvar_node*)(n)) +#define dvar_node(n) ((struct mrb_ast_dvar_node*)(n)) + +/* Unary operator and scope resolution nodes */ +struct mrb_ast_not_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *operand; +}; + +struct mrb_ast_negate_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *operand; +}; + +struct mrb_ast_colon2_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *base; + mrb_sym name; +}; + +struct mrb_ast_colon3_node { + struct mrb_ast_var_header header; + mrb_sym name; +}; + +struct mrb_ast_defined_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *expr; +}; + +#define not_node(n) ((struct mrb_ast_not_node*)(n)) +#define negate_node(n) ((struct mrb_ast_negate_node*)(n)) +#define colon2_node(n) ((struct mrb_ast_colon2_node*)(n)) +#define colon3_node(n) ((struct mrb_ast_colon3_node*)(n)) +#define defined_node(n) ((struct mrb_ast_defined_node*)(n)) + +/* Lambda nodes */ +struct mrb_ast_lambda_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *locals; + struct mrb_ast_args *args; + struct mrb_ast_node *body; +}; + +/* Array literal variant nodes */ +struct mrb_ast_zarray_node { + struct mrb_ast_var_header header; +}; + +struct mrb_ast_words_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *args; +}; + +struct mrb_ast_symbols_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *args; +}; + +/* Argument and parameter nodes */ + +struct mrb_ast_splat_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *value; +}; + +struct mrb_ast_block_arg_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *value; +}; + +/* Structural and block nodes */ + +struct mrb_ast_scope_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *locals; + struct mrb_ast_node *body; +}; + +struct mrb_ast_begin_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *body; +}; + +struct mrb_ast_ensure_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *body; + struct mrb_ast_node *ensure_clause; +}; + +struct mrb_ast_stmts_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *stmts; /* Cons-list of statements */ +}; + +struct mrb_ast_iter_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *vars; + struct mrb_ast_node *body; +}; + +/* Declaration nodes */ + +struct mrb_ast_alias_node { + struct mrb_ast_var_header header; + mrb_sym new_name; + mrb_sym old_name; +}; + +struct mrb_ast_undef_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *syms; +}; + +struct mrb_ast_postexe_node { + struct mrb_ast_var_header header; + struct mrb_ast_node *body; +}; + +#define zsuper_node(n) ((struct mrb_ast_super_node*)(n)) +#define lambda_node(n) ((struct mrb_ast_lambda_node*)(n)) +#define zarray_node(n) ((struct mrb_ast_zarray_node*)(n)) +#define words_node(n) ((struct mrb_ast_words_node*)(n)) +#define symbols_node(n) ((struct mrb_ast_symbols_node*)(n)) +#define splat_node(n) ((struct mrb_ast_splat_node*)(n)) +#define block_arg_node(n) ((struct mrb_ast_block_arg_node*)(n)) +#define scope_node(n) ((struct mrb_ast_scope_node*)(n)) +#define begin_node(n) ((struct mrb_ast_begin_node*)(n)) +#define ensure_node(n) ((struct mrb_ast_ensure_node*)(n)) +#define stmts_node(n) ((struct mrb_ast_stmts_node*)(n)) +#define iter_node(n) ((struct mrb_ast_iter_node*)(n)) +#define alias_node(n) ((struct mrb_ast_alias_node*)(n)) +#define undef_node(n) ((struct mrb_ast_undef_node*)(n)) +#define postexe_node(n) ((struct mrb_ast_postexe_node*)(n)) +#define sdef_node(n) ((struct mrb_ast_sdef_node*)(n)) + #endif /* MRUBY_COMPILER_NODE_H */ diff --git a/mrbgems/mruby-compiler/core/parse.y b/mrbgems/mruby-compiler/core/parse.y index 1c9e6c4b0..e5c25b8f4 100644 --- a/mrbgems/mruby-compiler/core/parse.y +++ b/mrbgems/mruby-compiler/core/parse.y @@ -12,6 +12,7 @@ #define YYSTACK_USE_ALLOCA 1 #include +#include #include #include #include @@ -42,6 +43,15 @@ static void yywarning(parser_state *p, const char *s); static void backref_error(parser_state *p, node *n); static void void_expr_error(parser_state *p, node *n); static void tokadd(parser_state *p, int32_t c); +static const char* tok(parser_state *p); +static int toklen(parser_state *p); + +/* Forward declarations for variable-sized simple node functions */ + +/* Forward declarations for variable-sized advanced node functions */ + +/* Helper function to check node type for both traditional and variable-sized nodes */ +static mrb_bool node_type_p(node *n, enum node_type type); #define identchar(c) (ISALNUM(c) || (c) == '_' || !ISASCII(c)) @@ -62,19 +72,7 @@ typedef unsigned int stack_type; #define CMDARG_LEXPOP() BITSTACK_LEXPOP(p->cmdarg_stack) #define CMDARG_P() BITSTACK_SET_P(p->cmdarg_stack) -#define SET_LINENO(c,n) ((c)->lineno = (n)) -#define NODE_LINENO(c,n) do {\ - if (n) {\ - (c)->filename_index = (n)->filename_index;\ - (c)->lineno = (n)->lineno;\ - }\ -} while (0) - -#define sym(x) ((mrb_sym)(intptr_t)(x)) -#define nsym(x) ((node*)(intptr_t)(x)) -#define nint(x) ((node*)(intptr_t)(x)) -#define intn(x) ((int)(intptr_t)(x)) -#define typen(x) ((enum node_type)(intptr_t)(x)) +#define SET_LINENO(c,n) (((struct mrb_ast_var_header*)(c))->lineno = (n)) #define NUM_SUFFIX_R (1<<0) #define NUM_SUFFIX_I (1<<1) @@ -128,27 +126,50 @@ parser_palloc(parser_state *p, size_t size) static node* cons_gen(parser_state *p, node *car, node *cdr) { - node *c; + struct mrb_ast_node *c; + /* Try to reuse from free list first - only for 16-byte nodes */ if (p->cells) { - c = p->cells; + c = (struct mrb_ast_node*)p->cells; p->cells = p->cells->cdr; } else { - c = (node*)parser_palloc(p, sizeof(mrb_ast_node)); + c = (struct mrb_ast_node*)parser_palloc(p, sizeof(struct mrb_ast_node)); } - c->car = car; c->cdr = cdr; - c->lineno = p->lineno; - c->filename_index = p->current_filename_index; - /* beginning of next partial file; need to point the previous file */ - if (p->lineno == 0 && p->current_filename_index > 0) { - c->filename_index--; - } - return c; + /* Don't initialize location fields for structure nodes - saves CPU */ + return (node*)c; } -#define cons(a,b) cons_gen(p,(a),(b)) + +/* Head-only location optimization: separate functions for head vs structure nodes */ +#define cons(a,b) cons_gen(p,(a),(b)) /* Structure nodes - no location */ +/* Initialize variable node header */ +static void +init_var_header(struct mrb_ast_var_header *header, parser_state *p, enum node_type type) +{ + header->lineno = p->lineno; + header->filename_index = p->current_filename_index; + header->node_type = (uint8_t)type; + + /* Handle file boundary edge case */ + if (p->lineno == 0 && p->current_filename_index > 0) { + header->filename_index--; + } +} + +/* Combined allocate + init header helper */ +static inline void* +new_node(parser_state *p, size_t size, enum node_type type) +{ + void *n = parser_palloc(p, size); + init_var_header((struct mrb_ast_var_header*)n, p, type); + return n; +} + +/* Type-safe macro wrapper for node allocation */ +#define NEW_NODE(type_name, node_type) \ + (struct mrb_ast_##type_name##_node*)new_node(p, sizeof(struct mrb_ast_##type_name##_node), node_type) static node* list1_gen(parser_state *p, node *a) @@ -160,38 +181,17 @@ list1_gen(parser_state *p, node *a) static node* list2_gen(parser_state *p, node *a, node *b) { - return cons(a, cons(b,0)); + return cons(a, cons(b, 0)); } #define list2(a,b) list2_gen(p, (a),(b)) static node* list3_gen(parser_state *p, node *a, node *b, node *c) { - return cons(a, cons(b, cons(c,0))); + return cons(a, cons(b, cons(c, 0))); } #define list3(a,b,c) list3_gen(p, (a),(b),(c)) -static node* -list4_gen(parser_state *p, node *a, node *b, node *c, node *d) -{ - return cons(a, cons(b, cons(c, cons(d, 0)))); -} -#define list4(a,b,c,d) list4_gen(p, (a),(b),(c),(d)) - -static node* -list5_gen(parser_state *p, node *a, node *b, node *c, node *d, node *e) -{ - return cons(a, cons(b, cons(c, cons(d, cons(e, 0))))); -} -#define list5(a,b,c,d,e) list5_gen(p, (a),(b),(c),(d),(e)) - -static node* -list6_gen(parser_state *p, node *a, node *b, node *c, node *d, node *e, node *f) -{ - return cons(a, cons(b, cons(c, cons(d, cons(e, cons(f, 0)))))); -} -#define list6(a,b,c,d,e,f) list6_gen(p, (a),(b),(c),(d),(e),(f)) - static node* append_gen(parser_state *p, node *a, node *b) { @@ -288,7 +288,7 @@ local_var_p(parser_state *p, mrb_sym sym) while (l) { node *n = l->car; while (n) { - if (sym(n->car) == sym) return TRUE; + if (node_to_sym(n->car) == sym) return TRUE; n = n->cdr; } l = l->cdr; @@ -317,7 +317,7 @@ local_add_f(parser_state *p, mrb_sym sym) if (p->locals) { node *n = p->locals->car; while (n) { - if (sym(n->car) == sym) { + if (node_to_sym(n->car) == sym) { mrb_int len; const char* name = mrb_sym_name_len(p->mrb, sym, &len); if (len > 0 && name[0] != '_') { @@ -327,7 +327,7 @@ local_add_f(parser_state *p, mrb_sym sym) } n = n->cdr; } - p->locals->car = push(p->locals->car, nsym(sym)); + p->locals->car = push(p->locals->car, sym_to_node(sym)); } } @@ -355,16 +355,47 @@ locals_node(parser_state *p) return p->locals ? p->locals->car : NULL; } +/* Helper function to check node type for both traditional and variable-sized nodes */ +static mrb_bool +node_type_p(node *n, enum node_type type) +{ + if (!n) return FALSE; + + /* Check if this is a variable-sized node */ + struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; + return ((enum node_type)header->node_type == type); +} + +/* Helper functions for variable-sized node detection */ +static enum node_type +get_node_type(node *n) +{ + if (!n) return (enum node_type)0; + + /* Try to interpret as variable-sized node */ + struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; + enum node_type type = (enum node_type)header->node_type; + + /* Validate that the node type is within valid range for variable-sized nodes */ + if (type >= NODE_SCOPE && type < NODE_LAST) { + return type; + } + + /* If node type is invalid, this is likely a cons-list node */ + /* Return a special sentinel value to indicate cons-list fallback */ + return NODE_LAST; /* Use NODE_LAST as sentinel for cons-list nodes */ +} + static void nvars_nest(parser_state *p) { - p->nvars = cons(nint(0), p->nvars); + p->nvars = cons(int_to_node(0), p->nvars); } static void nvars_block(parser_state *p) { - p->nvars = cons(nint(-2), p->nvars); + p->nvars = cons(int_to_node(-2), p->nvars); } static void @@ -373,41 +404,56 @@ nvars_unnest(parser_state *p) p->nvars = p->nvars->cdr; } -/* (:scope (vars..) (prog...)) */ +/* struct: scope_node(locals, body) */ static node* new_scope(parser_state *p, node *body) { - return cons((node*)NODE_SCOPE, cons(locals_node(p), body)); + struct mrb_ast_scope_node *scope_node = NEW_NODE(scope, NODE_SCOPE); + scope_node->locals = locals_node(p); + scope_node->body = body; + return (node*)scope_node; } -/* (:begin prog...) */ +/* struct: stmts_node(stmts) - uses cons list */ static node* new_stmts(parser_state *p, node *body) { - if (body) { - /* If body is already a NODE_STMTS, just return it directly */ - if (typen(body->car) == NODE_STMTS) { - return body; - } - return list2((node*)NODE_STMTS, body); - } - return cons((node*)NODE_STMTS, 0); + struct mrb_ast_stmts_node *n = NEW_NODE(stmts, NODE_STMTS); + n->stmts = body ? list1(body) : 0; /* Wrap single statement in cons-list */ + + return (node*)n; } -/* (:begin body) */ +/* Helper: push statement to stmts node */ +static node* +stmts_push(parser_state *p, node *stmts, node *stmt) +{ + struct mrb_ast_stmts_node *n = stmts_node(stmts); + n->stmts = push(n->stmts, stmt); + return stmts; +} + +/* struct: begin_node(body) */ static node* new_begin(parser_state *p, node *body) { - return cons((node*)NODE_BEGIN, body); + struct mrb_ast_begin_node *begin_node = NEW_NODE(begin, NODE_BEGIN); + begin_node->body = body; + return (node*)begin_node; } #define newline_node(n) (n) -/* (:rescue body rescue else) */ +/* struct: rescue_node(body, rescue_clauses, else_clause) */ static node* new_rescue(parser_state *p, node *body, node *resq, node *els) { - return list4((node*)NODE_RESCUE, body, resq, els); + struct mrb_ast_rescue_node *n = NEW_NODE(rescue, NODE_RESCUE); + n->body = body; + n->rescue_clauses = resq; + n->else_clause = els; + + return (node*)n; } static node* @@ -416,330 +462,411 @@ new_mod_rescue(parser_state *p, node *body, node *resq) return new_rescue(p, body, list1(list3(0, 0, resq)), 0); } -/* (:ensure body ensure) */ +/* struct: ensure_node(body, ensure_clause) */ static node* new_ensure(parser_state *p, node *a, node *b) { - return cons((node*)NODE_ENSURE, cons(a, cons(0, b))); + struct mrb_ast_ensure_node *ensure_node = NEW_NODE(ensure, NODE_ENSURE); + ensure_node->body = a; + ensure_node->ensure_clause = b; + return (node*)ensure_node; } -/* (:nil) */ +/* struct: nil_node() */ static node* new_nil(parser_state *p) { - return list1((node*)NODE_NIL); + struct mrb_ast_nil_node *n = NEW_NODE(nil, NODE_NIL); + + return (node*)n; } -/* (:true) */ +/* struct: true_node() */ static node* new_true(parser_state *p) { - return list1((node*)NODE_TRUE); + struct mrb_ast_true_node *n = NEW_NODE(true, NODE_TRUE); + + return (node*)n; } -/* (:false) */ +/* struct: false_node() */ static node* new_false(parser_state *p) { - return list1((node*)NODE_FALSE); + struct mrb_ast_false_node *n = NEW_NODE(false, NODE_FALSE); + + return (node*)n; } -/* (:alias new old) */ +/* struct: alias_node(new_name, old_name) */ static node* new_alias(parser_state *p, mrb_sym a, mrb_sym b) { - return cons((node*)NODE_ALIAS, cons(nsym(a), nsym(b))); + struct mrb_ast_alias_node *alias_node = NEW_NODE(alias, NODE_ALIAS); + alias_node->new_name = a; + alias_node->old_name = b; + return (node*)alias_node; } -/* (:if cond then else) */ +/* struct: if_node(cond, then_body, else_body) */ static node* -new_if(parser_state *p, node *a, node *b, node *c) +new_if(parser_state *p, node *condition, node *then_body, node *else_body) { - void_expr_error(p, a); - return list4((node*)NODE_IF, a, b, c); + void_expr_error(p, condition); + + struct mrb_ast_if_node *n = NEW_NODE(if, NODE_IF); + n->condition = condition; + n->then_body = then_body; + n->else_body = else_body; + + return (node*)n; } -/* (:unless cond then else) */ +/* struct: while_node(cond, body) */ static node* -new_unless(parser_state *p, node *a, node *b, node *c) +new_while(parser_state *p, node *condition, node *body) { - void_expr_error(p, a); - return list4((node*)NODE_IF, a, c, b); + void_expr_error(p, condition); + + struct mrb_ast_while_node *n = NEW_NODE(while, NODE_WHILE); + n->condition = condition; + n->body = body; + + return (node*)n; } -/* (:while cond body) */ +/* struct: until_node(cond, body) */ static node* -new_while(parser_state *p, node *a, node *b) +new_until(parser_state *p, node *condition, node *body) { - void_expr_error(p, a); - return cons((node*)NODE_WHILE, cons(a, b)); + void_expr_error(p, condition); + + struct mrb_ast_until_node *n = NEW_NODE(until, NODE_UNTIL); + n->condition = condition; + n->body = body; + + return (node*)n; } -/* (:until cond body) */ +/* struct: while_node(cond, body) */ static node* -new_until(parser_state *p, node *a, node *b) +new_while_mod(parser_state *p, node *condition, node *body) { - void_expr_error(p, a); - return cons((node*)NODE_UNTIL, cons(a, b)); + node *while_node = new_while(p, condition, body); + struct mrb_ast_while_node *n = (struct mrb_ast_while_node*)while_node; + n->header.node_type = NODE_WHILE_MOD; + return while_node; } -/* (:while_mod cond body) */ -static node* -new_while_mod(parser_state *p, node *a, node *b) -{ - void_expr_error(p, a); - return cons((node*)NODE_WHILE_MOD, cons(a, b)); -} - -/* (:until_mod cond body) */ +/* struct: until_node(cond, body) */ static node* new_until_mod(parser_state *p, node *a, node *b) { - void_expr_error(p, a); - return cons((node*)NODE_UNTIL_MOD, cons(a, b)); + node *until_node = new_until(p, a, b); + struct mrb_ast_until_node *n = (struct mrb_ast_until_node*)until_node; + n->header.node_type = NODE_UNTIL_MOD; + return until_node; } -/* (:for var obj body) */ + +/* struct: for_node(var, obj, body) */ static node* new_for(parser_state *p, node *v, node *o, node *b) { void_expr_error(p, o); - return list4((node*)NODE_FOR, v, o, b); + + struct mrb_ast_for_node *n = NEW_NODE(for, NODE_FOR); + n->var = v; + n->iterable = o; + n->body = b; + + return (node*)n; } -/* (:case a ((when ...) body) ((when...) body)) */ +/* struct: case_node(expr, when_clauses) - uses cons list */ static node* new_case(parser_state *p, node *a, node *b) { - node *n = list2((node*)NODE_CASE, a); - node *n2 = n; - void_expr_error(p, a); - while (n2->cdr) { - n2 = n2->cdr; - } - n2->cdr = b; - return n; + + struct mrb_ast_case_node *n = NEW_NODE(case, NODE_CASE); + n->value = a; + n->body = b; + + return (node*)n; } -/* (:postexe a) */ +/* struct: postexe_node(body) */ static node* new_postexe(parser_state *p, node *a) { - return cons((node*)NODE_POSTEXE, a); + struct mrb_ast_postexe_node *postexe_node = NEW_NODE(postexe, NODE_POSTEXE); + postexe_node->body = a; + return (node*)postexe_node; } -/* (:self) */ +/* struct: self_node() */ static node* new_self(parser_state *p) { - return list1((node*)NODE_SELF); + struct mrb_ast_self_node *n = NEW_NODE(self, NODE_SELF); + + return (node*)n; } -/* (:call a b c) */ +/* struct: call_node(receiver, method, args) */ static node* -new_call(parser_state *p, node *a, mrb_sym b, node *c, int pass) +new_call(parser_state *p, node *receiver, mrb_sym method, node *args, int pass) { - node *n = list4(nint(pass?NODE_CALL:NODE_SCALL), a, nsym(b), c); - void_expr_error(p, a); - NODE_LINENO(n, a); - return n; + /* Calculate size needed (fixed size now) */ struct mrb_ast_call_node *n = NEW_NODE(call, NODE_CALL); + n->receiver = receiver; + n->method_name = method; + n->safe_call = (pass == 0); /* pass == 0 means safe call (&.) */ + + /* Store args pointer directly - no need to unpack and repack */ + n->args = args; + + void_expr_error(p, receiver); + return (node*)n; } -/* (:fcall self mid args) */ +/* struct: fcall_node(method, args) */ static node* new_fcall(parser_state *p, mrb_sym b, node *c) { - node *n = list4((node*)NODE_FCALL, 0, nsym(b), c); - NODE_LINENO(n, c); - return n; + return new_call(p, NULL, b, c, '.'); } /* (a b . c) */ static node* new_callargs(parser_state *p, node *a, node *b, node *c) { - return cons(a, cons(b, c)); + /* Allocate struct mrb_ast_callargs (fixed size, like new_args) */ + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)parser_palloc(p, sizeof(struct mrb_ast_callargs)); + + /* Initialize members directly */ + callargs->regular_args = a; /* Cons list of regular arguments (preserves splat compatibility) */ + callargs->keyword_args = b; /* Keyword arguments hash node */ + callargs->block_arg = c; /* Block argument node */ + + /* Return direct cast to node (like new_args) */ + return (node*)callargs; } -/* (:super . c) */ +/* struct: super_node(args) */ static node* new_super(parser_state *p, node *c) { - return cons((node*)NODE_SUPER, c); + struct mrb_ast_super_node *n = NEW_NODE(super, NODE_SUPER); + n->args = c; + + return (node*)n; } -/* (:zsuper) */ +/* struct: zsuper_node() */ static node* new_zsuper(parser_state *p) { - return cons((node*)NODE_ZSUPER, 0); + struct mrb_ast_super_node *n = NEW_NODE(super, NODE_ZSUPER); + n->args = NULL; /* zsuper initially has no args, but may be added by call_with_block */ + return (node*)n; } -/* (:yield . c) */ +/* struct: yield_node(args) */ static node* new_yield(parser_state *p, node *c) { - if (c && c->cdr && c->cdr->cdr) { - yyerror(NULL, p, "both block arg and actual block given"); - } + /* Handle callargs structure - direct casting like new_args() */ + if (c) { + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)c; + if (callargs->block_arg) { + yyerror(NULL, p, "both block arg and actual block given"); + } + } struct mrb_ast_yield_node *n = NEW_NODE(yield, NODE_YIELD); + n->args = c; - return cons((node*)NODE_YIELD, c); + return (node*)n; } -/* (:return . c) */ +/* struct: return_node(value) */ static node* new_return(parser_state *p, node *c) { - return cons((node*)NODE_RETURN, c); + struct mrb_ast_return_node *n = NEW_NODE(return, NODE_RETURN); + n->args = c; + + return (node*)n; } -/* (:break . c) */ +/* struct: break_node(value) */ static node* new_break(parser_state *p, node *c) { - return cons((node*)NODE_BREAK, c); + struct mrb_ast_break_node *n = NEW_NODE(break, NODE_BREAK); + n->value = c; + return (node*)n; } -/* (:next . c) */ +/* struct: next_node(value) */ static node* new_next(parser_state *p, node *c) { - return cons((node*)NODE_NEXT, c); + struct mrb_ast_next_node *n = NEW_NODE(next, NODE_NEXT); + n->value = c; + return (node*)n; } -/* (:redo) */ +/* struct: redo_node() */ static node* new_redo(parser_state *p) { - return list1((node*)NODE_REDO); + struct mrb_ast_redo_node *n = NEW_NODE(redo, NODE_REDO); + return (node*)n; } -/* (:retry) */ +/* struct: retry_node() */ static node* new_retry(parser_state *p) { - return list1((node*)NODE_RETRY); + struct mrb_ast_retry_node *n = NEW_NODE(retry, NODE_RETRY); + return (node*)n; } -/* (:dot2 a b) */ +/* struct: dot2_node(beg, end) */ static node* new_dot2(parser_state *p, node *a, node *b) { - return cons((node*)NODE_DOT2, cons(a, b)); + struct mrb_ast_dot2_node *n = NEW_NODE(dot2, NODE_DOT2); + n->left = a; + n->right = b; + + return (node*)n; } -/* (:dot3 a b) */ +/* struct: dot3_node(beg, end) */ static node* new_dot3(parser_state *p, node *a, node *b) { - return cons((node*)NODE_DOT3, cons(a, b)); + struct mrb_ast_dot3_node *n = NEW_NODE(dot3, NODE_DOT3); + n->left = a; + n->right = b; + + return (node*)n; } -/* (:colon2 b c) */ +/* struct: colon2_node(base, name) */ static node* new_colon2(parser_state *p, node *b, mrb_sym c) { void_expr_error(p, b); - return cons((node*)NODE_COLON2, cons(b, nsym(c))); + + struct mrb_ast_colon2_node *colon2_node = NEW_NODE(colon2, NODE_COLON2); + colon2_node->base = b; + colon2_node->name = c; + return (node*)colon2_node; } -/* (:colon3 . c) */ +/* struct: colon3_node(name) */ static node* new_colon3(parser_state *p, mrb_sym c) { - return cons((node*)NODE_COLON3, nsym(c)); + struct mrb_ast_colon3_node *colon3_node = NEW_NODE(colon3, NODE_COLON3); + colon3_node->name = c; + return (node*)colon3_node; } -/* (:and a b) */ +/* struct: and_node(left, right) */ static node* new_and(parser_state *p, node *a, node *b) { void_expr_error(p, a); - return cons((node*)NODE_AND, cons(a, b)); + + struct mrb_ast_and_node *n = NEW_NODE(and, NODE_AND); + n->left = a; + n->right = b; + + return (node*)n; } -/* (:or a b) */ +/* struct: or_node(left, right) */ static node* new_or(parser_state *p, node *a, node *b) { void_expr_error(p, a); - return cons((node*)NODE_OR, cons(a, b)); + + struct mrb_ast_or_node *n = NEW_NODE(or, NODE_OR); + n->left = a; + n->right = b; + + return (node*)n; } -/* (:array a...) */ +/* struct: array_node(elements) - uses cons list */ static node* new_array(parser_state *p, node *a) { - return cons((node*)NODE_ARRAY, a); + struct mrb_ast_array_node *n = NEW_NODE(array, NODE_ARRAY); + n->elements = a; + + return (node*)n; } -/* (:splat . a) */ +/* struct: splat_node(value) */ static node* new_splat(parser_state *p, node *a) { void_expr_error(p, a); - return cons((node*)NODE_SPLAT, a); + + struct mrb_ast_splat_node *splat_node = NEW_NODE(splat, NODE_SPLAT); + splat_node->value = a; + return (node*)splat_node; } -/* (:hash (k . v) (k . v)...) */ +/* struct: hash_node(pairs) - uses cons list */ static node* new_hash(parser_state *p, node *a) { - return cons((node*)NODE_HASH, a); -} + struct mrb_ast_hash_node *n = NEW_NODE(hash, NODE_HASH); + n->pairs = a; -/* (:kw_hash (k . v) (k . v)...) */ -static node* -new_kw_hash(parser_state *p, node *a) -{ - return cons((node*)NODE_KW_HASH, a); + return (node*)n; } /* (:sym . a) */ +/* Symbol node creation - supports both variable and legacy modes */ static node* new_sym(parser_state *p, mrb_sym sym) { - return cons((node*)NODE_SYM, nsym(sym)); + struct mrb_ast_sym_node *n = NEW_NODE(sym, NODE_SYM); + n->symbol = sym; + + return (node*)n; } +static node* +new_xvar(parser_state *p, mrb_sym sym, enum node_type type) +{ + struct mrb_ast_var_node *n = NEW_NODE(var, type); + n->symbol = sym; + + return (node*)n; +} + +#define new_lvar(p, sym) new_xvar(p, sym, NODE_LVAR) +#define new_ivar(p, sym) new_xvar(p, sym, NODE_IVAR) +#define new_gvar(p, sym) new_xvar(p, sym, NODE_GVAR) +#define new_cvar(p, sym) new_xvar(p, sym, NODE_CVAR) + static mrb_sym new_strsym(parser_state *p, node* str) { - const char *s = (const char*)str->cdr->car; - size_t len = (size_t)str->cdr->cdr; + size_t len = (size_t)str->car; + const char *s = (const char*)str->cdr; return mrb_intern(p->mrb, s, len); } -/* (:lvar . a) */ -static node* -new_lvar(parser_state *p, mrb_sym sym) -{ - return cons((node*)NODE_LVAR, nsym(sym)); -} - -/* (:gvar . a) */ -static node* -new_gvar(parser_state *p, mrb_sym sym) -{ - return cons((node*)NODE_GVAR, nsym(sym)); -} - -/* (:ivar . a) */ -static node* -new_ivar(parser_state *p, mrb_sym sym) -{ - return cons((node*)NODE_IVAR, nsym(sym)); -} - -/* (:cvar . a) */ -static node* -new_cvar(parser_state *p, mrb_sym sym) -{ - return cons((node*)NODE_CVAR, nsym(sym)); -} - /* (:nvar . a) */ static node* new_nvar(parser_state *p, int num) @@ -747,132 +874,162 @@ new_nvar(parser_state *p, int num) int nvar; node *nvars = p->nvars->cdr; while (nvars) { - nvar = intn(nvars->car); + nvar = node_to_int(nvars->car); if (nvar == -2) break; /* top of the scope */ if (nvar > 0) { yyerror(NULL, p, "numbered parameter used in outer block"); break; } - nvars->car = nint(-1); + nvars->car = int_to_node(-1); nvars = nvars->cdr; } - nvar = intn(p->nvars->car); + nvar = node_to_int(p->nvars->car); if (nvar == -1) { yyerror(NULL, p, "numbered parameter used in inner block"); } else { - p->nvars->car = nint(nvar > num ? nvar : num); + p->nvars->car = int_to_node(nvar > num ? nvar : num); } - return cons((node*)NODE_NVAR, nint(num)); + struct mrb_ast_nvar_node *n = NEW_NODE(nvar, NODE_NVAR); + n->num = num; + return (node*)n; } -/* (:const . a) */ +/* struct: const_node(name) */ static node* new_const(parser_state *p, mrb_sym sym) { - return cons((node*)NODE_CONST, nsym(sym)); + struct mrb_ast_const_node *n = NEW_NODE(const, NODE_CONST); + n->symbol = sym; + + return (node*)n; } -/* (:undef a...) */ +/* struct: undef_node(syms) - uses cons list */ static node* -new_undef(parser_state *p, mrb_sym sym) +new_undef(parser_state *p, node *syms) { - return list2((node*)NODE_UNDEF, nsym(sym)); + struct mrb_ast_undef_node *undef_node = NEW_NODE(undef, NODE_UNDEF); + undef_node->syms = syms; + return (node*)undef_node; } -/* (:class class super body) */ +/* struct: class_node(path, super, body) */ static node* new_class(parser_state *p, node *c, node *s, node *b) { void_expr_error(p, s); - return list4((node*)NODE_CLASS, c, s, cons(locals_node(p), b)); + + struct mrb_ast_class_node *n = NEW_NODE(class, NODE_CLASS); + n->name = c; + n->superclass = s; + n->body = cons(locals_node(p), b); + + return (node*)n; } -/* (:sclass obj body) */ +/* struct: sclass_node(obj, body) */ static node* new_sclass(parser_state *p, node *o, node *b) { void_expr_error(p, o); - return list3((node*)NODE_SCLASS, o, cons(locals_node(p), b)); + + struct mrb_ast_sclass_node *n = NEW_NODE(sclass, NODE_SCLASS); + n->obj = o; + n->body = cons(locals_node(p), b); + + return (node*)n; } -/* (:module module body) */ +/* struct: module_node(path, body) */ static node* new_module(parser_state *p, node *m, node *b) { - return list3((node*)NODE_MODULE, m, cons(locals_node(p), b)); + struct mrb_ast_module_node *n = NEW_NODE(module, NODE_MODULE); + n->name = m; + n->body = cons(locals_node(p), b); + + return (node*)n; } -/* (:def m lv (arg . body)) */ +/* struct: def_node(name, args, body) */ static node* -new_def(parser_state *p, mrb_sym m, node *a, node *b) +new_def(parser_state *p, mrb_sym name) { - return list5((node*)NODE_DEF, nsym(m), 0, a, b); + struct mrb_ast_def_node *n = NEW_NODE(def, NODE_DEF); + n->name = name; + n->args = (struct mrb_ast_args *)int_to_node(p->cmdarg_stack); + n->locals = local_switch(p); + n->body = NULL; + + return (node*)n; } static void defn_setup(parser_state *p, node *d, node *a, node *b) { - node *n = d->cdr->cdr; + struct mrb_ast_def_node *n = def_node(d); + node *locals = n->locals; - n->car = locals_node(p); - p->cmdarg_stack = intn(n->cdr->car); - n->cdr->car = a; - local_resume(p, n->cdr->cdr->car); - n->cdr->cdr->car = b; + n->locals = locals_node(p); + p->cmdarg_stack = node_to_int(n->args); + n->args = (struct mrb_ast_args *)a; + n->body = b; + local_resume(p, locals); } -/* (:sdef obj m lv (arg . body)) */ +/* struct: sdef_node(obj, name, args, body) */ static node* -new_sdef(parser_state *p, node *o, mrb_sym m, node *a, node *b) +new_sdef(parser_state *p, node *o, mrb_sym name) { void_expr_error(p, o); - return list6((node*)NODE_SDEF, o, nsym(m), 0, a, b); -} -static void -defs_setup(parser_state *p, node *d, node *a, node *b) -{ - node *n = d->cdr->cdr->cdr; - - n->car = locals_node(p); - p->cmdarg_stack = intn(n->cdr->car); - n->cdr->car = a; - local_resume(p, n->cdr->cdr->car); - n->cdr->cdr->car = b; -} - -/* (:arg . sym) */ -static node* -new_arg(parser_state *p, mrb_sym sym) -{ - return cons((node*)NODE_ARG, nsym(sym)); + struct mrb_ast_sdef_node *sdef_node = NEW_NODE(sdef, NODE_SDEF); + sdef_node->obj = o; + sdef_node->name = name; + sdef_node->args = (struct mrb_ast_args *)int_to_node(p->cmdarg_stack); + sdef_node->locals = local_switch(p); + sdef_node->body = NULL; + return (node*)sdef_node; } static void local_add_margs(parser_state *p, node *n) { while (n) { - if (typen(n->car->car) == NODE_MASGN) { - node *t = n->car->cdr->cdr; + if (get_node_type(n->car) == NODE_MARG) { + struct mrb_ast_masgn_node *masgn_n = (struct mrb_ast_masgn_node*)n->car; + node *rhs = masgn_n->rhs; - n->car->cdr->cdr = NULL; - while (t) { - local_add_f(p, sym(t->car)); - t = t->cdr; + /* For parameter destructuring, rhs contains the locals */ + if (rhs) { + node *t = rhs; + while (t) { + local_add_f(p, node_to_sym(t->car)); + t = t->cdr; + } + /* Clear cons list RHS immediately after use */ + masgn_n->rhs = NULL; + } + + /* Process nested destructuring in lhs components */ + if (masgn_n->pre) { + local_add_margs(p, masgn_n->pre); + } + if (masgn_n->post) { + local_add_margs(p, masgn_n->post); } - local_add_margs(p, n->car->cdr->car->car); - local_add_margs(p, n->car->cdr->car->cdr->cdr->car); } n = n->cdr; } } + static void local_add_lv(parser_state *p, node *lv) { while (lv) { - local_add_f(p, sym(lv->car)); + local_add_f(p, node_to_sym(lv->car)); lv = lv->cdr; } } @@ -886,30 +1043,54 @@ local_add_lv(parser_state *p, node *lv) static node* new_args(parser_state *p, node *m, node *opt, mrb_sym rest, node *m2, node *tail) { - node *n; - local_add_margs(p, m); local_add_margs(p, m2); - n = cons(m2, tail); - n = cons(nsym(rest), n); - n = cons(opt, n); + + /* Save original optional arguments before processing */ + node *orig_opt = opt; + + /* Process optional arguments (keep original side effects) */ while (opt) { /* opt: (sym . (opt . lv)) -> (sym . opt) */ local_add_lv(p, opt->car->cdr->cdr); opt->car->cdr = opt->car->cdr->car; opt = opt->cdr; } - return cons(m, n); + + /* Allocate struct mrb_ast_args (no hdr) */ + struct mrb_ast_args *args = (struct mrb_ast_args*)parser_palloc(p, sizeof(struct mrb_ast_args)); + + /* Initialize members */ + args->mandatory_args = m; + args->optional_args = orig_opt; + args->rest_arg = rest; + args->post_mandatory_args = m2; + + /* Deconstruct tail cons list: (kws . (kwrest . blk)) */ + if (tail) { + args->keyword_args = (node*)tail->car; /* kws */ + args->kwrest_arg = (mrb_sym)(intptr_t)tail->cdr->car; /* kwrest */ + args->block_arg = (mrb_sym)(intptr_t)tail->cdr->cdr; /* blk */ + cons_free(tail->cdr); + cons_free(tail); + } + else { + args->keyword_args = NULL; + args->kwrest_arg = 0; + args->block_arg = 0; + } + + return (node*)args; } -/* (:args_tail keywords rest_keywords_sym block_sym) */ +/* struct: args_tail_node(kwargs, kwrest, block) */ static node* -new_args_tail(parser_state *p, node *kws, node *kwrest, mrb_sym blk) +new_args_tail(parser_state *p, node *kws, mrb_sym kwrest, mrb_sym blk) { node *k; if (kws || kwrest) { - local_add_kw(p, (kwrest && kwrest->cdr)? sym(kwrest->cdr) : 0); + local_add_kw(p, kwrest); } local_add_blk(p); @@ -918,34 +1099,35 @@ new_args_tail(parser_state *p, node *kws, node *kwrest, mrb_sym blk) /* allocate register for keywords arguments */ /* order is for Proc#parameters */ for (k = kws; k; k = k->cdr) { - if (!k->car->cdr->cdr->car) { /* allocate required keywords */ - local_add_f(p, sym(k->car->cdr->car)); + if (!k->car->cdr) { /* allocate required keywords - simplified structure: (key . NULL) */ + local_add_f(p, node_to_sym(k->car->car)); } } for (k = kws; k; k = k->cdr) { - if (k->car->cdr->cdr->car) { /* allocate keywords with default */ - local_add_lv(p, k->car->cdr->cdr->car->cdr); - k->car->cdr->cdr->car = k->car->cdr->cdr->car->car; - local_add_f(p, sym(k->car->cdr->car)); + if (k->car->cdr) { /* allocate keywords with default - simplified structure: (key . value) */ + local_add_lv(p, k->car->cdr->cdr); /* value->cdr for default args */ + k->car->cdr = k->car->cdr->car; /* value->car for default args */ + local_add_f(p, node_to_sym(k->car->car)); } } - return list4((node*)NODE_ARGS_TAIL, kws, kwrest, nsym(blk)); + /* Return cons list: (keyword . (kwrest . blk)) */ + return cons(kws, cons(sym_to_node(kwrest), sym_to_node(blk))); } -/* (:kw_arg kw_sym def_arg) */ +/* (kw_sym . def_arg) - simplified from NODE_KW_ARG wrapper */ static node* new_kw_arg(parser_state *p, mrb_sym kw, node *def_arg) { mrb_assert(kw); - return list3((node*)NODE_KW_ARG, nsym(kw), def_arg); + return cons(sym_to_node(kw), def_arg); } /* (:kw_rest_args . a) */ static node* new_kw_rest_args(parser_state *p, mrb_sym sym) { - return cons((node*)NODE_KW_REST_ARGS, nsym(sym)); + return sym_to_node(intern_op(pow)); /* Use ** symbol as direct marker */ } static node* @@ -955,26 +1137,29 @@ new_args_dots(parser_state *p, node *m) mrb_sym k = intern_op(pow); mrb_sym b = intern_op(and); local_add_f(p, r); - return new_args(p, m, 0, r, 0, - new_args_tail(p, 0, new_kw_rest_args(p, k), b)); + return new_args(p, m, 0, r, 0, new_args_tail(p, NULL, k, b)); } -/* (:block_arg . a) */ +/* struct: block_arg_node(value) */ static node* new_block_arg(parser_state *p, node *a) { - return cons((node*)NODE_BLOCK_ARG, a); + struct mrb_ast_block_arg_node *block_arg_node = NEW_NODE(block_arg, NODE_BLOCK_ARG); + block_arg_node->value = a; + return (node*)block_arg_node; } static node* setup_numparams(parser_state *p, node *a) { - int nvars = intn(p->nvars->car); + int nvars = node_to_int(p->nvars->car); if (nvars > 0) { int i; mrb_sym sym; - // m || opt || rest || tail - if (a && (a->car || (a->cdr && a->cdr->car) || (a->cdr->cdr && a->cdr->cdr->car) || (a->cdr->cdr->cdr->cdr && a->cdr->cdr->cdr->cdr->car))) { + // Check if any arguments are already defined + struct mrb_ast_args *args = (struct mrb_ast_args *)a; + if (a && (args->mandatory_args || args->optional_args || args->rest_arg || + args->post_mandatory_args || args->keyword_args || args->kwrest_arg)) { yyerror(NULL, p, "ordinary parameter is defined"); } else if (p->locals) { @@ -987,8 +1172,8 @@ setup_numparams(parser_state *p, node *a) buf[1] = i+'0'; buf[2] = '\0'; sym = intern_cstr(buf); - args = cons(new_arg(p, sym), args); - p->locals->car = cons(nsym(sym), p->locals->car); + args = cons(new_lvar(p, sym), args); + p->locals->car = cons(sym_to_node(sym), p->locals->car); } a = new_args(p, args, 0, 0, 0, 0); } @@ -996,58 +1181,120 @@ setup_numparams(parser_state *p, node *a) return a; } -/* (:block arg body) */ +/* struct: block_node(args, body) */ static node* new_block(parser_state *p, node *a, node *b) { - a = setup_numparams(p, a); - return list4((node*)NODE_BLOCK, locals_node(p), a, b); + a = setup_numparams(p, a); struct mrb_ast_block_node *n = NEW_NODE(block, NODE_BLOCK); + n->locals = locals_node(p); + n->args = (struct mrb_ast_args *)a; + n->body = b; + + return (node*)n; } -/* (:lambda arg body) */ +/* struct: lambda_node(args, body) */ static node* new_lambda(parser_state *p, node *a, node *b) { - a = setup_numparams(p, a); - return list4((node*)NODE_LAMBDA, locals_node(p), a, b); + a = setup_numparams(p, a); struct mrb_ast_lambda_node *lambda_node = NEW_NODE(lambda, NODE_LAMBDA); + lambda_node->locals = locals_node(p); + lambda_node->args = (struct mrb_ast_args *)a; + lambda_node->body = b; + return (node*)lambda_node; } -/* (:asgn lhs rhs) */ +/* struct: asgn_node(lhs, rhs) */ static node* new_asgn(parser_state *p, node *a, node *b) { void_expr_error(p, b); - return cons((node*)NODE_ASGN, cons(a, b)); + + struct mrb_ast_asgn_node *n = NEW_NODE(asgn, NODE_ASGN); + n->lhs = a; + n->rhs = b; + + return (node*)n; } -/* (:masgn mlhs=(pre rest post) mrhs) */ +/* Helper function to create MASGN/MARG nodes */ +static node* +new_masgn_helper(parser_state *p, node *a, node *b, enum node_type node_type) +{ + struct mrb_ast_masgn_node *n = NEW_NODE(masgn, node_type); + + /* Extract pre, rest, post from cons list structure (a b c) */ + if (a) { + n->pre = a->car; /* Pre-splat variables */ + if (a->cdr) { + n->rest = a->cdr->car; /* Splat variable (or -1 for anonymous) */ + if (a->cdr->cdr) { + n->post = a->cdr->cdr->car; /* Post-splat variables */ + cons_free(a->cdr->cdr); + } + else { + n->post = NULL; + } + cons_free(a->cdr); + } + else { + n->rest = NULL; + n->post = NULL; + } + cons_free(a); + } + else { + n->pre = NULL; + n->rest = NULL; + n->post = NULL; + } + n->rhs = b; + + return (node*)n; +} + +/* struct: masgn_node(lhs, rhs) */ static node* new_masgn(parser_state *p, node *a, node *b) { void_expr_error(p, b); - return cons((node*)NODE_MASGN, cons(a, b)); + return new_masgn_helper(p, a, b, NODE_MASGN); } -/* (:masgn mlhs mrhs) no check */ +/* (:marg mlhs mrhs) no check - for parameter destructuring */ static node* -new_masgn_param(parser_state *p, node *a, node *b) +new_marg(parser_state *p, node *a) { - return cons((node*)NODE_MASGN, cons(a, b)); + return new_masgn_helper(p, a, p->locals->car, NODE_MARG); } -/* (:asgn lhs rhs) */ +/* struct: op_asgn_node(lhs, op, rhs) */ static node* new_op_asgn(parser_state *p, node *a, mrb_sym op, node *b) { void_expr_error(p, b); - return list4((node*)NODE_OP_ASGN, a, nsym(op), b); + + struct mrb_ast_op_asgn_node *n = NEW_NODE(op_asgn, NODE_OP_ASGN); + n->lhs = a; + n->op = op; + n->rhs = b; + return (node*)n; +} + +static node* +new_int_n(parser_state *p, int32_t val) +{ + struct mrb_ast_int_node *n = NEW_NODE(int, NODE_INT); + n->value = val; + + return (node*)n; } static node* new_imaginary(parser_state *p, node *imaginary) { return new_fcall(p, MRB_SYM_2(p->mrb, Complex), - new_callargs(p, list2(list3((node*)NODE_INT, (node*)strdup("0"), nint(10)), imaginary), 0, 0)); + new_callargs(p, list2(new_int_n(p, 0), imaginary), 0, 0)); } static node* @@ -1056,26 +1303,121 @@ new_rational(parser_state *p, node *rational) return new_fcall(p, MRB_SYM_2(p->mrb, Rational), new_callargs(p, list1(rational), 0, 0)); } -/* (:int . i) */ +/* Read integer into int32_t with overflow detection */ +static mrb_bool +read_int32(const char *p, int base, int32_t *result) +{ + const char *e = p + strlen(p); + int32_t value = 0; + mrb_bool neg = FALSE; + + if (base < 2 || base > 16) { + return FALSE; + } + + if (*p == '+') { + p++; + } + else if (*p == '-') { + neg = TRUE; + p++; + } + + while (p < e) { + int n; + char c = *p; + + /* Skip underscores */ + if (c == '_') { + p++; + continue; + } + + /* Parse digit */ + if (c >= '0' && c <= '9') { + n = c - '0'; + } + else if (c >= 'a' && c <= 'f') { + n = c - 'a' + 10; + } + else if (c >= 'A' && c <= 'F') { + n = c - 'A' + 10; + } + else { + /* Invalid character */ + return FALSE; + } + + if (n >= base) { + /* Digit not valid for this base */ + return FALSE; + } + + /* Check for multiplication overflow */ + if (value > INT32_MAX / base) { + return FALSE; + } + + value *= base; + + /* Check for addition overflow */ + if (value > INT32_MAX - n) { + /* Special case: -INT32_MIN is valid */ + if (neg && value == (INT32_MAX - n + 1) && p + 1 == e) { + *result = INT32_MIN; + return TRUE; + } + return FALSE; + } + + value += n; + p++; + } + + *result = neg ? -value : value; + return TRUE; +} + static node* new_int(parser_state *p, const char *s, int base, int suffix) { - node* result = list3((node*)NODE_INT, (node*)strdup(s), nint(base)); + int32_t val; + node* result; + + /* Try to parse as int32_t first */ + if (read_int32(s, base, &val)) { + result = new_int_n(p, val); + } + else { + /* Big integer - create NODE_BIGINT */ + struct mrb_ast_bigint_node *n = NEW_NODE(bigint, NODE_BIGINT); + n->string = strdup(s); + n->base = base; + + result = (node*)n; + } + + /* Handle suffix modifiers */ if (suffix & NUM_SUFFIX_R) { result = new_rational(p, result); } if (suffix & NUM_SUFFIX_I) { result = new_imaginary(p, result); } + return result; } #ifndef MRB_NO_FLOAT -/* (:float . i) */ +/* struct: float_node(value) */ static node* new_float(parser_state *p, const char *s, int suffix) { - node* result = cons((node*)NODE_FLOAT, (node*)strdup(s)); + struct mrb_ast_float_node *n = NEW_NODE(float, NODE_FLOAT); + n->value = strdup(s); + + node* result = (node*)n; + if (suffix & NUM_SUFFIX_R) { result = new_rational(p, result); } @@ -1086,155 +1428,85 @@ new_float(parser_state *p, const char *s, int suffix) } #endif -/* (:str . (s . len)) */ +/* Create string node from cons list */ +/* struct: str_node(str) */ static node* -new_str(parser_state *p, const char *s, size_t len) +new_str(parser_state *p, node *a) { - return cons((node*)NODE_STR, cons((node*)strndup(s, len), nint(len))); + struct mrb_ast_str_node *n = NEW_NODE(str, NODE_STR); + n->list = a; + + return (node*)n; } -/* (:dstr . a) */ +/* struct: xstr_node(str) */ static node* -new_dstr(parser_state *p, node *a) +new_xstr(parser_state *p, node *a) { - return cons((node*)NODE_DSTR, a); + struct mrb_ast_xstr_node *n = NEW_NODE(xstr, NODE_XSTR); + n->list = a; + return (node*)n; } -static int -string_node_p(node *n) -{ - return (int)(typen(n->car) == NODE_STR); -} - -static node* -composite_string_node(parser_state *p, node *a, node *b) -{ - size_t newlen = (size_t)a->cdr + (size_t)b->cdr; - char *str = (char*)mempool_realloc(p->pool, a->car, (size_t)a->cdr + 1, newlen + 1); - memcpy(str + (size_t)a->cdr, b->car, (size_t)b->cdr); - str[newlen] = '\0'; - a->car = (node*)str; - a->cdr = (node*)newlen; - cons_free(b); - return a; -} - -static node* -concat_string(parser_state *p, node *a, node *b) -{ - if (string_node_p(a)) { - if (string_node_p(b)) { - /* a == NODE_STR && b == NODE_STR */ - composite_string_node(p, a->cdr, b->cdr); - cons_free(b); - return a; - } - else { - /* a == NODE_STR && b == NODE_DSTR */ - - if (string_node_p(b->cdr->car)) { - /* a == NODE_STR && b->[NODE_STR, ...] */ - composite_string_node(p, a->cdr, b->cdr->car->cdr); - cons_free(b->cdr->car); - b->cdr->car = a; - return b; - } - } - } - else { - node *c; /* last node of a */ - for (c = a; c->cdr != NULL; c = c->cdr) - ; - if (string_node_p(b)) { - /* a == NODE_DSTR && b == NODE_STR */ - if (string_node_p(c->car)) { - /* a->[..., NODE_STR] && b == NODE_STR */ - composite_string_node(p, c->car->cdr, b->cdr); - cons_free(b); - return a; - } - - push(a, b); - return a; - } - else { - /* a == NODE_DSTR && b == NODE_DSTR */ - if (string_node_p(c->car) && string_node_p(b->cdr->car)) { - /* a->[..., NODE_STR] && b->[NODE_STR, ...] */ - node *d = b->cdr; - cons_free(b); - composite_string_node(p, c->car->cdr, d->car->cdr); - cons_free(d->car); - c->cdr = d->cdr; - cons_free(d); - return a; - } - else { - c->cdr = b->cdr; - cons_free(b); - return a; - } - } - } - - return new_dstr(p, list2(a, b)); -} - -/* (:str . (s . len)) */ -static node* -new_xstr(parser_state *p, const char *s, int len) -{ - return cons((node*)NODE_XSTR, cons((node*)strndup(s, len), nint(len))); -} - -/* (:xstr . a) */ -static node* -new_dxstr(parser_state *p, node *a) -{ - return cons((node*)NODE_DXSTR, a); -} - -/* (:dsym . a) */ +/* struct: dsym_node(parts) - uses cons list */ static node* new_dsym(parser_state *p, node *a) { - return cons((node*)NODE_DSYM, a); + struct mrb_ast_str_node *n = NEW_NODE(str, NODE_DSYM); + n->list = a; + return (node*)n; } -/* (:regx . (s . (opt . enc))) */ +/* struct: regx_node(pattern, flags, encoding) */ static node* -new_regx(parser_state *p, const char *p1, const char* p2, const char* p3) +new_regx(parser_state *p, node *list, const char *flags, const char *encoding) { - return cons((node*)NODE_REGX, cons((node*)p1, cons((node*)p2, (node*)p3))); + struct mrb_ast_regx_node *n = NEW_NODE(regx, NODE_REGX); + n->list = list; + n->flags = flags; + n->encoding = encoding; + return (node*)n; } -/* (:dregx . (a . b)) */ -static node* -new_dregx(parser_state *p, node *a, node *b) -{ - return cons((node*)NODE_DREGX, cons(a, b)); -} - -/* (:backref . n) */ +/* struct: back_ref_node(n) */ static node* new_back_ref(parser_state *p, int n) { - return cons((node*)NODE_BACK_REF, nint(n)); + struct mrb_ast_back_ref_node *backref_node = NEW_NODE(back_ref, NODE_BACK_REF); + backref_node->type = n; + return (node*)backref_node; } -/* (:nthref . n) */ +/* struct: nth_ref_node(n) */ static node* new_nth_ref(parser_state *p, int n) { - return cons((node*)NODE_NTH_REF, nint(n)); + struct mrb_ast_nth_ref_node *nthref_node = NEW_NODE(nth_ref, NODE_NTH_REF); + nthref_node->nth = n; + return (node*)nthref_node; } -/* (:heredoc . a) */ +/* struct: heredoc_node(str) */ static node* -new_heredoc(parser_state *p) +new_heredoc(parser_state *p, struct mrb_parser_heredoc_info **infop) { - parser_heredoc_info *inf = (parser_heredoc_info*)parser_palloc(p, sizeof(parser_heredoc_info)); - return cons((node*)NODE_HEREDOC, (node*)inf); + struct mrb_ast_heredoc_node *n = NEW_NODE(heredoc, NODE_HEREDOC); + + /* Initialize embedded heredoc info struct */ + n->info.allow_indent = FALSE; + n->info.remove_indent = FALSE; + n->info.line_head = FALSE; + n->info.indent = 0; + n->info.indented = NULL; + n->info.type = str_not_parsing; // Will be set by heredoc processing + n->info.term = NULL; // Will be set by heredoc processing + n->info.term_len = 0; + n->info.doc = NULL; + + /* Return pointer to embedded info if requested */ + *infop = &n->info; + + return (node*)n; } static void @@ -1245,21 +1517,46 @@ new_bv(parser_state *p, mrb_sym id) static node* new_literal_delim(parser_state *p) { - return cons((node*)NODE_LITERAL_DELIM, 0); + return cons((node*)0, (node*)0); +} + +/* Helper for creating string representation cons (length . string_ptr) */ +static node* +new_str_rep(parser_state *p, const char *str, int len) +{ + return cons(int_to_node(len), (node*)strndup(str, len)); +} + +/* Helper for creating string representation from current token */ +static node* +new_str_tok(parser_state *p) +{ + return new_str_rep(p, tok(p), toklen(p)); +} + +/* Helper for creating empty string representation */ +static node* +new_str_empty(parser_state *p) +{ + return new_str_rep(p, "", 0); } /* (:words . a) */ static node* new_words(parser_state *p, node *a) { - return cons((node*)NODE_WORDS, a); + struct mrb_ast_words_node *words_node = NEW_NODE(words, NODE_WORDS); + words_node->args = a; + return (node*)words_node; } /* (:symbols . a) */ static node* new_symbols(parser_state *p, node *a) { - return cons((node*)NODE_SYMBOLS, a); + struct mrb_ast_symbols_node *symbols_node = NEW_NODE(symbols, NODE_SYMBOLS); + symbols_node->args = a; + return (node*)symbols_node; } /* xxx ----------------------------- */ @@ -1283,17 +1580,20 @@ static void args_with_block(parser_state *p, node *a, node *b) { if (b) { - if (a->cdr && a->cdr->cdr) { + /* Handle callargs structure - direct casting like new_args() */ + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)a; + if (callargs->block_arg) { yyerror(NULL, p, "both block arg and actual block given"); } - a->cdr->cdr = b; + callargs->block_arg = b; } } static void endless_method_name(parser_state *p, node *defn) { - mrb_sym sym = sym(defn->cdr->car); + struct mrb_ast_def_node *def = (struct mrb_ast_def_node*)defn; + mrb_sym sym = def->name; mrb_int len; const char *name = mrb_sym_name_len(p->mrb, sym, &len); @@ -1308,29 +1608,80 @@ endless_method_name(parser_state *p, node *defn) static void call_with_block(parser_state *p, node *a, node *b) { - node *n; + if (!a) return; - switch (typen(a->car)) { + /* Handle direct variable-sized nodes */ + struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)a; + + enum node_type var_type = (enum node_type)header->node_type; + switch (var_type) { case NODE_SUPER: case NODE_ZSUPER: - if (!a->cdr) a->cdr = new_callargs(p, 0, 0, b); - else args_with_block(p, a->cdr, b); + /* For variable-sized super/zsuper nodes, update the args field directly */ + { + struct mrb_ast_super_node *super_n = super_node(a); + if (!super_n->args) { + super_n->args = new_callargs(p, 0, 0, b); + } + else { + args_with_block(p, super_n->args, b); + } + } break; - case NODE_CALL: - case NODE_FCALL: - case NODE_SCALL: - /* (NODE_CALL recv mid (args kw . blk)) */ - n = a->cdr->cdr->cdr; /* (args kw . blk) */ - if (!n->car) n->car = new_callargs(p, 0, 0, b); - else args_with_block(p, n->car, b); + case NODE_YIELD: + /* Variable-sized yield nodes should generate an error when given a block */ + yyerror(NULL, p, "block given to yield"); break; case NODE_RETURN: + /* Variable-sized return nodes - recursively call with args */ + { + struct mrb_ast_return_node *return_n = return_node(a); + if (return_n->args != NULL) { + call_with_block(p, return_n->args, b); + } + } + break; case NODE_BREAK: + /* Variable-sized break nodes - recursively call with value */ + { + struct mrb_ast_break_node *break_n = (struct mrb_ast_break_node*)a; + if (break_n->value != NULL) { + call_with_block(p, break_n->value, b); + } + } + break; case NODE_NEXT: - if (a->cdr == NULL) return; - call_with_block(p, a->cdr, b); + /* Variable-sized next nodes - recursively call with value */ + { + struct mrb_ast_next_node *next_n = (struct mrb_ast_next_node*)a; + if (next_n->value != NULL) { + call_with_block(p, next_n->value, b); + } + } + break; + case NODE_CALL: + /* Variable-sized call nodes - add block to existing args */ + { + struct mrb_ast_call_node *call = call_node(a); + + if (call->args && callargs_node(call->args)->block_arg) { + yyerror(NULL, p, "both block arg and actual block given"); + return; + } + + /* Use existing args and add block */ + if (call->args) { + /* Modify existing callargs structure to add block */ + args_with_block(p, call->args, b); + } + else { + /* Create new callargs with just the block */ + call->args = new_callargs(p, NULL, NULL, b); + } + } break; default: + /* For other variable-sized nodes, do nothing */ break; } } @@ -1338,7 +1689,9 @@ call_with_block(parser_state *p, node *a, node *b) static node* new_negate(parser_state *p, node *n) { - return cons((node*)NODE_NEGATE, n); + struct mrb_ast_negate_node *negate_node = NEW_NODE(negate, NODE_NEGATE); + negate_node->operand = n; + return (node*)negate_node; } static node* @@ -1350,36 +1703,43 @@ cond(node *n) static node* ret_args(parser_state *p, node *n) { - if (n->cdr->cdr) { + /* Handle callargs structure - direct casting like new_args() */ + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)n; + if (callargs->block_arg) { yyerror(NULL, p, "block argument should not be given"); return NULL; } - if (!n->car) return NULL; - if (!n->car->cdr) return n->car->car; - return new_array(p, n->car); + if (!callargs->regular_args) return NULL; + if (!callargs->regular_args->cdr) return callargs->regular_args->car; + return new_array(p, callargs->regular_args); } static void assignable(parser_state *p, node *lhs) { - switch (intn(lhs->car)) { + switch (get_node_type(lhs)) { case NODE_LVAR: - local_add(p, sym(lhs->cdr)); + local_add(p, var_node(lhs)->symbol); break; case NODE_CONST: if (p->in_def) yyerror(NULL, p, "dynamic constant assignment"); break; + default: + /* Other node types don't need special handling in assignable */ + break; } } static node* var_reference(parser_state *p, node *lhs) { - if (intn(lhs->car) == NODE_LVAR) { - if (!local_var_p(p, sym(lhs->cdr))) { - node *n = new_fcall(p, sym(lhs->cdr), 0); - cons_free(lhs); + /* Check if this is a variable-sized node */ + if (node_type_p(lhs, NODE_LVAR)) { + mrb_sym sym = var_node(lhs)->symbol; + if (!local_var_p(p, sym)) { + node *n = new_fcall(p, sym, 0); + /* Don't free variable-sized nodes - they're managed by the parser allocator */ return n; } } @@ -1454,6 +1814,11 @@ parsing_heredoc_info(parser_state *p) node *nd = p->parsing_heredoc; if (nd == NULL) return NULL; /* mrb_assert(nd->car->car == NODE_HEREDOC); */ + if (get_node_type(nd->car) == NODE_HEREDOC) { + /* Variable-sized heredoc node - return address of embedded info struct */ + struct mrb_ast_heredoc_node *heredoc = (struct mrb_ast_heredoc_node*)nd->car; + return &heredoc->info; + } return (parser_heredoc_info*)nd->car->cdr; } @@ -1491,14 +1856,12 @@ prohibit_literals(parser_state *p, node *n) yyerror(NULL, p, "can't define singleton method for ()."); } else { - switch (typen(n->car)) { + enum node_type nt = get_node_type(n); + switch (nt) { case NODE_INT: case NODE_STR: - case NODE_DSTR: case NODE_XSTR: - case NODE_DXSTR: - case NODE_DREGX: - case NODE_MATCH: + case NODE_REGX: case NODE_FLOAT: case NODE_ARRAY: case NODE_HEREDOC: @@ -1617,9 +1980,9 @@ prohibit_literals(parser_state *p, node *n) %type heredoc words symbols %type call_op call_op2 /* 0:'&.', 1:'.', 2:'::' */ -%type args_tail opt_args_tail f_kwarg f_kw f_kwrest +%type args_tail opt_args_tail f_kwarg f_kw %type f_block_kwarg f_block_kw block_args_tail opt_block_args_tail -%type f_label +%type f_label f_kwrest %token tUPLUS "unary plus" %token tUMINUS "unary minus" @@ -1696,7 +2059,6 @@ program : { top_compstmt { p->tree = new_scope(p, $2); - NODE_LINENO(p->tree, $2); } ; @@ -1713,11 +2075,10 @@ top_stmts : none | top_stmt { $$ = new_stmts(p, $1); - NODE_LINENO($$, $1); } | top_stmts terms top_stmt { - $$ = push($1, newline_node($3)); + $$ = stmts_push(p, $1, newline_node($3)); } | error top_stmt { @@ -1747,11 +2108,10 @@ bodystmt : compstmt { if ($2) { $$ = new_rescue(p, $1, $2, $3); - NODE_LINENO($$, $1); } else if ($3) { yywarning(p, "else without rescue is useless"); - $$ = push($1, $3); + $$ = stmts_push(p, $1, $3); } else { $$ = $1; @@ -1780,11 +2140,10 @@ stmts : none | stmt { $$ = new_stmts(p, $1); - NODE_LINENO($$, $1); } | stmts terms stmt { - $$ = push($1, newline_node($3)); + $$ = stmts_push(p, $1, newline_node($3)); } | error stmt { @@ -1798,7 +2157,7 @@ stmt : keyword_alias fsym {p->lstate = EXPR_FNAME;} fsym } | keyword_undef undef_list { - $$ = $2; + $$ = new_undef(p, $2); } | stmt modifier_if expr_value { @@ -1806,11 +2165,11 @@ stmt : keyword_alias fsym {p->lstate = EXPR_FNAME;} fsym } | stmt modifier_unless expr_value { - $$ = new_unless(p, cond($3), $1, 0); + $$ = new_if(p, cond($3), 0, $1); } | stmt modifier_while expr_value { - if ($1 && typen($1->car) == NODE_BEGIN) { + if ($1 && node_type_p($1, NODE_BEGIN)) { $$ = new_while_mod(p, cond($3), $1); } else { @@ -1819,7 +2178,7 @@ stmt : keyword_alias fsym {p->lstate = EXPR_FNAME;} fsym } | stmt modifier_until expr_value { - if ($1 && typen($1->car) == NODE_BEGIN) { + if ($1 && node_type_p($1, NODE_BEGIN)) { $$ = new_until_mod(p, cond($3), $1); } else { @@ -1912,7 +2271,7 @@ command_asgn : lhs '=' command_rhs { $$ = $1; void_expr_error(p, $4); - defs_setup(p, $$, $2, $4); + defn_setup(p, $$, $2, $4); nvars_unnest(p); p->in_def--; p->in_single--; @@ -1921,7 +2280,7 @@ command_asgn : lhs '=' command_rhs { $$ = $1; void_expr_error(p, $4); - defs_setup(p, $$, $2, new_mod_rescue(p, $4, $6)); + defn_setup(p, $$, $2, new_mod_rescue(p, $4, $6)); nvars_unnest(p); p->in_def--; p->in_single--; @@ -1941,7 +2300,6 @@ command_rhs : command_call %prec tOP_ASGN | command_asgn ; - expr : command_call | expr keyword_and expr { @@ -1962,10 +2320,9 @@ expr : command_call | arg ; - defn_head : keyword_def fname { - $$ = new_def(p, $2, nint(p->cmdarg_stack), local_switch(p)); + $$ = new_def(p, $2); p->cmdarg_stack = 0; p->in_def++; nvars_block(p); @@ -1978,7 +2335,7 @@ defs_head : keyword_def singleton dot_or_colon } fname { - $$ = new_sdef(p, $2, $5, nint(p->cmdarg_stack), local_switch(p)); + $$ = new_sdef(p, $2, $5); p->cmdarg_stack = 0; p->in_def++; p->in_single++; @@ -2248,16 +2605,16 @@ cname : tIDENTIFIER cpath : tCOLON3 cname { - $$ = cons(nint(1), nsym($2)); + $$ = cons(int_to_node(1), sym_to_node($2)); } | cname { - $$ = cons(nint(0), nsym($1)); + $$ = cons(int_to_node(0), sym_to_node($1)); } | primary_value tCOLON2 cname { void_expr_error(p, $1); - $$ = cons($1, nsym($3)); + $$ = cons($1, sym_to_node($3)); } ; @@ -2282,11 +2639,11 @@ fsym : fname undef_list : fsym { - $$ = new_undef(p, $1); + $$ = cons(sym_to_node($1), 0); } | undef_list ',' {p->lstate = EXPR_FNAME;} fsym { - $$ = push($1, nsym($4)); + $$ = push($1, sym_to_node($4)); } ; @@ -2545,7 +2902,7 @@ arg : lhs '=' arg_rhs { $$ = $1; void_expr_error(p, $4); - defs_setup(p, $$, $2, $4); + defn_setup(p, $$, $2, $4); nvars_unnest(p); p->in_def--; p->in_single--; @@ -2554,7 +2911,7 @@ arg : lhs '=' arg_rhs { $$ = $1; void_expr_error(p, $4); - defs_setup(p, $$, $2, new_mod_rescue(p, $4, $6)); + defn_setup(p, $$, $2, new_mod_rescue(p, $4, $6)); nvars_unnest(p); p->in_def--; p->in_single--; @@ -2569,7 +2926,6 @@ aref_args : none | args trailer { $$ = $1; - NODE_LINENO($$, $1); } | args comma assocs trailer { @@ -2577,8 +2933,7 @@ aref_args : none } | assocs trailer { - $$ = cons(new_kw_hash(p, $1), 0); - NODE_LINENO($$, $1); + $$ = cons(new_hash(p, $1), 0); } ; @@ -2603,7 +2958,7 @@ paren_args : '(' opt_call_args ')' mrb_sym k = intern_op(pow); mrb_sym b = intern_op(and); $$ = new_callargs(p, push($2, new_splat(p, new_lvar(p, r))), - new_kw_hash(p, list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k)))), + list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k))), new_block_arg(p, new_lvar(p, b))); } | '(' tBDOT3 rparen @@ -2613,7 +2968,7 @@ paren_args : '(' opt_call_args ')' mrb_sym b = intern_op(and); if (local_var_p(p, r) && local_var_p(p, k) && local_var_p(p, b)) { $$ = new_callargs(p, list1(new_splat(p, new_lvar(p, r))), - new_kw_hash(p, list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k)))), + list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k))), new_block_arg(p, new_lvar(p, b))); } else { @@ -2632,17 +2987,14 @@ opt_call_args : none | args comma { $$ = new_callargs(p,$1,0,0); - NODE_LINENO($$, $1); } | args comma assocs comma { - $$ = new_callargs(p,$1,new_kw_hash(p,$3),0); - NODE_LINENO($$, $1); + $$ = new_callargs(p,$1,$3,0); } | assocs comma { - $$ = new_callargs(p,0,new_kw_hash(p,$1),0); - NODE_LINENO($$, $1); + $$ = new_callargs(p,0,$1,0); } ; @@ -2650,27 +3002,22 @@ call_args : command { void_expr_error(p, $1); $$ = new_callargs(p, list1($1), 0, 0); - NODE_LINENO($$, $1); } | args opt_block_arg { $$ = new_callargs(p, $1, 0, $2); - NODE_LINENO($$, $1); } | assocs opt_block_arg { - $$ = new_callargs(p, 0, new_kw_hash(p, $1), $2); - NODE_LINENO($$, $1); + $$ = new_callargs(p, 0, $1, $2); } | args comma assocs opt_block_arg { - $$ = new_callargs(p, $1, new_kw_hash(p, $3), $4); - NODE_LINENO($$, $1); + $$ = new_callargs(p, $1, $3, $4); } | block_arg { $$ = new_callargs(p, 0, 0, $1); - NODE_LINENO($$, $1); } ; @@ -2712,7 +3059,6 @@ args : arg { void_expr_error(p, $1); $$ = list1($1); - NODE_LINENO($$, $1); } | tSTAR { @@ -2721,7 +3067,6 @@ args : arg | tSTAR arg { $$ = list1(new_splat(p, $2)); - NODE_LINENO($$, $2); } | args comma arg { @@ -2755,7 +3100,13 @@ mrhs : args comma arg primary : literal | string + { + $$ = new_str(p, $1); + } | xstring + { + $$ = new_xstr(p, $1); + } | regexp | heredoc | var_ref @@ -2804,12 +3155,10 @@ primary : literal | tLBRACK aref_args ']' { $$ = new_array(p, $2); - NODE_LINENO($$, $2); } | tLBRACE assoc_list '}' { $$ = new_hash(p, $2); - NODE_LINENO($$, $2); } | keyword_return { @@ -2871,7 +3220,7 @@ primary : literal opt_else keyword_end { - $$ = new_unless(p, cond($2), $4, $5); + $$ = new_if(p, cond($2), $5, $4); SET_LINENO($$, $1); } | keyword_while {COND_PUSH(1);} expr_value do {COND_POP();} @@ -2932,7 +3281,7 @@ primary : literal } term { - $$ = cons(local_switch(p), nint(p->in_single)); + $$ = cons(local_switch(p), int_to_node(p->in_single)); nvars_block(p); p->in_single = 0; } @@ -2944,7 +3293,7 @@ primary : literal local_resume(p, $6->car); nvars_unnest(p); p->in_def = $4; - p->in_single = intn($6->cdr); + p->in_single = node_to_int($6->cdr); } | keyword_module cpath @@ -2978,7 +3327,7 @@ primary : literal keyword_end { $$ = $1; - defs_setup(p, $$, $2, $3); + defn_setup(p, $$, $2, $3); nvars_unnest(p); p->in_def--; p->in_single--; @@ -3046,33 +3395,33 @@ f_margs : f_arg } | f_arg ',' tSTAR f_norm_arg { - $$ = list3($1, new_arg(p, $4), 0); + $$ = list3($1, new_lvar(p, $4), 0); } | f_arg ',' tSTAR f_norm_arg ',' f_arg { - $$ = list3($1, new_arg(p, $4), $6); + $$ = list3($1, new_lvar(p, $4), $6); } | f_arg ',' tSTAR { local_add_f(p, intern_op(mul)); - $$ = list3($1, nint(-1), 0); + $$ = list3($1, int_to_node(-1), 0); } | f_arg ',' tSTAR ',' f_arg { - $$ = list3($1, nint(-1), $5); + $$ = list3($1, int_to_node(-1), $5); } | tSTAR f_norm_arg { - $$ = list3(0, new_arg(p, $2), 0); + $$ = list3(0, new_lvar(p, $2), 0); } | tSTAR f_norm_arg ',' f_arg { - $$ = list3(0, new_arg(p, $2), $4); + $$ = list3(0, new_lvar(p, $2), $4); } | tSTAR { local_add_f(p, intern_op(mul)); - $$ = list3(0, nint(-1), 0); + $$ = list3(0, int_to_node(-1), 0); } | tSTAR ',' { @@ -3080,7 +3429,7 @@ f_margs : f_arg } f_arg { - $$ = list3(0, nint(-1), $4); + $$ = list3(0, int_to_node(-1), $4); } ; @@ -3201,7 +3550,6 @@ block_param_def : '|' {local_add_blk(p);} opt_bv_decl '|' } ; - opt_bv_decl : opt_nl { $$ = 0; @@ -3263,12 +3611,7 @@ do_block : keyword_do_block block_call : command do_block { - if (typen($1->car) == NODE_YIELD) { - yyerror(&@1, p, "block given to yield"); - } - else { - call_with_block(p, $1, $2); - } + call_with_block(p, $1, $2); $$ = $1; } | block_call call_op2 operation2 opt_paren_args @@ -3416,26 +3759,28 @@ literal : numeric string : string_fragment | string string_fragment { - $$ = concat_string(p, $1, $2); + $$ = append($1, $2); } ; string_fragment : tCHAR + { + /* tCHAR is (len . str), wrap as cons list */ + $$ = list1($1); + } | tSTRING + { + /* tSTRING is (len . str), wrap as cons list */ + $$ = list1($1); + } | tSTRING_BEG tSTRING { - $$ = $2; + /* $2 is (len . str), wrap as cons list */ + $$ = list1($2); } | tSTRING_BEG string_rep tSTRING { - node *n = $2; - if (intn($3->cdr->cdr) > 0) { - n = push(n, $3); - } - else { - cons_free($3); - } - $$ = new_dstr(p, n); + $$ = push($2, $3); } ; @@ -3448,6 +3793,7 @@ string_rep : string_interp string_interp : tSTRING_MID { + /* $1 is already in (len . str) format */ $$ = list1($1); } | tSTRING_PART @@ -3458,7 +3804,9 @@ string_interp : tSTRING_MID '}' { pop_strterm(p,$2); - $$ = list2($1, $3); + /* $1 is already in (len . str) format, create (-1 . node) for expression */ + node *expr_elem = cons(int_to_node(-1), $3); + $$ = list2($1, expr_elem); } | tLITERAL_DELIM { @@ -3472,28 +3820,31 @@ string_interp : tSTRING_MID xstring : tXSTRING_BEG tXSTRING { - $$ = $2; + $$ = cons($2, (node*)NULL); } | tXSTRING_BEG string_rep tXSTRING { - node *n = $2; - if (intn($3->cdr->cdr) > 0) { - n = push(n, $3); - } - else { - cons_free($3); - } - $$ = new_dxstr(p, n); + $$ = push($2, $3); } ; regexp : tREGEXP_BEG tREGEXP { - $$ = $2; + node *data = $2; /* ((len . pattern) . (flags . encoding)) */ + const char *flags = (const char*)data->cdr->car; + const char *encoding = (const char*)data->cdr->cdr; + /* Use data->car directly as pattern_list: (len . pattern) */ + node *pattern_list = cons(data->car, (node*)NULL); + $$ = new_regx(p, pattern_list, flags, encoding); } | tREGEXP_BEG string_rep tREGEXP { - $$ = new_dregx(p, $2, $3); + node *data = $3; /* ((len . pattern) . (flags . encoding)) */ + const char *flags = (const char*)data->cdr->car; + const char *encoding = (const char*)data->cdr->cdr; + /* Append the pattern from $3->car to the string list $2 */ + node *complete_list = push($2, data->car); + $$ = new_regx(p, complete_list, flags, encoding); } ; @@ -3507,7 +3858,7 @@ heredoc_bodies : heredoc_body heredoc_body : tHEREDOC_END { parser_heredoc_info *info = parsing_heredoc_info(p); - info->doc = push(info->doc, new_str(p, "", 0)); + info->doc = push(info->doc, new_str_empty(p)); heredoc_end(p); } | heredoc_string_rep tHEREDOC_END @@ -3535,7 +3886,9 @@ heredoc_string_interp : tHD_STRING_MID { pop_strterm(p, $2); parser_heredoc_info *info = parsing_heredoc_info(p); - info->doc = push(push(info->doc, $1), $3); + /* $1 is already in (len . str) format, create (-1 . node) for expression */ + node *expr_elem = cons(int_to_node(-1), $3); + info->doc = push(push(info->doc, $1), expr_elem); } ; @@ -3546,17 +3899,11 @@ words : tWORDS_BEG tSTRING | tWORDS_BEG string_rep tSTRING { node *n = $2; - if (intn($3->cdr->cdr) > 0) { - n = push(n, $3); - } - else { - cons_free($3); - } + n = push(n, $3); $$ = new_words(p, n); } ; - symbol : basic_symbol { $$ = new_sym(p, $1); @@ -3565,13 +3912,13 @@ symbol : basic_symbol { node *n = $3; p->lstate = EXPR_ENDARG; - if (intn($4->cdr->cdr) > 0) { + if (node_to_int($4->car) > 0) { n = push(n, $4); } else { cons_free($4); } - $$ = new_dsym(p, new_dstr(p, n)); + $$ = new_dsym(p, n); } | tSYMBEG tNUMPARAM { @@ -3608,9 +3955,7 @@ symbols : tSYMBOLS_BEG tSTRING | tSYMBOLS_BEG string_rep tSTRING { node *n = $2; - if (intn($3->cdr->cdr) > 0) { - n = push(n, $3); - } + n = push(n, $3); $$ = new_symbols(p, n); } ; @@ -3689,7 +4034,7 @@ var_ref : variable if (!fn) { fn = "(null)"; } - $$ = new_str(p, fn, strlen(fn)); + $$ = new_str(p, cons(cons(int_to_node(strlen(fn)), (node*)fn), (node*)NULL)); } | keyword__LINE__ { @@ -3826,11 +4171,11 @@ kwrest_mark : tPOW f_kwrest : kwrest_mark tIDENTIFIER { - $$ = new_kw_rest_args(p, $2); + $$ = $2; } | kwrest_mark { - $$ = new_kw_rest_args(p, 0); + $$ = intern_op(pow); } ; @@ -3965,7 +4310,7 @@ f_norm_arg : f_bad_arg f_arg_item : f_norm_arg { - $$ = new_arg(p, $1); + $$ = new_lvar(p, $1); } | tLPAREN { @@ -3973,7 +4318,7 @@ f_arg_item : f_norm_arg } f_margs rparen { - $$ = new_masgn_param(p, $3, p->locals->car); + $$ = new_marg(p, $3); local_resume(p, $2); local_add_f(p, 0); } @@ -4000,7 +4345,7 @@ f_opt_asgn : tIDENTIFIER '=' f_opt : f_opt_asgn arg { void_expr_error(p, $2); - $$ = cons(nsym($1), cons($2, locals_node(p))); + $$ = cons(sym_to_node($1), cons($2, locals_node(p))); local_unnest(p); } ; @@ -4008,7 +4353,7 @@ f_opt : f_opt_asgn arg f_block_opt : f_opt_asgn primary_value { void_expr_error(p, $2); - $$ = cons(nsym($1), cons($2, locals_node(p))); + $$ = cons(sym_to_node($1), cons($2, locals_node(p))); local_unnest(p); } ; @@ -4096,7 +4441,6 @@ assoc_list : none assocs : assoc { $$ = list1($1); - NODE_LINENO($$, $1); } | assocs comma assoc { @@ -4132,11 +4476,17 @@ assoc : arg tASSOC arg | string_fragment tLABEL_TAG arg { void_expr_error(p, $3); - if (typen($1->car) == NODE_DSTR) { + if ($1->cdr) { + /* Multiple fragments - create dynamic symbol */ + $$ = cons(new_dsym(p, $1), $3); + } + else if (node_to_int($1->car->car) < 0) { + /* Single fragment but it's an expression (-1 . node) - create dynamic symbol */ $$ = cons(new_dsym(p, $1), $3); } else { - $$ = cons(new_sym(p, new_strsym(p, $1)), $3); + /* Single string fragment - create simple symbol */ + $$ = cons(new_sym(p, new_strsym(p, $1->car)), $3); } } | tDSTAR arg @@ -4311,13 +4661,13 @@ backref_error(parser_state *p, node *n) { int c; - c = intn(n->car); + c = node_to_int(n->car); if (c == NODE_NTH_REF) { - yyerror_c(p, "can't set variable $", (char)intn(n->cdr)+'0'); + yyerror_c(p, "can't set variable $", (char)node_to_int(n->cdr)+'0'); } else if (c == NODE_BACK_REF) { - yyerror_c(p, "can't set variable $", (char)intn(n->cdr)); + yyerror_c(p, "can't set variable $", (char)node_to_int(n->cdr)); } else { yyerror(NULL, p, "Internal error in backref_error()"); @@ -4327,37 +4677,56 @@ backref_error(parser_state *p, node *n) static void void_expr_error(parser_state *p, node *n) { - int c; - if (n == NULL) return; - c = intn(n->car); - switch (c) { - case NODE_BREAK: - case NODE_RETURN: - case NODE_NEXT: - case NODE_REDO: - case NODE_RETRY: - yyerror(NULL, p, "void value expression"); - break; - case NODE_AND: - case NODE_OR: - if (n->cdr) { - void_expr_error(p, n->cdr->car); - void_expr_error(p, n->cdr->cdr); - } - break; - case NODE_STMTS: - case NODE_BEGIN: - if (n->cdr) { - while (n->cdr) { - n = n->cdr; + + /* Check if this is a variable-sized node first */ + struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; + if (header) { + /* Handle variable-sized nodes */ + switch ((enum node_type)header->node_type) { + case NODE_BREAK: + case NODE_RETURN: + case NODE_NEXT: + case NODE_REDO: + case NODE_RETRY: + yyerror(NULL, p, "void value expression"); + return; + case NODE_AND: + case NODE_OR: + { + struct mrb_ast_and_node *and_n = (struct mrb_ast_and_node*)n; + void_expr_error(p, (node*)and_n->left); + void_expr_error(p, (node*)and_n->right); } - void_expr_error(p, n->car); + return; + case NODE_STMTS: + { + struct mrb_ast_stmts_node *stmts = (struct mrb_ast_stmts_node*)n; + node *last = stmts->stmts; + if (last) { + /* Find the last statement in the cons list */ + while (last->cdr) { + last = last->cdr; + } + void_expr_error(p, last->car); + } + } + return; + case NODE_BEGIN: + { + struct mrb_ast_begin_node *begin_n = (struct mrb_ast_begin_node*)n; + if (begin_n->body) { + void_expr_error(p, (node*)begin_n->body); + } + } + return; + default: + /* Other variable-sized nodes are OK */ + return; } - break; - default: - break; } + + /* Should not reach here - all nodes should be variable-sized now */ } static void pushback(parser_state *p, int c); @@ -4391,7 +4760,7 @@ nextc(parser_state *p) if (p->pb) { node *tmp; - c = intn(p->pb->car); + c = node_to_int(p->pb->car); tmp = p->pb; p->pb = p->pb->cdr; cons_free(tmp); @@ -4427,7 +4796,7 @@ pushback(parser_state *p, int c) if (c >= 0) { p->column--; } - p->pb = cons(nint(c), p->pb); + p->pb = cons(int_to_node(c), p->pb); } static void @@ -4452,7 +4821,7 @@ peekc_n(parser_state *p, int n) c0 = nextc(p); if (c0 == -1) return c0; /* do not skip partial EOF */ if (c0 >= 0) --p->column; - list = push(list, nint(c0)); + list = push(list, int_to_node(c0)); } while(n--); if (p->pb) { p->pb = append(list, p->pb); @@ -4519,14 +4888,13 @@ skips(parser_state *p, const char *s) } return TRUE; } - else{ + else { s--; } } return FALSE; } - static int newtok(parser_state *p) { @@ -4868,8 +5236,8 @@ heredoc_remove_indent(parser_state *p, parser_heredoc_info *hinfo) while (indented) { n = indented->car; pair = n->car; - str = (char*)pair->car; - len = (size_t)pair->cdr; + len = (size_t)pair->car; + str = (char*)pair->cdr; escaped = n->cdr->car; nspaces = n->cdr->cdr; if (escaped) { @@ -4892,14 +5260,14 @@ heredoc_remove_indent(parser_state *p, parser_heredoc_info *hinfo) } if (newlen < len) newstr[newlen] = '\0'; - pair->car = (node*)newstr; - pair->cdr = (node*)newlen; + pair->car = (node*)newlen; + pair->cdr = (node*)newstr; } else { spaces = (size_t)nspaces->car; heredoc_count_indent(hinfo, str, len, spaces, &offset); - pair->car = (node*)(str + offset); - pair->cdr = (node*)(len - offset); + pair->car = (node*)(len - offset); + pair->cdr = (node*)(str + offset); } indented = indented->cdr; } @@ -4979,11 +5347,10 @@ parse_string(parser_state *p) } return 0; } - node *nd = new_str(p, tok(p), toklen(p)); - pylval.nd = nd; + pylval.nd = new_str_tok(p); if (unindent && head) { - nspaces = push(nspaces, nint(spaces)); - heredoc_push_indented(p, hinfo, nd->cdr, escaped, nspaces, empty && line_head); + nspaces = push(nspaces, int_to_node(spaces)); + heredoc_push_indented(p, hinfo, pylval.nd, escaped, nspaces, empty && line_head); } return tHD_STRING_MID; } @@ -5017,8 +5384,8 @@ parse_string(parser_state *p) p->lineno++; p->column = 0; if (unindent) { - nspaces = push(nspaces, nint(spaces)); - escaped = push(escaped, nint(pos)); + nspaces = push(nspaces, int_to_node(spaces)); + escaped = push(escaped, int_to_node(pos)); pos--; empty = TRUE; spaces = 0; @@ -5072,12 +5439,11 @@ parse_string(parser_state *p) tokfix(p); p->lstate = EXPR_BEG; p->cmd_start = TRUE; - node *nd = new_str(p, tok(p), toklen(p)); - pylval.nd = nd; + pylval.nd = new_str_tok(p); if (hinfo) { if (unindent && head) { - nspaces = push(nspaces, nint(spaces)); - heredoc_push_indented(p, hinfo, nd->cdr, escaped, nspaces, FALSE); + nspaces = push(nspaces, int_to_node(spaces)); + heredoc_push_indented(p, hinfo, pylval.nd, escaped, nspaces, FALSE); } hinfo->line_head = FALSE; return tHD_STRING_PART; @@ -5107,7 +5473,7 @@ parse_string(parser_state *p) else { pushback(p, c); tokfix(p); - pylval.nd = new_str(p, tok(p), toklen(p)); + pylval.nd = new_str_tok(p); return tSTRING_MID; } } @@ -5123,14 +5489,15 @@ parse_string(parser_state *p) end_strterm(p); if (type & STR_FUNC_XQUOTE) { - pylval.nd = new_xstr(p, tok(p), toklen(p)); + pylval.nd = new_str_tok(p); return tXSTRING; } if (type & STR_FUNC_REGEXP) { int f = 0; int re_opt; - char *s = strndup(tok(p), toklen(p)); + int pattern_len = toklen(p); + char *s = strndup(tok(p), pattern_len); char flags[3]; char *flag = flags; char enc = '\0'; @@ -5181,11 +5548,11 @@ parse_string(parser_state *p) else { encp = NULL; } - pylval.nd = new_regx(p, s, dup, encp); + pylval.nd = cons(cons(int_to_node(pattern_len), (node*)s), cons((node*)dup, (node*)encp)); return tREGEXP; } - pylval.nd = new_str(p, tok(p), toklen(p)); + pylval.nd = new_str_tok(p); return tSTRING; } @@ -5199,7 +5566,7 @@ number_literal_suffix(parser_state *p) int mask = NUM_SUFFIX_R|NUM_SUFFIX_I; while ((c = nextc(p)) != -1) { - list = push(list, nint(c)); + list = push(list, int_to_node(c)); if ((mask & NUM_SUFFIX_I) && c == 'i') { result |= (mask & NUM_SUFFIX_I); @@ -5286,8 +5653,7 @@ heredoc_identifier(parser_state *p) pushback(p, c); } tokfix(p); - newnode = new_heredoc(p); - info = (parser_heredoc_info*)newnode->cdr; + newnode = new_heredoc(p, &info); info->term = strndup(tok(p), toklen(p)); info->term_len = toklen(p); if (! quote) @@ -5679,7 +6045,7 @@ parser_yylex(parser_state *p) tokadd(p, c); } tokfix(p); - pylval.nd = new_str(p, tok(p), toklen(p)); + pylval.nd = new_str_tok(p); p->lstate = EXPR_END; return tCHAR; @@ -6515,7 +6881,7 @@ parser_yylex(parser_state *p) int nvar; if (n > 0) { - nvar = intn(p->nvars->car); + nvar = node_to_int(p->nvars->car); if (nvar != -2) { /* numbered parameters never appear on toplevel */ pylval.num = n; p->lstate = EXPR_END; @@ -6669,8 +7035,11 @@ parser_update_cxt(parser_state *p, mrb_ccontext *cxt) if (!cxt) return; if (!p->tree) return; - if (intn(p->tree->car) != NODE_SCOPE) return; - n0 = n = p->tree->cdr->car; + if (!node_type_p(p->tree, NODE_SCOPE)) return; + + /* Extract locals from variable-sized NODE_SCOPE */ + struct mrb_ast_scope_node *scope = scope_node(p->tree); + n0 = n = scope->locals; while (n) { i++; n = n->cdr; @@ -6678,11 +7047,11 @@ parser_update_cxt(parser_state *p, mrb_ccontext *cxt) cxt->syms = (mrb_sym*)mrbc_realloc(cxt->syms, i*sizeof(mrb_sym)); cxt->slen = i; for (i=0, n=n0; n; i++,n=n->cdr) { - cxt->syms[i] = sym(n->car); + cxt->syms[i] = node_to_sym(n->car); } } -void mrb_parser_dump(mrb_state *mrb, node *tree, int offset); +static void dump_node(mrb_state *mrb, node *tree, int offset); MRB_API void mrb_parser_parse(parser_state *p, mrb_ccontext *c) @@ -6708,7 +7077,7 @@ mrb_parser_parse(parser_state *p, mrb_ccontext *c) } parser_update_cxt(p, c); if (c && c->dump_result) { - mrb_parser_dump(p->mrb, p->tree, 0); + dump_node(p->mrb, p->tree, 0); } } MRB_CATCH(p->mrb->jmp) { @@ -7016,15 +7385,10 @@ mrb_load_detect_file_cxt(mrb_state *mrb, FILE *fp, mrb_ccontext *c) return mrb_load_exec(mrb, mrb_parse_file_continue(mrb, fp, leading.b, bufsize, c), c); } else { - mrb_int binsize; - uint8_t *bin; - mrb_value bin_obj = mrb_nil_value(); /* temporary string object */ - mrb_value result; - - binsize = bin_to_uint32(leading.h.binary_size); - bin_obj = mrb_str_new(mrb, NULL, binsize); - bin = (uint8_t*)RSTRING_PTR(bin_obj); - if ((size_t)binsize > bufsize) { + mrb_int binsize = bin_to_uint32(leading.h.binary_size); + mrb_value bin_obj = mrb_str_new(mrb, NULL, binsize); + uint8_t *bin = (uint8_t*)RSTRING_PTR(bin_obj); + if ((size_t)binsize > bufsize) { memcpy(bin, leading.b, bufsize); if (fread(bin + bufsize, binsize - bufsize, 1, fp) == 0) { binsize = bufsize; @@ -7032,7 +7396,7 @@ mrb_load_detect_file_cxt(mrb_state *mrb, FILE *fp, mrb_ccontext *c) } } - result = mrb_load_irep_buf_cxt(mrb, bin, binsize, c); + mrb_value result = mrb_load_irep_buf_cxt(mrb, bin, binsize, c); if (mrb_string_p(bin_obj)) mrb_str_resize(mrb, bin_obj, 0); return result; } @@ -7066,9 +7430,9 @@ mrb_load_string(mrb_state *mrb, const char *s) #ifndef MRB_NO_STDIO static void -dump_prefix(node *tree, int offset) +dump_prefix(int offset, uint16_t lineno) { - printf("%05d ", tree->lineno); + printf("%05d ", lineno); while (offset--) { putc(' ', stdout); putc(' ', stdout); @@ -7079,56 +7443,60 @@ static void dump_recur(mrb_state *mrb, node *tree, int offset) { while (tree) { - mrb_parser_dump(mrb, tree->car, offset); + dump_node(mrb, tree->car, offset); tree = tree->cdr; } } static void -dump_args(mrb_state *mrb, node *n, int offset) +dump_locals(mrb_state *mrb, node *tree, int offset, uint16_t lineno) { - if (n->car) { - dump_prefix(n, offset+1); - printf("mandatory args:\n"); - dump_recur(mrb, n->car, offset+2); - } - n = n->cdr; - if (n->car) { - dump_prefix(n, offset+1); - printf("optional args:\n"); - { - node *n2 = n->car; + if (!tree || (!tree->car && !tree->cdr)) return; - while (n2) { - dump_prefix(n2, offset+2); - printf("%s=\n", mrb_sym_name(mrb, sym(n2->car->car))); - mrb_parser_dump(mrb, n2->car->cdr, offset+3); - n2 = n2->cdr; + dump_prefix(offset, lineno); + printf("locals:\n"); + dump_prefix(offset+1, lineno); + while (tree) { + if (tree->car) { + mrb_sym sym = node_to_sym(tree->car); + if (sym != 0) { + const char *name = mrb_sym_name(mrb, sym); + if (name && strlen(name) > 0 && name[0] != '!' && name[0] != '@' && name[0] != '$') { + printf(" %s", mrb_sym_dump(mrb, sym)); + } + else { + printf(" (invalid symbol: %s)", name ? name : "(null)"); + } + } + else { + printf(" (anonymous)"); } } + tree = tree->cdr; } - n = n->cdr; - if (n->car) { - mrb_sym rest = sym(n->car); + printf("\n"); +} - dump_prefix(n, offset+1); - if (rest == MRB_OPSYM(mul)) - printf("rest=*\n"); - else - printf("rest=*%s\n", mrb_sym_name(mrb, rest)); +static void +dump_cpath(mrb_state *mrb, node *tree, int offset, uint16_t lineno) +{ + dump_prefix(offset, lineno); + printf("cpath: "); + if (!tree) { + printf("(null)\n"); } - n = n->cdr; - if (n->car) { - dump_prefix(n, offset+1); - printf("post mandatory args:\n"); - dump_recur(mrb, n->car, offset+2); + else if (node_to_int(tree->car) == 0) { + printf("(null)\n"); } - - n = n->cdr; - if (n) { - mrb_assert(intn(n->car) == NODE_ARGS_TAIL); - mrb_parser_dump(mrb, n, offset); + else if (node_to_int(tree->car) == 1) { + printf("Object\n"); } + else { + printf("\n"); + dump_node(mrb, tree->car, offset+1); + } + dump_prefix(offset, lineno); + printf("name: %s\n", mrb_sym_dump(mrb, node_to_sym(tree->cdr))); } /* @@ -7141,610 +7509,242 @@ static const char* str_dump(mrb_state *mrb, const char *str, int len) { int ai = mrb_gc_arena_save(mrb); - mrb_value s; -# if INT_MAX > MRB_INT_MAX / 4 - /* check maximum length with "\xNN" character */ - if (len > MRB_INT_MAX / 4) { - len = MRB_INT_MAX / 4; - } -# endif - s = mrb_str_new(mrb, str, (mrb_int)len); + mrb_value s = mrb_str_new(mrb, str, (mrb_int)len); s = mrb_str_dump(mrb, s); mrb_gc_arena_restore(mrb, ai); return RSTRING_PTR(s); } + +static void +dump_str(mrb_state *mrb, node *n, int offset, uint16_t lineno) +{ + while (n) { + dump_prefix(offset, lineno); + int len = node_to_int(n->car->car); + if (len >= 0) { + printf("str: %s\n", str_dump(mrb, (char*)n->car->cdr, len)); + } + else { + printf("interpolation:\n"); + dump_node(mrb, n->car->cdr, offset+1); + } + n = n->cdr; + } +} + +static void +dump_args(mrb_state *mrb, struct mrb_ast_args *args, int offset, uint16_t lineno) +{ + if (args->mandatory_args) { + dump_prefix(offset, lineno); + printf("mandatory args:\n"); + dump_recur(mrb, args->mandatory_args, offset+1); + } + if (args->optional_args) { + dump_prefix(offset, lineno); + printf("optional args:\n"); + { + node *n = args->optional_args; + while (n) { + dump_prefix(offset+1, lineno); + printf("%s=\n", mrb_sym_name(mrb, node_to_sym(n->car->car))); + dump_node(mrb, n->car->cdr, offset+2); + n = n->cdr; + } + } + } + if (args->rest_arg) { + mrb_sym rest = args->rest_arg; + + dump_prefix(offset, lineno); + if (rest == MRB_OPSYM(mul)) + printf("rest=*\n"); + else + printf("rest=*%s\n", mrb_sym_name(mrb, rest)); + } + if (args->post_mandatory_args) { + dump_prefix(offset, lineno); + printf("post mandatory args:\n"); + dump_recur(mrb, args->post_mandatory_args, offset+1); + } + if (args->keyword_args) { + dump_prefix(offset, lineno); + printf("keyword args:\n"); + { + node *n = args->keyword_args; + while (n) { + dump_prefix(offset+1, lineno); + printf("%s:\n", mrb_sym_name(mrb, node_to_sym(n->car->car))); + dump_node(mrb, n->car->cdr, offset+2); + n = n->cdr; + } + } + } + if (args->kwrest_arg) { + mrb_sym rest = args->kwrest_arg; + + dump_prefix(offset, lineno); + if (rest == MRB_OPSYM(pow)) + printf("kwrest=**\n"); + else + printf("kwrest=**%s\n", mrb_sym_name(mrb, rest)); + } + if (args->block_arg) { + mrb_sym blk = args->block_arg; + + dump_prefix(offset, lineno); + if (blk == MRB_OPSYM(and)) + printf("blk=&\n"); + else + printf("blk=&%s\n", mrb_sym_name(mrb, blk)); + } +} + +static void +dump_callargs(mrb_state *mrb, node *n, int offset, uint16_t lineno) +{ + if (!n) return; + + struct mrb_ast_callargs *args = (struct mrb_ast_callargs*)n; + if (args->regular_args) { + dump_prefix(offset+1, lineno); + printf("args:\n"); + dump_recur(mrb, args->regular_args, offset+2); + } + if (args->keyword_args) { + dump_prefix(offset+1, lineno); + printf("kw_args:\n"); + node *kw = args->keyword_args; + while (kw) { + dump_prefix(offset+2, lineno); + printf("key:\n"); + if (node_to_sym(kw->car->car) == MRB_OPSYM(pow)) { + dump_prefix(offset+3, lineno); + printf("**:\n"); + } + else { + dump_node(mrb, kw->car->car, offset+3); + } + dump_prefix(offset+2, lineno); + printf("value:\n"); + dump_node(mrb, kw->car->cdr, offset+3); + kw = kw->cdr; + } + } + if (args->block_arg) { + dump_prefix(offset+1, lineno); + printf("block:\n"); + dump_node(mrb, args->block_arg, offset+2); + } +} + #endif void -mrb_parser_dump(mrb_state *mrb, node *tree, int offset) +dump_node(mrb_state *mrb, node *tree, int offset) { #ifndef MRB_NO_STDIO - int nodetype; + enum node_type nodetype; + uint16_t lineno = 0; if (!tree) return; - again: - dump_prefix(tree, offset); - nodetype = intn(tree->car); - tree = tree->cdr; + + /* Extract line number from variable-sized node header */ + if (get_node_type(tree) != NODE_LAST) { + lineno = ((struct mrb_ast_var_header*)tree)->lineno; + } + + dump_prefix(offset, lineno); + + /* All nodes are now variable-sized nodes with headers */ + nodetype = get_node_type(tree); + switch (nodetype) { - case NODE_STMTS: - printf("NODE_STMTS:\n"); - dump_recur(mrb, tree, offset+1); - break; - - case NODE_BEGIN: - printf("NODE_BEGIN:\n"); - mrb_parser_dump(mrb, tree, offset+1); - break; - - case NODE_RESCUE: - printf("NODE_RESCUE:\n"); - if (tree->car) { - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - } - tree = tree->cdr; - if (tree->car) { - node *n2 = tree->car; - - dump_prefix(n2, offset+1); - printf("rescue:\n"); - while (n2) { - node *n3 = n2->car; - if (n3->car) { - dump_prefix(n2, offset+2); - printf("handle classes:\n"); - dump_recur(mrb, n3->car, offset+3); - } - if (n3->cdr->car) { - dump_prefix(n3, offset+2); - printf("exc_var:\n"); - mrb_parser_dump(mrb, n3->cdr->car, offset+3); - } - if (n3->cdr->cdr->car) { - dump_prefix(n3, offset+2); - printf("rescue body:\n"); - mrb_parser_dump(mrb, n3->cdr->cdr->car, offset+3); - } - n2 = n2->cdr; - } - } - tree = tree->cdr; - if (tree->car) { - dump_prefix(tree, offset+1); - printf("else:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - } - break; - - case NODE_ENSURE: - printf("NODE_ENSURE:\n"); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("ensure:\n"); - mrb_parser_dump(mrb, tree->cdr->cdr, offset+2); - break; - - case NODE_LAMBDA: - printf("NODE_LAMBDA:\n"); - dump_prefix(tree, offset+1); - goto block; - - case NODE_BLOCK: - block: - printf("NODE_BLOCK:\n"); - tree = tree->cdr; - if (tree->car) { - dump_args(mrb, tree->car, offset+1); - } - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr->car, offset+2); - break; - - case NODE_IF: - printf("NODE_IF:\n"); - dump_prefix(tree, offset+1); - printf("cond:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("then:\n"); - mrb_parser_dump(mrb, tree->cdr->car, offset+2); - if (tree->cdr->cdr->car) { - dump_prefix(tree, offset+1); - printf("else:\n"); - mrb_parser_dump(mrb, tree->cdr->cdr->car, offset+2); - } - break; - - case NODE_AND: - printf("NODE_AND:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - mrb_parser_dump(mrb, tree->cdr, offset+1); - break; - - case NODE_OR: - printf("NODE_OR:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - mrb_parser_dump(mrb, tree->cdr, offset+1); - break; - - case NODE_CASE: - printf("NODE_CASE:\n"); - if (tree->car) { - mrb_parser_dump(mrb, tree->car, offset+1); - } - tree = tree->cdr; - while (tree) { - dump_prefix(tree, offset+1); - printf("case:\n"); - dump_recur(mrb, tree->car->car, offset+2); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->car->cdr, offset+2); - tree = tree->cdr; - } - break; - - case NODE_WHILE: - printf("NODE_WHILE:\n"); - dump_prefix(tree, offset+1); - printf("cond:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_UNTIL: - printf("NODE_UNTIL:\n"); - dump_prefix(tree, offset+1); - printf("cond:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_WHILE_MOD: - printf("NODE_WHILE_MOD:\n"); - dump_prefix(tree, offset+1); - printf("cond:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_UNTIL_MOD: - printf("NODE_UNTIL_MOD:\n"); - dump_prefix(tree, offset+1); - printf("cond:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_FOR: - printf("NODE_FOR:\n"); - dump_prefix(tree, offset+1); - printf("var:\n"); - { - node *n2 = tree->car; - - if (n2->car) { - dump_prefix(n2, offset+2); - printf("pre:\n"); - dump_recur(mrb, n2->car, offset+3); - } - n2 = n2->cdr; - if (n2) { - if (n2->car) { - dump_prefix(n2, offset+2); - printf("rest:\n"); - mrb_parser_dump(mrb, n2->car, offset+3); - } - n2 = n2->cdr; - if (n2) { - if (n2->car) { - dump_prefix(n2, offset+2); - printf("post:\n"); - dump_recur(mrb, n2->car, offset+3); - } - } - } - } - tree = tree->cdr; - dump_prefix(tree, offset+1); - printf("in:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - tree = tree->cdr; - dump_prefix(tree, offset+1); - printf("do:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - break; - + /* Variable-sized node cases */ case NODE_SCOPE: printf("NODE_SCOPE:\n"); - { - node *n2 = tree->car; - mrb_bool first_lval = TRUE; - - if (n2 && (n2->car || n2->cdr)) { - dump_prefix(n2, offset+1); - printf("local variables:\n"); - dump_prefix(n2, offset+2); - while (n2) { - if (n2->car) { - if (!first_lval) printf(", "); - printf("%s", mrb_sym_name(mrb, sym(n2->car))); - first_lval = FALSE; - } - n2 = n2->cdr; - } - printf("\n"); - } + if (scope_node(tree)->locals) { + dump_locals(mrb, scope_node(tree)->locals, offset+1, lineno); } - tree = tree->cdr; - offset++; - goto again; - - case NODE_FCALL: - case NODE_CALL: - case NODE_SCALL: - switch (nodetype) { - case NODE_FCALL: - printf("NODE_FCALL:\n"); break; - case NODE_CALL: - printf("NODE_CALL(.):\n"); break; - case NODE_SCALL: - printf("NODE_SCALL(&.):\n"); break; - default: - break; + if (scope_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, scope_node(tree)->body, offset+2); } - mrb_parser_dump(mrb, tree->car, offset+1); - dump_prefix(tree, offset+1); - printf("method='%s' (%d)\n", - mrb_sym_dump(mrb, sym(tree->cdr->car)), - intn(tree->cdr->car)); - tree = tree->cdr->cdr->car; - if (tree) { - dump_prefix(tree, offset+1); - printf("args:\n"); - dump_recur(mrb, tree->car, offset+2); - if (tree->cdr) { - if (tree->cdr->car) { - dump_prefix(tree, offset+1); - printf("kwargs:\n"); - mrb_parser_dump(mrb, tree->cdr->car, offset+2); - } - if (tree->cdr->cdr) { - dump_prefix(tree, offset+1); - printf("block:\n"); - mrb_parser_dump(mrb, tree->cdr->cdr, offset+2); - } - } - } - break; - - case NODE_DOT2: - printf("NODE_DOT2:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - mrb_parser_dump(mrb, tree->cdr, offset+1); - break; - - case NODE_DOT3: - printf("NODE_DOT3:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - mrb_parser_dump(mrb, tree->cdr, offset+1); - break; - - case NODE_COLON2: - printf("NODE_COLON2:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - dump_prefix(tree, offset+1); - printf("::%s\n", mrb_sym_name(mrb, sym(tree->cdr))); - break; - - case NODE_COLON3: - printf("NODE_COLON3: ::%s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_ARRAY: - printf("NODE_ARRAY:\n"); - dump_recur(mrb, tree, offset+1); - break; - - case NODE_HASH: - printf("NODE_HASH:\n"); - while (tree) { - dump_prefix(tree, offset+1); - printf("key:\n"); - mrb_parser_dump(mrb, tree->car->car, offset+2); - dump_prefix(tree, offset+1); - printf("value:\n"); - mrb_parser_dump(mrb, tree->car->cdr, offset+2); - tree = tree->cdr; - } - break; - - case NODE_KW_HASH: - printf("NODE_KW_HASH:\n"); - while (tree) { - dump_prefix(tree, offset+1); - printf("key:\n"); - mrb_parser_dump(mrb, tree->car->car, offset+2); - dump_prefix(tree, offset+1); - printf("value:\n"); - mrb_parser_dump(mrb, tree->car->cdr, offset+2); - tree = tree->cdr; - } - break; - - case NODE_SPLAT: - printf("NODE_SPLAT:\n"); - mrb_parser_dump(mrb, tree, offset+1); - break; - - case NODE_ASGN: - printf("NODE_ASGN:\n"); - dump_prefix(tree, offset+1); - printf("lhs:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("rhs:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_MASGN: - printf("NODE_MASGN:\n"); - dump_prefix(tree, offset+1); - printf("mlhs:\n"); - { - node *n2 = tree->car; - - if (n2->car) { - dump_prefix(tree, offset+2); - printf("pre:\n"); - dump_recur(mrb, n2->car, offset+3); - } - n2 = n2->cdr; - if (n2) { - if (n2->car) { - dump_prefix(n2, offset+2); - printf("rest:\n"); - if (n2->car == nint(-1)) { - dump_prefix(n2, offset+2); - printf("(empty)\n"); - } - else { - mrb_parser_dump(mrb, n2->car, offset+3); - } - } - n2 = n2->cdr; - if (n2 && n2->car) { - dump_prefix(n2, offset+2); - printf("post:\n"); - dump_recur(mrb, n2->car, offset+3); - } - } - } - dump_prefix(tree, offset+1); - printf("rhs:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_OP_ASGN: - printf("NODE_OP_ASGN:\n"); - dump_prefix(tree, offset+1); - printf("lhs:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - tree = tree->cdr; - dump_prefix(tree, offset+1); - printf("op='%s' (%d)\n", mrb_sym_name(mrb, sym(tree->car)), intn(tree->car)); - tree = tree->cdr; - mrb_parser_dump(mrb, tree->car, offset+1); - break; - - case NODE_SUPER: - printf("NODE_SUPER:\n"); - if (tree) { - dump_prefix(tree, offset+1); - printf("args:\n"); - dump_recur(mrb, tree->car, offset+2); - if (tree->cdr) { - dump_prefix(tree, offset+1); - printf("block:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - } - } - break; - - case NODE_ZSUPER: - printf("NODE_ZSUPER:\n"); - if (tree) { - dump_prefix(tree, offset+1); - printf("args:\n"); - dump_recur(mrb, tree->car, offset+2); - if (tree->cdr) { - dump_prefix(tree, offset+1); - printf("block:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - } - } - break; - - case NODE_RETURN: - printf("NODE_RETURN:\n"); - mrb_parser_dump(mrb, tree, offset+1); - break; - - case NODE_YIELD: - printf("NODE_YIELD:\n"); - dump_recur(mrb, tree, offset+1); - break; - - case NODE_BREAK: - printf("NODE_BREAK:\n"); - mrb_parser_dump(mrb, tree, offset+1); - break; - - case NODE_NEXT: - printf("NODE_NEXT:\n"); - mrb_parser_dump(mrb, tree, offset+1); - break; - - case NODE_REDO: - printf("NODE_REDO\n"); - break; - - case NODE_RETRY: - printf("NODE_RETRY\n"); - break; - - case NODE_LVAR: - printf("NODE_LVAR %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_GVAR: - printf("NODE_GVAR %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_IVAR: - printf("NODE_IVAR %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_CVAR: - printf("NODE_CVAR %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_NVAR: - printf("NODE_NVAR %d\n", intn(tree)); - break; - - case NODE_CONST: - printf("NODE_CONST %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_MATCH: - printf("NODE_MATCH:\n"); - dump_prefix(tree, offset + 1); - printf("lhs:\n"); - mrb_parser_dump(mrb, tree->car, offset + 2); - dump_prefix(tree, offset + 1); - printf("rhs:\n"); - mrb_parser_dump(mrb, tree->cdr, offset + 2); - break; - - case NODE_BACK_REF: - printf("NODE_BACK_REF: $%c\n", intn(tree)); - break; - - case NODE_NTH_REF: - printf("NODE_NTH_REF: $%d\n", intn(tree)); - break; - - case NODE_ARG: - printf("NODE_ARG %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_BLOCK_ARG: - printf("NODE_BLOCK_ARG:\n"); - mrb_parser_dump(mrb, tree, offset+1); break; case NODE_INT: - printf("NODE_INT %s base %d\n", (char*)tree->car, intn(tree->cdr->car)); + printf("NODE_INT: %d\n", int_node(tree)->value); + break; + + case NODE_BIGINT: + printf("NODE_BIGINT: %s (base %d)\n", bigint_node(tree)->string, bigint_node(tree)->base); break; case NODE_FLOAT: - printf("NODE_FLOAT %s\n", (char*)tree); - break; - - case NODE_NEGATE: - printf("NODE_NEGATE:\n"); - mrb_parser_dump(mrb, tree, offset+1); + printf("NODE_FLOAT: %s\n", float_node(tree)->value); break; case NODE_STR: - printf("NODE_STR %s len %d\n", str_dump(mrb, (char*)tree->car, intn(tree->cdr)), intn(tree->cdr)); - break; - - case NODE_DSTR: - printf("NODE_DSTR:\n"); - dump_recur(mrb, tree, offset+1); + printf("NODE_STR:\n"); + dump_str(mrb, str_node(tree)->list, offset+1, lineno); break; case NODE_XSTR: - printf("NODE_XSTR %s len %d\n", str_dump(mrb, (char*)tree->car, intn(tree->cdr)), intn(tree->cdr)); - break; - - case NODE_DXSTR: - printf("NODE_DXSTR:\n"); - dump_recur(mrb, tree, offset+1); - break; - - case NODE_REGX: - printf("NODE_REGX /%s/\n", (char*)tree->car); - if (tree->cdr->car) { - dump_prefix(tree, offset+1); - printf("opt: %s\n", (char*)tree->cdr->car); - } - if (tree->cdr->cdr) { - dump_prefix(tree, offset+1); - printf("enc: %s\n", (char*)tree->cdr->cdr); - } - break; - - case NODE_DREGX: - printf("NODE_DREGX:\n"); - dump_recur(mrb, tree->car, offset+1); - dump_prefix(tree, offset+1); - printf("tail: %s\n", (char*)tree->cdr->cdr->car); - if (tree->cdr->cdr->cdr->car) { - dump_prefix(tree, offset+1); - printf("opt: %s\n", (char*)tree->cdr->cdr->cdr->car); - } - if (tree->cdr->cdr->cdr->cdr) { - dump_prefix(tree, offset+1); - printf("enc: %s\n", (char*)tree->cdr->cdr->cdr->cdr); - } + printf("NODE_XSTR:\n"); + dump_str(mrb, xstr_node(tree)->list, offset+1, lineno); break; case NODE_SYM: - printf("NODE_SYM :%s (%d)\n", mrb_sym_dump(mrb, sym(tree)), - intn(tree)); + printf("NODE_SYM: %s\n", mrb_sym_dump(mrb, sym_node(tree)->symbol)); break; case NODE_DSYM: printf("NODE_DSYM:\n"); - mrb_parser_dump(mrb, tree, offset+1); + dump_str(mrb, str_node(tree)->list, offset+1, lineno); break; - case NODE_WORDS: - printf("NODE_WORDS:\n"); - dump_recur(mrb, tree, offset+1); + case NODE_LVAR: + printf("NODE_LVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; - case NODE_SYMBOLS: - printf("NODE_SYMBOLS:\n"); - dump_recur(mrb, tree, offset+1); + case NODE_GVAR: + printf("NODE_GVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; - case NODE_LITERAL_DELIM: - printf("NODE_LITERAL_DELIM\n"); + case NODE_IVAR: + printf("NODE_IVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; - case NODE_SELF: - printf("NODE_SELF\n"); + case NODE_CVAR: + printf("NODE_CVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; - case NODE_NIL: - printf("NODE_NIL\n"); + case NODE_NVAR: + printf("NODE_NVAR: %d\n", nvar_node(tree)->num); + break; + + case NODE_CONST: + printf("NODE_CONST: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); + break; + + case NODE_CALL: + printf("NODE_CALL: %s\n", mrb_sym_dump(mrb, call_node(tree)->method_name)); + if (call_node(tree)->receiver) { + dump_prefix(offset+1, lineno); + printf("receiver:\n"); + dump_node(mrb, call_node(tree)->receiver, offset+2); + } + if (call_node(tree)->args) { + dump_callargs(mrb, call_node(tree)->args, offset, lineno); + } + break; + + case NODE_ARRAY: + printf("NODE_ARRAY:\n"); + if (array_node(tree)->elements) { + dump_recur(mrb, array_node(tree)->elements, offset+1); + } break; case NODE_TRUE: @@ -7755,10 +7755,501 @@ mrb_parser_dump(mrb_state *mrb, node *tree, int offset) printf("NODE_FALSE\n"); break; + case NODE_NIL: + printf("NODE_NIL\n"); + break; + + case NODE_SELF: + printf("NODE_SELF\n"); + break; + + case NODE_IF: + printf("NODE_IF:\n"); + if (if_node(tree)->condition) { + dump_prefix(offset+1, lineno); + printf("cond:\n"); + dump_node(mrb, if_node(tree)->condition, offset+2); + } + if (if_node(tree)->then_body) { + dump_prefix(offset+1, lineno); + printf("then:\n"); + dump_node(mrb, if_node(tree)->then_body, offset+2); + } + if (if_node(tree)->else_body) { + dump_prefix(offset+1, lineno); + printf("else:\n"); + dump_node(mrb, if_node(tree)->else_body, offset+2); + } + break; + + case NODE_DEF: + printf("NODE_DEF: %s\n", mrb_sym_dump(mrb, def_node(tree)->name)); + if (def_node(tree)->args) { + dump_args(mrb, sdef_node(tree)->args, offset+1, lineno); + } + if (def_node(tree)->locals) { + dump_locals(mrb, def_node(tree)->locals, offset+1, lineno); + } + if (def_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, def_node(tree)->body, offset+2); + } + break; + + case NODE_ASGN: + printf("NODE_ASGN:\n"); + if (asgn_node(tree)->lhs) { + dump_prefix(offset+1, lineno); + printf("lhs:\n"); + dump_node(mrb, asgn_node(tree)->lhs, offset+2); + } + if (asgn_node(tree)->rhs) { + dump_prefix(offset+1, lineno); + printf("rhs:\n"); + dump_node(mrb, asgn_node(tree)->rhs, offset+2); + } + break; + + case NODE_MASGN: + case NODE_MARG: + printf("%s:\n", get_node_type(tree) == NODE_MASGN ? "NODE_MASGN" : "NODE_MARG"); + /* Handle pre-splat variables */ + if (masgn_node(tree)->pre) { + dump_prefix(offset+1, lineno); + printf("pre:\n"); + dump_recur(mrb, masgn_node(tree)->pre, offset+2); + } + /* Handle splat variable (can be -1 sentinel for anonymous splat) */ + if (masgn_node(tree)->rest) { + if ((intptr_t)masgn_node(tree)->rest == -1) { + dump_prefix(offset+1, lineno); + printf("rest: *\n"); + } + else { + dump_prefix(offset+1, lineno); + printf("rest:\n"); + dump_node(mrb, masgn_node(tree)->rest, offset+2); + } + } + /* Handle post-splat variables */ + if (masgn_node(tree)->post) { + dump_prefix(offset+1, lineno); + printf("post:\n"); + dump_recur(mrb, masgn_node(tree)->post, offset+2); + } + if (masgn_node(tree)->rhs) { + dump_prefix(offset+1, lineno); + printf("rhs:\n"); + dump_node(mrb, masgn_node(tree)->rhs, offset+2); + } + break; + + case NODE_RETURN: + printf("NODE_RETURN:\n"); + if (return_node(tree)->args) { + dump_node(mrb, return_node(tree)->args, offset); + } + break; + + case NODE_BREAK: + printf("NODE_BREAK:\n"); + if (break_node(tree)->value) { + dump_prefix(offset+1, lineno); + printf("value:\n"); + dump_node(mrb, break_node(tree)->value, offset+2); + } + break; + + case NODE_NEXT: + printf("NODE_NEXT:\n"); + if (next_node(tree)->value) { + dump_prefix(offset+1, lineno); + printf("value:\n"); + dump_node(mrb, next_node(tree)->value, offset+2); + } + break; + + case NODE_NEGATE: + printf("NODE_NEGATE:\n"); + if (negate_node(tree)->operand) { + dump_prefix(offset+1, lineno); + printf("operand:\n"); + dump_node(mrb, negate_node(tree)->operand, offset+2); + } + break; + + case NODE_STMTS: + printf("NODE_STMTS:\n"); + if (stmts_node(tree)->stmts) { + dump_recur(mrb, stmts_node(tree)->stmts, offset+1); + } + break; + + case NODE_BEGIN: + printf("NODE_BEGIN:\n"); + if (begin_node(tree)->body) { + dump_node(mrb, begin_node(tree)->body, offset+1); + } + break; + + case NODE_RESCUE: + printf("NODE_RESCUE:\n"); + if (rescue_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, rescue_node(tree)->body, offset+2); + } + if (rescue_node(tree)->rescue_clauses) { + node *n2 = rescue_node(tree)->rescue_clauses; + dump_prefix(offset+1, lineno); + printf("rescue:\n"); + while (n2) { + node *n3 = n2->car; + if (n3->car) { + dump_prefix(offset+2, lineno); + printf("handle classes:\n"); + dump_recur(mrb, n3->car, offset+3); + } + if (n3->cdr->car) { + dump_prefix(offset+2, lineno); + printf("exc_var:\n"); + dump_node(mrb, n3->cdr->car, offset+3); + } + if (n3->cdr->cdr->car) { + dump_prefix(offset+2, lineno); + printf("rescue body:\n"); + dump_node(mrb, n3->cdr->cdr->car, offset+3); + } + n2 = n2->cdr; + } + } + if (rescue_node(tree)->else_clause) { + dump_prefix(offset+1, lineno); + printf("else:\n"); + dump_node(mrb, rescue_node(tree)->else_clause, offset+2); + } + break; + + case NODE_ENSURE: + printf("NODE_ENSURE:\n"); + if (ensure_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, ensure_node(tree)->body, offset+2); + } + if (ensure_node(tree)->ensure_clause) { + dump_prefix(offset+1, lineno); + printf("ensure:\n"); + dump_node(mrb, ensure_node(tree)->ensure_clause, offset+2); + } + break; + + case NODE_LAMBDA: + printf("NODE_LAMBDA:\n"); + goto block; + + case NODE_BLOCK: + printf("NODE_BLOCK:\n"); + block: + if (block_node(tree)->locals) { + dump_locals(mrb, block_node(tree)->locals, offset+1, lineno); + } + if (block_node(tree)->args) { + dump_args(mrb, block_node(tree)->args, offset+1, lineno); + } + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, block_node(tree)->body, offset+2); + break; + + case NODE_AND: + printf("NODE_AND:\n"); + dump_node(mrb, and_node(tree)->left, offset+1); + dump_node(mrb, and_node(tree)->right, offset+1); + break; + + case NODE_OR: + printf("NODE_OR:\n"); + dump_node(mrb, or_node(tree)->left, offset+1); + dump_node(mrb, or_node(tree)->right, offset+1); + break; + + case NODE_CASE: + printf("NODE_CASE:\n"); + if (case_node(tree)->value) { + dump_prefix(offset+1, lineno); + printf("value:\n"); + dump_node(mrb, case_node(tree)->value, offset+2); + } + if (case_node(tree)->body) { + node *when_node = case_node(tree)->body; + while (when_node) { + dump_prefix(offset+1, lineno); + printf("when:\n"); + node *when_clause = when_node->car; + if (when_clause && when_clause->car) { + dump_prefix(offset+2, lineno); + printf("cond:\n"); + dump_recur(mrb, when_clause->car, offset+3); + } + if (when_clause && when_clause->cdr) { + dump_prefix(offset+2, lineno); + printf("body:\n"); + dump_node(mrb, when_clause->cdr, offset+3); + } + when_node = when_node->cdr; + } + } + break; + + case NODE_WHILE: + printf("NODE_WHILE:\n"); + goto dump_loop_node; + case NODE_UNTIL: + printf("NODE_UNTIL:\n"); + goto dump_loop_node; + case NODE_WHILE_MOD: + printf("NODE_WHILE_MOD:\n"); + goto dump_loop_node; + case NODE_UNTIL_MOD: + printf("NODE_UNTIL_MOD:\n"); + + dump_loop_node: + dump_prefix(offset+1, lineno); + printf("cond:\n"); + dump_node(mrb, while_node(tree)->condition, offset+2); + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, while_node(tree)->body, offset+2); + break; + + case NODE_FOR: + printf("NODE_FOR:\n"); + if (for_node(tree)->var) { + dump_prefix(offset+1, lineno); + printf("var:\n"); + /* FOR_NODE_VAR structure: + * var_list->car: cons-list of pre-splat variables + * var_list->cdr->car: splat varnode (not a cons-list) + * var_list->cdr->cdr->car: cons-list of post-splat variables */ + node *var_list = for_node(tree)->var; + if (var_list) { + dump_recur(mrb, var_list->car, offset+2); + if (var_list && var_list->cdr) { + /* Second element is a varnode, not a cons-list */ + dump_prefix(offset+1, lineno); + printf("splat var:\n"); + dump_node(mrb, var_list->cdr->car, offset+2); + if (var_list->cdr->cdr) { + /* Third element is a cons-list of post-splat variables */ + dump_prefix(offset+1, lineno); + printf("post var:\n"); + dump_recur(mrb, var_list->cdr->cdr->car, offset+2); + } + } + } + } + if (for_node(tree)->iterable) { + dump_prefix(offset+1, lineno); + printf("iterable:\n"); + dump_node(mrb, for_node(tree)->iterable, offset+2); + } + if (for_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, for_node(tree)->body, offset+2); + } + break; + + case NODE_DOT2: + printf("NODE_DOT2:\n"); + { + if (dot2_node(tree)->left) { + dump_prefix(offset+1, lineno); + printf("left:\n"); + dump_node(mrb, dot2_node(tree)->left, offset+2); + } + if (dot2_node(tree)->right) { + dump_prefix(offset+1, lineno); + printf("right:\n"); + dump_node(mrb, dot2_node(tree)->right, offset+2); + } + } + break; + + case NODE_DOT3: + printf("NODE_DOT3:\n"); + { + if (dot3_node(tree)->left) { + dump_prefix(offset+1, lineno); + printf("left:\n"); + dump_node(mrb, dot3_node(tree)->left, offset+2); + } + if (dot3_node(tree)->right) { + dump_prefix(offset+1, lineno); + printf("right:\n"); + dump_node(mrb, dot3_node(tree)->right, offset+2); + } + } + break; + + case NODE_COLON2: + printf("NODE_COLON2:\n"); + if (colon2_node(tree)->base) { + dump_prefix(offset+1, lineno); + printf("base:\n"); + dump_node(mrb, colon2_node(tree)->base, offset+2); + } + dump_prefix(offset+1, lineno); + printf("name: %s\n", mrb_sym_name(mrb, colon2_node(tree)->name)); + break; + + case NODE_COLON3: + printf("NODE_COLON3: ::%s\n", mrb_sym_name(mrb, colon3_node(tree)->name)); + break; + + case NODE_HASH: + printf("NODE_HASH:\n"); + { + node *pairs = hash_node(tree)->pairs; + while (pairs) { + dump_prefix(offset+1, lineno); + printf("key:\n"); + if (node_to_sym(pairs->car->car) == MRB_OPSYM(pow)) { + dump_prefix(offset+2, lineno); + printf("**\n"); + } + else { + dump_node(mrb, pairs->car->car, offset+2); + } + dump_prefix(offset+1, lineno); + printf("value:\n"); + dump_node(mrb, pairs->car->cdr, offset+2); + pairs = pairs->cdr; + } + } + break; + + case NODE_SPLAT: + printf("NODE_SPLAT:\n"); + dump_node(mrb, splat_node(tree)->value, offset+1); + break; + + case NODE_OP_ASGN: + printf("NODE_OP_ASGN:\n"); + dump_prefix(offset+1, lineno); + printf("lhs:\n"); + dump_node(mrb, op_asgn_node(tree)->lhs, offset+2); + dump_prefix(offset+1, lineno); + printf("op='%s' (%d)\n", mrb_sym_name(mrb, op_asgn_node(tree)->op), (int)op_asgn_node(tree)->op); + dump_node(mrb, op_asgn_node(tree)->rhs, offset+1); + break; + + case NODE_SUPER: + printf("NODE_SUPER:\n"); + if (super_node(tree)->args) { + dump_callargs(mrb, super_node(tree)->args, offset, lineno); + } + break; + + case NODE_ZSUPER: + printf("NODE_ZSUPER:\n"); + if (super_node(tree)->args) { + dump_callargs(mrb, super_node(tree)->args, offset, lineno); + } + break; + + case NODE_YIELD: + printf("NODE_YIELD:\n"); + if (yield_node(tree)->args) { + dump_callargs(mrb, yield_node(tree)->args, offset, lineno); + } + break; + + case NODE_REDO: + printf("NODE_REDO\n"); + break; + + case NODE_RETRY: + printf("NODE_RETRY\n"); + break; + + case NODE_BACK_REF: + printf("NODE_BACK_REF: $%c\n", node_to_int(tree)); + break; + + case NODE_NTH_REF: + printf("NODE_NTH_REF: $%d\n", node_to_int(tree)); + break; + + case NODE_BLOCK_ARG: + printf("NODE_BLOCK_ARG:\n"); + dump_node(mrb, block_arg_node(tree)->value, offset+1); + break; + + case NODE_REGX: + printf("NODE_REGX:\n"); + if (regx_node(tree)->list) { + dump_str(mrb, regx_node(tree)->list, offset+1, lineno); + } + if (regx_node(tree)->flags) { + dump_prefix(offset+1, lineno); + printf("flags: %s\n", regx_node(tree)->flags); + } + if (regx_node(tree)->encoding) { + dump_prefix(offset+1, lineno); + printf("encoding: %s\n", regx_node(tree)->encoding); + } + break; + + case NODE_WORDS: + printf("NODE_WORDS:\n"); + if (words_node(tree)->args) { + node *list = words_node(tree)->args; + while (list && list->car) { + node *item = list->car; + if (item->car == 0 && item->cdr == 0) { + /* Skip separator (0 . 0) */ + } + else if (item->car && item->cdr) { + /* String item: (len . str) */ + dump_prefix(offset+1, lineno); + int len = node_to_int(item->car); + if (len >= 0 && len < 1000 && item->cdr) { + printf("word: \"%.*s\"\n", len, (char*)item->cdr); + } + } + list = list->cdr; + } + } + break; + + case NODE_SYMBOLS: + printf("NODE_SYMBOLS:\n"); + if (symbols_node(tree)->args) { + node *list = symbols_node(tree)->args; + while (list && list->car) { + node *item = list->car; + if (item->car == 0 && item->cdr == 0) { + /* Skip separator (0 . 0) */ + } else if (item->car && item->cdr) { + /* String item: (len . str) */ + dump_prefix(offset+1, lineno); + int len = node_to_int(item->car); + if (len >= 0 && len < 1000 && item->cdr) { + printf("symbol: \"%.*s\"\n", len, (char*)item->cdr); + } + } + list = list->cdr; + } + } + break; + case NODE_ALIAS: printf("NODE_ALIAS %s %s:\n", - mrb_sym_dump(mrb, sym(tree->car)), - mrb_sym_dump(mrb, sym(tree->cdr))); + mrb_sym_dump(mrb, node_to_sym(tree->car)), + mrb_sym_dump(mrb, node_to_sym(tree->cdr))); break; case NODE_UNDEF: @@ -7766,7 +8257,7 @@ mrb_parser_dump(mrb_state *mrb, node *tree, int offset) { node *t = tree; while (t) { - printf(" %s", mrb_sym_dump(mrb, sym(t->car))); + printf(" %s", mrb_sym_dump(mrb, node_to_sym(t->car))); t = t->cdr; } } @@ -7775,153 +8266,107 @@ mrb_parser_dump(mrb_state *mrb, node *tree, int offset) case NODE_CLASS: printf("NODE_CLASS:\n"); - if (tree->car->car == nint(0)) { - dump_prefix(tree, offset+1); - printf(":%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); + if (class_node(tree)->name) { + dump_cpath(mrb, module_node(tree)->name, offset+1, lineno); } - else if (tree->car->car == nint(1)) { - dump_prefix(tree, offset+1); - printf("::%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); - } - else { - mrb_parser_dump(mrb, tree->car->car, offset+1); - dump_prefix(tree, offset+1); - printf("::%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); - } - if (tree->cdr->car) { - dump_prefix(tree, offset+1); + if (class_node(tree)->superclass) { + dump_prefix(offset+1, lineno); printf("super:\n"); - mrb_parser_dump(mrb, tree->cdr->car, offset+2); + dump_node(mrb, class_node(tree)->superclass, offset+2); + } + if (class_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, class_node(tree)->body->cdr, offset+2); } - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr->cdr->car->cdr, offset+2); break; case NODE_MODULE: printf("NODE_MODULE:\n"); - if (tree->car->car == nint(0)) { - dump_prefix(tree, offset+1); - printf(":%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); + if (module_node(tree)->name) { + dump_cpath(mrb, module_node(tree)->name, offset+1, lineno); } - else if (tree->car->car == nint(1)) { - dump_prefix(tree, offset+1); - printf("::%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); + if (module_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, module_node(tree)->body->cdr, offset+2); } - else { - mrb_parser_dump(mrb, tree->car->car, offset+1); - dump_prefix(tree, offset+1); - printf("::%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); - } - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr->car->cdr, offset+2); break; case NODE_SCLASS: printf("NODE_SCLASS:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr->car->cdr, offset+2); - break; - - case NODE_DEF: - printf("NODE_DEF:\n"); - dump_prefix(tree, offset+1); - printf("%s\n", mrb_sym_dump(mrb, sym(tree->car))); - tree = tree->cdr; - { - node *n2 = tree->car; - mrb_bool first_lval = TRUE; - - if (n2 && (n2->car || n2->cdr)) { - dump_prefix(n2, offset+1); - printf("local variables:\n"); - dump_prefix(n2, offset+2); - while (n2) { - if (n2->car) { - if (!first_lval) printf(", "); - printf("%s", mrb_sym_name(mrb, sym(n2->car))); - first_lval = FALSE; - } - n2 = n2->cdr; - } - printf("\n"); - } + if (sclass_node(tree)->obj) { + dump_prefix(offset+1, lineno); + printf("obj:\n"); + dump_node(mrb, sclass_node(tree)->obj, offset+2); } - tree = tree->cdr; - if (tree->car) { - dump_args(mrb, tree->car, offset); + if (sclass_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, sclass_node(tree)->body->cdr, offset+2); } - mrb_parser_dump(mrb, tree->cdr->car, offset+1); break; case NODE_SDEF: - printf("NODE_SDEF:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - tree = tree->cdr; - dump_prefix(tree, offset+1); - printf(":%s\n", mrb_sym_dump(mrb, sym(tree->car))); - tree = tree->cdr->cdr; - if (tree->car) { - dump_args(mrb, tree->car, offset+1); + printf("NODE_SDEF: %s\n", mrb_sym_dump(mrb, def_node(tree)->name)); + if (sdef_node(tree)->obj) { + dump_prefix(offset+1, lineno); + printf("recv:\n"); + dump_node(mrb, sdef_node(tree)->obj, offset+2); + } + if (sdef_node(tree)->args) { + dump_args(mrb, sdef_node(tree)->args, offset+1, lineno); + } + if (sdef_node(tree)->locals) { + dump_locals(mrb, sdef_node(tree)->locals, offset+1, lineno); + } + if (sdef_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, sdef_node(tree)->body, offset+2); } - tree = tree->cdr; - mrb_parser_dump(mrb, tree->car, offset+1); break; case NODE_POSTEXE: printf("NODE_POSTEXE:\n"); - mrb_parser_dump(mrb, tree, offset+1); + dump_node(mrb, tree, offset+1); break; case NODE_HEREDOC: - printf("NODE_HEREDOC (<<%s):\n", ((parser_heredoc_info*)tree)->term); - dump_recur(mrb, ((parser_heredoc_info*)tree)->doc, offset+1); - break; - - case NODE_ARGS_TAIL: - printf("NODE_ARGS_TAIL:\n"); - { - node *kws = tree->car; - - while (kws) { - mrb_parser_dump(mrb, kws->car, offset+1); - kws = kws->cdr; - } + printf("NODE_HEREDOC:\n"); + if (heredoc_node(tree)->info.term) { + dump_prefix(offset+1, lineno); + printf("terminator: \"%s\"\n", heredoc_node(tree)->info.term); } - tree = tree->cdr; - if (tree->car) { - mrb_assert(intn(tree->car->car) == NODE_KW_REST_ARGS); - mrb_parser_dump(mrb, tree->car, offset+1); + if (heredoc_node(tree)->info.doc) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_str(mrb, heredoc_node(tree)->info.doc, offset+2, lineno); } - tree = tree->cdr; - if (tree->car) { - dump_prefix(tree, offset+1); - printf("block='%s'\n", mrb_sym_name(mrb, sym(tree->car))); + if (heredoc_node(tree)->info.allow_indent) { + dump_prefix(offset+1, lineno); + printf("allow_indent: true\n"); + } + if (heredoc_node(tree)->info.remove_indent) { + dump_prefix(offset+1, lineno); + printf("remove_indent: true\n"); } - break; - - case NODE_KW_ARG: - printf("NODE_KW_ARG %s:\n", mrb_sym_name(mrb, sym(tree->car))); - mrb_parser_dump(mrb, tree->cdr->car, offset + 1); - break; - - case NODE_KW_REST_ARGS: - if (tree) - printf("NODE_KW_REST_ARGS %s\n", mrb_sym_name(mrb, sym(tree))); - else - printf("NODE_KW_REST_ARGS\n"); break; default: - printf("node type: %d (0x%x)\n", nodetype, (unsigned)nodetype); + /* Fallback: unknown node type - skip like codegen.c does */ + printf("unknown node type %d (0x%x)\n", nodetype, (unsigned)nodetype); break; } #endif } +void +mrb_parser_dump(mrb_state *mrb, node *tree, int offset) +{ + dump_node(mrb, tree, offset); +} + typedef mrb_bool mrb_parser_foreach_top_variable_func(mrb_state *mrb, mrb_sym sym, void *user); void mrb_parser_foreach_top_variable(mrb_state *mrb, struct mrb_parser_state *p, mrb_parser_foreach_top_variable_func *func, void *user); @@ -7929,11 +8374,15 @@ void mrb_parser_foreach_top_variable(mrb_state *mrb, struct mrb_parser_state *p, mrb_parser_foreach_top_variable_func *func, void *user) { const mrb_ast_node *n = p->tree; - if ((intptr_t)n->car == NODE_SCOPE) { - n = n->cdr->car; + if (node_type_p((node*)n, NODE_SCOPE)) { + /* Extract locals from variable-sized NODE_SCOPE */ + struct mrb_ast_scope_node *scope = scope_node(n); + n = scope->locals; for (; n; n = n->cdr) { - mrb_sym sym = sym(n->car); - if (sym && !func(mrb, sym, user)) break; + mrb_sym sym = node_to_sym(n->car); + if (sym != 0) { + if (!func(mrb, sym, user)) break; + } } } } diff --git a/mrbgems/mruby-compiler/core/y.tab.c b/mrbgems/mruby-compiler/core/y.tab.c index 0769ced6e..a4ea9b22b 100644 --- a/mrbgems/mruby-compiler/core/y.tab.c +++ b/mrbgems/mruby-compiler/core/y.tab.c @@ -74,6 +74,7 @@ #define YYSTACK_USE_ALLOCA 1 #include +#include #include #include #include @@ -104,6 +105,15 @@ static void yywarning(parser_state *p, const char *s); static void backref_error(parser_state *p, node *n); static void void_expr_error(parser_state *p, node *n); static void tokadd(parser_state *p, int32_t c); +static const char* tok(parser_state *p); +static int toklen(parser_state *p); + +/* Forward declarations for variable-sized simple node functions */ + +/* Forward declarations for variable-sized advanced node functions */ + +/* Helper function to check node type for both traditional and variable-sized nodes */ +static mrb_bool node_type_p(node *n, enum node_type type); #define identchar(c) (ISALNUM(c) || (c) == '_' || !ISASCII(c)) @@ -124,19 +134,7 @@ typedef unsigned int stack_type; #define CMDARG_LEXPOP() BITSTACK_LEXPOP(p->cmdarg_stack) #define CMDARG_P() BITSTACK_SET_P(p->cmdarg_stack) -#define SET_LINENO(c,n) ((c)->lineno = (n)) -#define NODE_LINENO(c,n) do {\ - if (n) {\ - (c)->filename_index = (n)->filename_index;\ - (c)->lineno = (n)->lineno;\ - }\ -} while (0) - -#define sym(x) ((mrb_sym)(intptr_t)(x)) -#define nsym(x) ((node*)(intptr_t)(x)) -#define nint(x) ((node*)(intptr_t)(x)) -#define intn(x) ((int)(intptr_t)(x)) -#define typen(x) ((enum node_type)(intptr_t)(x)) +#define SET_LINENO(c,n) (((struct mrb_ast_var_header*)(c))->lineno = (n)) #define NUM_SUFFIX_R (1<<0) #define NUM_SUFFIX_I (1<<1) @@ -190,27 +188,50 @@ parser_palloc(parser_state *p, size_t size) static node* cons_gen(parser_state *p, node *car, node *cdr) { - node *c; + struct mrb_ast_node *c; + /* Try to reuse from free list first - only for 16-byte nodes */ if (p->cells) { - c = p->cells; + c = (struct mrb_ast_node*)p->cells; p->cells = p->cells->cdr; } else { - c = (node*)parser_palloc(p, sizeof(mrb_ast_node)); + c = (struct mrb_ast_node*)parser_palloc(p, sizeof(struct mrb_ast_node)); } - c->car = car; c->cdr = cdr; - c->lineno = p->lineno; - c->filename_index = p->current_filename_index; - /* beginning of next partial file; need to point the previous file */ - if (p->lineno == 0 && p->current_filename_index > 0) { - c->filename_index--; - } - return c; + /* Don't initialize location fields for structure nodes - saves CPU */ + return (node*)c; } -#define cons(a,b) cons_gen(p,(a),(b)) + +/* Head-only location optimization: separate functions for head vs structure nodes */ +#define cons(a,b) cons_gen(p,(a),(b)) /* Structure nodes - no location */ +/* Initialize variable node header */ +static void +init_var_header(struct mrb_ast_var_header *header, parser_state *p, enum node_type type) +{ + header->lineno = p->lineno; + header->filename_index = p->current_filename_index; + header->node_type = (uint8_t)type; + + /* Handle file boundary edge case */ + if (p->lineno == 0 && p->current_filename_index > 0) { + header->filename_index--; + } +} + +/* Combined allocate + init header helper */ +static inline void* +new_node(parser_state *p, size_t size, enum node_type type) +{ + void *n = parser_palloc(p, size); + init_var_header((struct mrb_ast_var_header*)n, p, type); + return n; +} + +/* Type-safe macro wrapper for node allocation */ +#define NEW_NODE(type_name, node_type) \ + (struct mrb_ast_##type_name##_node*)new_node(p, sizeof(struct mrb_ast_##type_name##_node), node_type) static node* list1_gen(parser_state *p, node *a) @@ -222,38 +243,17 @@ list1_gen(parser_state *p, node *a) static node* list2_gen(parser_state *p, node *a, node *b) { - return cons(a, cons(b,0)); + return cons(a, cons(b, 0)); } #define list2(a,b) list2_gen(p, (a),(b)) static node* list3_gen(parser_state *p, node *a, node *b, node *c) { - return cons(a, cons(b, cons(c,0))); + return cons(a, cons(b, cons(c, 0))); } #define list3(a,b,c) list3_gen(p, (a),(b),(c)) -static node* -list4_gen(parser_state *p, node *a, node *b, node *c, node *d) -{ - return cons(a, cons(b, cons(c, cons(d, 0)))); -} -#define list4(a,b,c,d) list4_gen(p, (a),(b),(c),(d)) - -static node* -list5_gen(parser_state *p, node *a, node *b, node *c, node *d, node *e) -{ - return cons(a, cons(b, cons(c, cons(d, cons(e, 0))))); -} -#define list5(a,b,c,d,e) list5_gen(p, (a),(b),(c),(d),(e)) - -static node* -list6_gen(parser_state *p, node *a, node *b, node *c, node *d, node *e, node *f) -{ - return cons(a, cons(b, cons(c, cons(d, cons(e, cons(f, 0)))))); -} -#define list6(a,b,c,d,e,f) list6_gen(p, (a),(b),(c),(d),(e),(f)) - static node* append_gen(parser_state *p, node *a, node *b) { @@ -350,7 +350,7 @@ local_var_p(parser_state *p, mrb_sym sym) while (l) { node *n = l->car; while (n) { - if (sym(n->car) == sym) return TRUE; + if (node_to_sym(n->car) == sym) return TRUE; n = n->cdr; } l = l->cdr; @@ -379,7 +379,7 @@ local_add_f(parser_state *p, mrb_sym sym) if (p->locals) { node *n = p->locals->car; while (n) { - if (sym(n->car) == sym) { + if (node_to_sym(n->car) == sym) { mrb_int len; const char* name = mrb_sym_name_len(p->mrb, sym, &len); if (len > 0 && name[0] != '_') { @@ -389,7 +389,7 @@ local_add_f(parser_state *p, mrb_sym sym) } n = n->cdr; } - p->locals->car = push(p->locals->car, nsym(sym)); + p->locals->car = push(p->locals->car, sym_to_node(sym)); } } @@ -417,16 +417,47 @@ locals_node(parser_state *p) return p->locals ? p->locals->car : NULL; } +/* Helper function to check node type for both traditional and variable-sized nodes */ +static mrb_bool +node_type_p(node *n, enum node_type type) +{ + if (!n) return FALSE; + + /* Check if this is a variable-sized node */ + struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; + return ((enum node_type)header->node_type == type); +} + +/* Helper functions for variable-sized node detection */ +static enum node_type +get_node_type(node *n) +{ + if (!n) return (enum node_type)0; + + /* Try to interpret as variable-sized node */ + struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; + enum node_type type = (enum node_type)header->node_type; + + /* Validate that the node type is within valid range for variable-sized nodes */ + if (type >= NODE_SCOPE && type < NODE_LAST) { + return type; + } + + /* If node type is invalid, this is likely a cons-list node */ + /* Return a special sentinel value to indicate cons-list fallback */ + return NODE_LAST; /* Use NODE_LAST as sentinel for cons-list nodes */ +} + static void nvars_nest(parser_state *p) { - p->nvars = cons(nint(0), p->nvars); + p->nvars = cons(int_to_node(0), p->nvars); } static void nvars_block(parser_state *p) { - p->nvars = cons(nint(-2), p->nvars); + p->nvars = cons(int_to_node(-2), p->nvars); } static void @@ -435,41 +466,56 @@ nvars_unnest(parser_state *p) p->nvars = p->nvars->cdr; } -/* (:scope (vars..) (prog...)) */ +/* struct: scope_node(locals, body) */ static node* new_scope(parser_state *p, node *body) { - return cons((node*)NODE_SCOPE, cons(locals_node(p), body)); + struct mrb_ast_scope_node *scope_node = NEW_NODE(scope, NODE_SCOPE); + scope_node->locals = locals_node(p); + scope_node->body = body; + return (node*)scope_node; } -/* (:begin prog...) */ +/* struct: stmts_node(stmts) - uses cons list */ static node* new_stmts(parser_state *p, node *body) { - if (body) { - /* If body is already a NODE_STMTS, just return it directly */ - if (typen(body->car) == NODE_STMTS) { - return body; - } - return list2((node*)NODE_STMTS, body); - } - return cons((node*)NODE_STMTS, 0); + struct mrb_ast_stmts_node *n = NEW_NODE(stmts, NODE_STMTS); + n->stmts = body ? list1(body) : 0; /* Wrap single statement in cons-list */ + + return (node*)n; } -/* (:begin body) */ +/* Helper: push statement to stmts node */ +static node* +stmts_push(parser_state *p, node *stmts, node *stmt) +{ + struct mrb_ast_stmts_node *n = stmts_node(stmts); + n->stmts = push(n->stmts, stmt); + return stmts; +} + +/* struct: begin_node(body) */ static node* new_begin(parser_state *p, node *body) { - return cons((node*)NODE_BEGIN, body); + struct mrb_ast_begin_node *begin_node = NEW_NODE(begin, NODE_BEGIN); + begin_node->body = body; + return (node*)begin_node; } #define newline_node(n) (n) -/* (:rescue body rescue else) */ +/* struct: rescue_node(body, rescue_clauses, else_clause) */ static node* new_rescue(parser_state *p, node *body, node *resq, node *els) { - return list4((node*)NODE_RESCUE, body, resq, els); + struct mrb_ast_rescue_node *n = NEW_NODE(rescue, NODE_RESCUE); + n->body = body; + n->rescue_clauses = resq; + n->else_clause = els; + + return (node*)n; } static node* @@ -478,330 +524,411 @@ new_mod_rescue(parser_state *p, node *body, node *resq) return new_rescue(p, body, list1(list3(0, 0, resq)), 0); } -/* (:ensure body ensure) */ +/* struct: ensure_node(body, ensure_clause) */ static node* new_ensure(parser_state *p, node *a, node *b) { - return cons((node*)NODE_ENSURE, cons(a, cons(0, b))); + struct mrb_ast_ensure_node *ensure_node = NEW_NODE(ensure, NODE_ENSURE); + ensure_node->body = a; + ensure_node->ensure_clause = b; + return (node*)ensure_node; } -/* (:nil) */ +/* struct: nil_node() */ static node* new_nil(parser_state *p) { - return list1((node*)NODE_NIL); + struct mrb_ast_nil_node *n = NEW_NODE(nil, NODE_NIL); + + return (node*)n; } -/* (:true) */ +/* struct: true_node() */ static node* new_true(parser_state *p) { - return list1((node*)NODE_TRUE); + struct mrb_ast_true_node *n = NEW_NODE(true, NODE_TRUE); + + return (node*)n; } -/* (:false) */ +/* struct: false_node() */ static node* new_false(parser_state *p) { - return list1((node*)NODE_FALSE); + struct mrb_ast_false_node *n = NEW_NODE(false, NODE_FALSE); + + return (node*)n; } -/* (:alias new old) */ +/* struct: alias_node(new_name, old_name) */ static node* new_alias(parser_state *p, mrb_sym a, mrb_sym b) { - return cons((node*)NODE_ALIAS, cons(nsym(a), nsym(b))); + struct mrb_ast_alias_node *alias_node = NEW_NODE(alias, NODE_ALIAS); + alias_node->new_name = a; + alias_node->old_name = b; + return (node*)alias_node; } -/* (:if cond then else) */ +/* struct: if_node(cond, then_body, else_body) */ static node* -new_if(parser_state *p, node *a, node *b, node *c) +new_if(parser_state *p, node *condition, node *then_body, node *else_body) { - void_expr_error(p, a); - return list4((node*)NODE_IF, a, b, c); + void_expr_error(p, condition); + + struct mrb_ast_if_node *n = NEW_NODE(if, NODE_IF); + n->condition = condition; + n->then_body = then_body; + n->else_body = else_body; + + return (node*)n; } -/* (:unless cond then else) */ +/* struct: while_node(cond, body) */ static node* -new_unless(parser_state *p, node *a, node *b, node *c) +new_while(parser_state *p, node *condition, node *body) { - void_expr_error(p, a); - return list4((node*)NODE_IF, a, c, b); + void_expr_error(p, condition); + + struct mrb_ast_while_node *n = NEW_NODE(while, NODE_WHILE); + n->condition = condition; + n->body = body; + + return (node*)n; } -/* (:while cond body) */ +/* struct: until_node(cond, body) */ static node* -new_while(parser_state *p, node *a, node *b) +new_until(parser_state *p, node *condition, node *body) { - void_expr_error(p, a); - return cons((node*)NODE_WHILE, cons(a, b)); + void_expr_error(p, condition); + + struct mrb_ast_until_node *n = NEW_NODE(until, NODE_UNTIL); + n->condition = condition; + n->body = body; + + return (node*)n; } -/* (:until cond body) */ +/* struct: while_node(cond, body) */ static node* -new_until(parser_state *p, node *a, node *b) +new_while_mod(parser_state *p, node *condition, node *body) { - void_expr_error(p, a); - return cons((node*)NODE_UNTIL, cons(a, b)); + node *while_node = new_while(p, condition, body); + struct mrb_ast_while_node *n = (struct mrb_ast_while_node*)while_node; + n->header.node_type = NODE_WHILE_MOD; + return while_node; } -/* (:while_mod cond body) */ -static node* -new_while_mod(parser_state *p, node *a, node *b) -{ - void_expr_error(p, a); - return cons((node*)NODE_WHILE_MOD, cons(a, b)); -} - -/* (:until_mod cond body) */ +/* struct: until_node(cond, body) */ static node* new_until_mod(parser_state *p, node *a, node *b) { - void_expr_error(p, a); - return cons((node*)NODE_UNTIL_MOD, cons(a, b)); + node *until_node = new_until(p, a, b); + struct mrb_ast_until_node *n = (struct mrb_ast_until_node*)until_node; + n->header.node_type = NODE_UNTIL_MOD; + return until_node; } -/* (:for var obj body) */ + +/* struct: for_node(var, obj, body) */ static node* new_for(parser_state *p, node *v, node *o, node *b) { void_expr_error(p, o); - return list4((node*)NODE_FOR, v, o, b); + + struct mrb_ast_for_node *n = NEW_NODE(for, NODE_FOR); + n->var = v; + n->iterable = o; + n->body = b; + + return (node*)n; } -/* (:case a ((when ...) body) ((when...) body)) */ +/* struct: case_node(expr, when_clauses) - uses cons list */ static node* new_case(parser_state *p, node *a, node *b) { - node *n = list2((node*)NODE_CASE, a); - node *n2 = n; - void_expr_error(p, a); - while (n2->cdr) { - n2 = n2->cdr; - } - n2->cdr = b; - return n; + + struct mrb_ast_case_node *n = NEW_NODE(case, NODE_CASE); + n->value = a; + n->body = b; + + return (node*)n; } -/* (:postexe a) */ +/* struct: postexe_node(body) */ static node* new_postexe(parser_state *p, node *a) { - return cons((node*)NODE_POSTEXE, a); + struct mrb_ast_postexe_node *postexe_node = NEW_NODE(postexe, NODE_POSTEXE); + postexe_node->body = a; + return (node*)postexe_node; } -/* (:self) */ +/* struct: self_node() */ static node* new_self(parser_state *p) { - return list1((node*)NODE_SELF); + struct mrb_ast_self_node *n = NEW_NODE(self, NODE_SELF); + + return (node*)n; } -/* (:call a b c) */ +/* struct: call_node(receiver, method, args) */ static node* -new_call(parser_state *p, node *a, mrb_sym b, node *c, int pass) +new_call(parser_state *p, node *receiver, mrb_sym method, node *args, int pass) { - node *n = list4(nint(pass?NODE_CALL:NODE_SCALL), a, nsym(b), c); - void_expr_error(p, a); - NODE_LINENO(n, a); - return n; + /* Calculate size needed (fixed size now) */ struct mrb_ast_call_node *n = NEW_NODE(call, NODE_CALL); + n->receiver = receiver; + n->method_name = method; + n->safe_call = (pass == 0); /* pass == 0 means safe call (&.) */ + + /* Store args pointer directly - no need to unpack and repack */ + n->args = args; + + void_expr_error(p, receiver); + return (node*)n; } -/* (:fcall self mid args) */ +/* struct: fcall_node(method, args) */ static node* new_fcall(parser_state *p, mrb_sym b, node *c) { - node *n = list4((node*)NODE_FCALL, 0, nsym(b), c); - NODE_LINENO(n, c); - return n; + return new_call(p, NULL, b, c, '.'); } /* (a b . c) */ static node* new_callargs(parser_state *p, node *a, node *b, node *c) { - return cons(a, cons(b, c)); + /* Allocate struct mrb_ast_callargs (fixed size, like new_args) */ + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)parser_palloc(p, sizeof(struct mrb_ast_callargs)); + + /* Initialize members directly */ + callargs->regular_args = a; /* Cons list of regular arguments (preserves splat compatibility) */ + callargs->keyword_args = b; /* Keyword arguments hash node */ + callargs->block_arg = c; /* Block argument node */ + + /* Return direct cast to node (like new_args) */ + return (node*)callargs; } -/* (:super . c) */ +/* struct: super_node(args) */ static node* new_super(parser_state *p, node *c) { - return cons((node*)NODE_SUPER, c); + struct mrb_ast_super_node *n = NEW_NODE(super, NODE_SUPER); + n->args = c; + + return (node*)n; } -/* (:zsuper) */ +/* struct: zsuper_node() */ static node* new_zsuper(parser_state *p) { - return cons((node*)NODE_ZSUPER, 0); + struct mrb_ast_super_node *n = NEW_NODE(super, NODE_ZSUPER); + n->args = NULL; /* zsuper initially has no args, but may be added by call_with_block */ + return (node*)n; } -/* (:yield . c) */ +/* struct: yield_node(args) */ static node* new_yield(parser_state *p, node *c) { - if (c && c->cdr && c->cdr->cdr) { - yyerror(NULL, p, "both block arg and actual block given"); - } + /* Handle callargs structure - direct casting like new_args() */ + if (c) { + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)c; + if (callargs->block_arg) { + yyerror(NULL, p, "both block arg and actual block given"); + } + } struct mrb_ast_yield_node *n = NEW_NODE(yield, NODE_YIELD); + n->args = c; - return cons((node*)NODE_YIELD, c); + return (node*)n; } -/* (:return . c) */ +/* struct: return_node(value) */ static node* new_return(parser_state *p, node *c) { - return cons((node*)NODE_RETURN, c); + struct mrb_ast_return_node *n = NEW_NODE(return, NODE_RETURN); + n->args = c; + + return (node*)n; } -/* (:break . c) */ +/* struct: break_node(value) */ static node* new_break(parser_state *p, node *c) { - return cons((node*)NODE_BREAK, c); + struct mrb_ast_break_node *n = NEW_NODE(break, NODE_BREAK); + n->value = c; + return (node*)n; } -/* (:next . c) */ +/* struct: next_node(value) */ static node* new_next(parser_state *p, node *c) { - return cons((node*)NODE_NEXT, c); + struct mrb_ast_next_node *n = NEW_NODE(next, NODE_NEXT); + n->value = c; + return (node*)n; } -/* (:redo) */ +/* struct: redo_node() */ static node* new_redo(parser_state *p) { - return list1((node*)NODE_REDO); + struct mrb_ast_redo_node *n = NEW_NODE(redo, NODE_REDO); + return (node*)n; } -/* (:retry) */ +/* struct: retry_node() */ static node* new_retry(parser_state *p) { - return list1((node*)NODE_RETRY); + struct mrb_ast_retry_node *n = NEW_NODE(retry, NODE_RETRY); + return (node*)n; } -/* (:dot2 a b) */ +/* struct: dot2_node(beg, end) */ static node* new_dot2(parser_state *p, node *a, node *b) { - return cons((node*)NODE_DOT2, cons(a, b)); + struct mrb_ast_dot2_node *n = NEW_NODE(dot2, NODE_DOT2); + n->left = a; + n->right = b; + + return (node*)n; } -/* (:dot3 a b) */ +/* struct: dot3_node(beg, end) */ static node* new_dot3(parser_state *p, node *a, node *b) { - return cons((node*)NODE_DOT3, cons(a, b)); + struct mrb_ast_dot3_node *n = NEW_NODE(dot3, NODE_DOT3); + n->left = a; + n->right = b; + + return (node*)n; } -/* (:colon2 b c) */ +/* struct: colon2_node(base, name) */ static node* new_colon2(parser_state *p, node *b, mrb_sym c) { void_expr_error(p, b); - return cons((node*)NODE_COLON2, cons(b, nsym(c))); + + struct mrb_ast_colon2_node *colon2_node = NEW_NODE(colon2, NODE_COLON2); + colon2_node->base = b; + colon2_node->name = c; + return (node*)colon2_node; } -/* (:colon3 . c) */ +/* struct: colon3_node(name) */ static node* new_colon3(parser_state *p, mrb_sym c) { - return cons((node*)NODE_COLON3, nsym(c)); + struct mrb_ast_colon3_node *colon3_node = NEW_NODE(colon3, NODE_COLON3); + colon3_node->name = c; + return (node*)colon3_node; } -/* (:and a b) */ +/* struct: and_node(left, right) */ static node* new_and(parser_state *p, node *a, node *b) { void_expr_error(p, a); - return cons((node*)NODE_AND, cons(a, b)); + + struct mrb_ast_and_node *n = NEW_NODE(and, NODE_AND); + n->left = a; + n->right = b; + + return (node*)n; } -/* (:or a b) */ +/* struct: or_node(left, right) */ static node* new_or(parser_state *p, node *a, node *b) { void_expr_error(p, a); - return cons((node*)NODE_OR, cons(a, b)); + + struct mrb_ast_or_node *n = NEW_NODE(or, NODE_OR); + n->left = a; + n->right = b; + + return (node*)n; } -/* (:array a...) */ +/* struct: array_node(elements) - uses cons list */ static node* new_array(parser_state *p, node *a) { - return cons((node*)NODE_ARRAY, a); + struct mrb_ast_array_node *n = NEW_NODE(array, NODE_ARRAY); + n->elements = a; + + return (node*)n; } -/* (:splat . a) */ +/* struct: splat_node(value) */ static node* new_splat(parser_state *p, node *a) { void_expr_error(p, a); - return cons((node*)NODE_SPLAT, a); + + struct mrb_ast_splat_node *splat_node = NEW_NODE(splat, NODE_SPLAT); + splat_node->value = a; + return (node*)splat_node; } -/* (:hash (k . v) (k . v)...) */ +/* struct: hash_node(pairs) - uses cons list */ static node* new_hash(parser_state *p, node *a) { - return cons((node*)NODE_HASH, a); -} + struct mrb_ast_hash_node *n = NEW_NODE(hash, NODE_HASH); + n->pairs = a; -/* (:kw_hash (k . v) (k . v)...) */ -static node* -new_kw_hash(parser_state *p, node *a) -{ - return cons((node*)NODE_KW_HASH, a); + return (node*)n; } /* (:sym . a) */ +/* Symbol node creation - supports both variable and legacy modes */ static node* new_sym(parser_state *p, mrb_sym sym) { - return cons((node*)NODE_SYM, nsym(sym)); + struct mrb_ast_sym_node *n = NEW_NODE(sym, NODE_SYM); + n->symbol = sym; + + return (node*)n; } +static node* +new_xvar(parser_state *p, mrb_sym sym, enum node_type type) +{ + struct mrb_ast_var_node *n = NEW_NODE(var, type); + n->symbol = sym; + + return (node*)n; +} + +#define new_lvar(p, sym) new_xvar(p, sym, NODE_LVAR) +#define new_ivar(p, sym) new_xvar(p, sym, NODE_IVAR) +#define new_gvar(p, sym) new_xvar(p, sym, NODE_GVAR) +#define new_cvar(p, sym) new_xvar(p, sym, NODE_CVAR) + static mrb_sym new_strsym(parser_state *p, node* str) { - const char *s = (const char*)str->cdr->car; - size_t len = (size_t)str->cdr->cdr; + size_t len = (size_t)str->car; + const char *s = (const char*)str->cdr; return mrb_intern(p->mrb, s, len); } -/* (:lvar . a) */ -static node* -new_lvar(parser_state *p, mrb_sym sym) -{ - return cons((node*)NODE_LVAR, nsym(sym)); -} - -/* (:gvar . a) */ -static node* -new_gvar(parser_state *p, mrb_sym sym) -{ - return cons((node*)NODE_GVAR, nsym(sym)); -} - -/* (:ivar . a) */ -static node* -new_ivar(parser_state *p, mrb_sym sym) -{ - return cons((node*)NODE_IVAR, nsym(sym)); -} - -/* (:cvar . a) */ -static node* -new_cvar(parser_state *p, mrb_sym sym) -{ - return cons((node*)NODE_CVAR, nsym(sym)); -} - /* (:nvar . a) */ static node* new_nvar(parser_state *p, int num) @@ -809,132 +936,162 @@ new_nvar(parser_state *p, int num) int nvar; node *nvars = p->nvars->cdr; while (nvars) { - nvar = intn(nvars->car); + nvar = node_to_int(nvars->car); if (nvar == -2) break; /* top of the scope */ if (nvar > 0) { yyerror(NULL, p, "numbered parameter used in outer block"); break; } - nvars->car = nint(-1); + nvars->car = int_to_node(-1); nvars = nvars->cdr; } - nvar = intn(p->nvars->car); + nvar = node_to_int(p->nvars->car); if (nvar == -1) { yyerror(NULL, p, "numbered parameter used in inner block"); } else { - p->nvars->car = nint(nvar > num ? nvar : num); + p->nvars->car = int_to_node(nvar > num ? nvar : num); } - return cons((node*)NODE_NVAR, nint(num)); + struct mrb_ast_nvar_node *n = NEW_NODE(nvar, NODE_NVAR); + n->num = num; + return (node*)n; } -/* (:const . a) */ +/* struct: const_node(name) */ static node* new_const(parser_state *p, mrb_sym sym) { - return cons((node*)NODE_CONST, nsym(sym)); + struct mrb_ast_const_node *n = NEW_NODE(const, NODE_CONST); + n->symbol = sym; + + return (node*)n; } -/* (:undef a...) */ +/* struct: undef_node(syms) - uses cons list */ static node* -new_undef(parser_state *p, mrb_sym sym) +new_undef(parser_state *p, node *syms) { - return list2((node*)NODE_UNDEF, nsym(sym)); + struct mrb_ast_undef_node *undef_node = NEW_NODE(undef, NODE_UNDEF); + undef_node->syms = syms; + return (node*)undef_node; } -/* (:class class super body) */ +/* struct: class_node(path, super, body) */ static node* new_class(parser_state *p, node *c, node *s, node *b) { void_expr_error(p, s); - return list4((node*)NODE_CLASS, c, s, cons(locals_node(p), b)); + + struct mrb_ast_class_node *n = NEW_NODE(class, NODE_CLASS); + n->name = c; + n->superclass = s; + n->body = cons(locals_node(p), b); + + return (node*)n; } -/* (:sclass obj body) */ +/* struct: sclass_node(obj, body) */ static node* new_sclass(parser_state *p, node *o, node *b) { void_expr_error(p, o); - return list3((node*)NODE_SCLASS, o, cons(locals_node(p), b)); + + struct mrb_ast_sclass_node *n = NEW_NODE(sclass, NODE_SCLASS); + n->obj = o; + n->body = cons(locals_node(p), b); + + return (node*)n; } -/* (:module module body) */ +/* struct: module_node(path, body) */ static node* new_module(parser_state *p, node *m, node *b) { - return list3((node*)NODE_MODULE, m, cons(locals_node(p), b)); + struct mrb_ast_module_node *n = NEW_NODE(module, NODE_MODULE); + n->name = m; + n->body = cons(locals_node(p), b); + + return (node*)n; } -/* (:def m lv (arg . body)) */ +/* struct: def_node(name, args, body) */ static node* -new_def(parser_state *p, mrb_sym m, node *a, node *b) +new_def(parser_state *p, mrb_sym name) { - return list5((node*)NODE_DEF, nsym(m), 0, a, b); + struct mrb_ast_def_node *n = NEW_NODE(def, NODE_DEF); + n->name = name; + n->args = (struct mrb_ast_args *)int_to_node(p->cmdarg_stack); + n->locals = local_switch(p); + n->body = NULL; + + return (node*)n; } static void defn_setup(parser_state *p, node *d, node *a, node *b) { - node *n = d->cdr->cdr; + struct mrb_ast_def_node *n = def_node(d); + node *locals = n->locals; - n->car = locals_node(p); - p->cmdarg_stack = intn(n->cdr->car); - n->cdr->car = a; - local_resume(p, n->cdr->cdr->car); - n->cdr->cdr->car = b; + n->locals = locals_node(p); + p->cmdarg_stack = node_to_int(n->args); + n->args = (struct mrb_ast_args *)a; + n->body = b; + local_resume(p, locals); } -/* (:sdef obj m lv (arg . body)) */ +/* struct: sdef_node(obj, name, args, body) */ static node* -new_sdef(parser_state *p, node *o, mrb_sym m, node *a, node *b) +new_sdef(parser_state *p, node *o, mrb_sym name) { void_expr_error(p, o); - return list6((node*)NODE_SDEF, o, nsym(m), 0, a, b); -} -static void -defs_setup(parser_state *p, node *d, node *a, node *b) -{ - node *n = d->cdr->cdr->cdr; - - n->car = locals_node(p); - p->cmdarg_stack = intn(n->cdr->car); - n->cdr->car = a; - local_resume(p, n->cdr->cdr->car); - n->cdr->cdr->car = b; -} - -/* (:arg . sym) */ -static node* -new_arg(parser_state *p, mrb_sym sym) -{ - return cons((node*)NODE_ARG, nsym(sym)); + struct mrb_ast_sdef_node *sdef_node = NEW_NODE(sdef, NODE_SDEF); + sdef_node->obj = o; + sdef_node->name = name; + sdef_node->args = (struct mrb_ast_args *)int_to_node(p->cmdarg_stack); + sdef_node->locals = local_switch(p); + sdef_node->body = NULL; + return (node*)sdef_node; } static void local_add_margs(parser_state *p, node *n) { while (n) { - if (typen(n->car->car) == NODE_MASGN) { - node *t = n->car->cdr->cdr; + if (get_node_type(n->car) == NODE_MARG) { + struct mrb_ast_masgn_node *masgn_n = (struct mrb_ast_masgn_node*)n->car; + node *rhs = masgn_n->rhs; - n->car->cdr->cdr = NULL; - while (t) { - local_add_f(p, sym(t->car)); - t = t->cdr; + /* For parameter destructuring, rhs contains the locals */ + if (rhs) { + node *t = rhs; + while (t) { + local_add_f(p, node_to_sym(t->car)); + t = t->cdr; + } + /* Clear cons list RHS immediately after use */ + masgn_n->rhs = NULL; + } + + /* Process nested destructuring in lhs components */ + if (masgn_n->pre) { + local_add_margs(p, masgn_n->pre); + } + if (masgn_n->post) { + local_add_margs(p, masgn_n->post); } - local_add_margs(p, n->car->cdr->car->car); - local_add_margs(p, n->car->cdr->car->cdr->cdr->car); } n = n->cdr; } } + static void local_add_lv(parser_state *p, node *lv) { while (lv) { - local_add_f(p, sym(lv->car)); + local_add_f(p, node_to_sym(lv->car)); lv = lv->cdr; } } @@ -948,30 +1105,54 @@ local_add_lv(parser_state *p, node *lv) static node* new_args(parser_state *p, node *m, node *opt, mrb_sym rest, node *m2, node *tail) { - node *n; - local_add_margs(p, m); local_add_margs(p, m2); - n = cons(m2, tail); - n = cons(nsym(rest), n); - n = cons(opt, n); + + /* Save original optional arguments before processing */ + node *orig_opt = opt; + + /* Process optional arguments (keep original side effects) */ while (opt) { /* opt: (sym . (opt . lv)) -> (sym . opt) */ local_add_lv(p, opt->car->cdr->cdr); opt->car->cdr = opt->car->cdr->car; opt = opt->cdr; } - return cons(m, n); + + /* Allocate struct mrb_ast_args (no hdr) */ + struct mrb_ast_args *args = (struct mrb_ast_args*)parser_palloc(p, sizeof(struct mrb_ast_args)); + + /* Initialize members */ + args->mandatory_args = m; + args->optional_args = orig_opt; + args->rest_arg = rest; + args->post_mandatory_args = m2; + + /* Deconstruct tail cons list: (kws . (kwrest . blk)) */ + if (tail) { + args->keyword_args = (node*)tail->car; /* kws */ + args->kwrest_arg = (mrb_sym)(intptr_t)tail->cdr->car; /* kwrest */ + args->block_arg = (mrb_sym)(intptr_t)tail->cdr->cdr; /* blk */ + cons_free(tail->cdr); + cons_free(tail); + } + else { + args->keyword_args = NULL; + args->kwrest_arg = 0; + args->block_arg = 0; + } + + return (node*)args; } -/* (:args_tail keywords rest_keywords_sym block_sym) */ +/* struct: args_tail_node(kwargs, kwrest, block) */ static node* -new_args_tail(parser_state *p, node *kws, node *kwrest, mrb_sym blk) +new_args_tail(parser_state *p, node *kws, mrb_sym kwrest, mrb_sym blk) { node *k; if (kws || kwrest) { - local_add_kw(p, (kwrest && kwrest->cdr)? sym(kwrest->cdr) : 0); + local_add_kw(p, kwrest); } local_add_blk(p); @@ -980,34 +1161,35 @@ new_args_tail(parser_state *p, node *kws, node *kwrest, mrb_sym blk) /* allocate register for keywords arguments */ /* order is for Proc#parameters */ for (k = kws; k; k = k->cdr) { - if (!k->car->cdr->cdr->car) { /* allocate required keywords */ - local_add_f(p, sym(k->car->cdr->car)); + if (!k->car->cdr) { /* allocate required keywords - simplified structure: (key . NULL) */ + local_add_f(p, node_to_sym(k->car->car)); } } for (k = kws; k; k = k->cdr) { - if (k->car->cdr->cdr->car) { /* allocate keywords with default */ - local_add_lv(p, k->car->cdr->cdr->car->cdr); - k->car->cdr->cdr->car = k->car->cdr->cdr->car->car; - local_add_f(p, sym(k->car->cdr->car)); + if (k->car->cdr) { /* allocate keywords with default - simplified structure: (key . value) */ + local_add_lv(p, k->car->cdr->cdr); /* value->cdr for default args */ + k->car->cdr = k->car->cdr->car; /* value->car for default args */ + local_add_f(p, node_to_sym(k->car->car)); } } - return list4((node*)NODE_ARGS_TAIL, kws, kwrest, nsym(blk)); + /* Return cons list: (keyword . (kwrest . blk)) */ + return cons(kws, cons(sym_to_node(kwrest), sym_to_node(blk))); } -/* (:kw_arg kw_sym def_arg) */ +/* (kw_sym . def_arg) - simplified from NODE_KW_ARG wrapper */ static node* new_kw_arg(parser_state *p, mrb_sym kw, node *def_arg) { mrb_assert(kw); - return list3((node*)NODE_KW_ARG, nsym(kw), def_arg); + return cons(sym_to_node(kw), def_arg); } /* (:kw_rest_args . a) */ static node* new_kw_rest_args(parser_state *p, mrb_sym sym) { - return cons((node*)NODE_KW_REST_ARGS, nsym(sym)); + return sym_to_node(intern_op(pow)); /* Use ** symbol as direct marker */ } static node* @@ -1017,26 +1199,29 @@ new_args_dots(parser_state *p, node *m) mrb_sym k = intern_op(pow); mrb_sym b = intern_op(and); local_add_f(p, r); - return new_args(p, m, 0, r, 0, - new_args_tail(p, 0, new_kw_rest_args(p, k), b)); + return new_args(p, m, 0, r, 0, new_args_tail(p, NULL, k, b)); } -/* (:block_arg . a) */ +/* struct: block_arg_node(value) */ static node* new_block_arg(parser_state *p, node *a) { - return cons((node*)NODE_BLOCK_ARG, a); + struct mrb_ast_block_arg_node *block_arg_node = NEW_NODE(block_arg, NODE_BLOCK_ARG); + block_arg_node->value = a; + return (node*)block_arg_node; } static node* setup_numparams(parser_state *p, node *a) { - int nvars = intn(p->nvars->car); + int nvars = node_to_int(p->nvars->car); if (nvars > 0) { int i; mrb_sym sym; - // m || opt || rest || tail - if (a && (a->car || (a->cdr && a->cdr->car) || (a->cdr->cdr && a->cdr->cdr->car) || (a->cdr->cdr->cdr->cdr && a->cdr->cdr->cdr->cdr->car))) { + // Check if any arguments are already defined + struct mrb_ast_args *args = (struct mrb_ast_args *)a; + if (a && (args->mandatory_args || args->optional_args || args->rest_arg || + args->post_mandatory_args || args->keyword_args || args->kwrest_arg)) { yyerror(NULL, p, "ordinary parameter is defined"); } else if (p->locals) { @@ -1049,8 +1234,8 @@ setup_numparams(parser_state *p, node *a) buf[1] = i+'0'; buf[2] = '\0'; sym = intern_cstr(buf); - args = cons(new_arg(p, sym), args); - p->locals->car = cons(nsym(sym), p->locals->car); + args = cons(new_lvar(p, sym), args); + p->locals->car = cons(sym_to_node(sym), p->locals->car); } a = new_args(p, args, 0, 0, 0, 0); } @@ -1058,58 +1243,120 @@ setup_numparams(parser_state *p, node *a) return a; } -/* (:block arg body) */ +/* struct: block_node(args, body) */ static node* new_block(parser_state *p, node *a, node *b) { - a = setup_numparams(p, a); - return list4((node*)NODE_BLOCK, locals_node(p), a, b); + a = setup_numparams(p, a); struct mrb_ast_block_node *n = NEW_NODE(block, NODE_BLOCK); + n->locals = locals_node(p); + n->args = (struct mrb_ast_args *)a; + n->body = b; + + return (node*)n; } -/* (:lambda arg body) */ +/* struct: lambda_node(args, body) */ static node* new_lambda(parser_state *p, node *a, node *b) { - a = setup_numparams(p, a); - return list4((node*)NODE_LAMBDA, locals_node(p), a, b); + a = setup_numparams(p, a); struct mrb_ast_lambda_node *lambda_node = NEW_NODE(lambda, NODE_LAMBDA); + lambda_node->locals = locals_node(p); + lambda_node->args = (struct mrb_ast_args *)a; + lambda_node->body = b; + return (node*)lambda_node; } -/* (:asgn lhs rhs) */ +/* struct: asgn_node(lhs, rhs) */ static node* new_asgn(parser_state *p, node *a, node *b) { void_expr_error(p, b); - return cons((node*)NODE_ASGN, cons(a, b)); + + struct mrb_ast_asgn_node *n = NEW_NODE(asgn, NODE_ASGN); + n->lhs = a; + n->rhs = b; + + return (node*)n; } -/* (:masgn mlhs=(pre rest post) mrhs) */ +/* Helper function to create MASGN/MARG nodes */ +static node* +new_masgn_helper(parser_state *p, node *a, node *b, enum node_type node_type) +{ + struct mrb_ast_masgn_node *n = NEW_NODE(masgn, node_type); + + /* Extract pre, rest, post from cons list structure (a b c) */ + if (a) { + n->pre = a->car; /* Pre-splat variables */ + if (a->cdr) { + n->rest = a->cdr->car; /* Splat variable (or -1 for anonymous) */ + if (a->cdr->cdr) { + n->post = a->cdr->cdr->car; /* Post-splat variables */ + cons_free(a->cdr->cdr); + } + else { + n->post = NULL; + } + cons_free(a->cdr); + } + else { + n->rest = NULL; + n->post = NULL; + } + cons_free(a); + } + else { + n->pre = NULL; + n->rest = NULL; + n->post = NULL; + } + n->rhs = b; + + return (node*)n; +} + +/* struct: masgn_node(lhs, rhs) */ static node* new_masgn(parser_state *p, node *a, node *b) { void_expr_error(p, b); - return cons((node*)NODE_MASGN, cons(a, b)); + return new_masgn_helper(p, a, b, NODE_MASGN); } -/* (:masgn mlhs mrhs) no check */ +/* (:marg mlhs mrhs) no check - for parameter destructuring */ static node* -new_masgn_param(parser_state *p, node *a, node *b) +new_marg(parser_state *p, node *a) { - return cons((node*)NODE_MASGN, cons(a, b)); + return new_masgn_helper(p, a, p->locals->car, NODE_MARG); } -/* (:asgn lhs rhs) */ +/* struct: op_asgn_node(lhs, op, rhs) */ static node* new_op_asgn(parser_state *p, node *a, mrb_sym op, node *b) { void_expr_error(p, b); - return list4((node*)NODE_OP_ASGN, a, nsym(op), b); + + struct mrb_ast_op_asgn_node *n = NEW_NODE(op_asgn, NODE_OP_ASGN); + n->lhs = a; + n->op = op; + n->rhs = b; + return (node*)n; +} + +static node* +new_int_n(parser_state *p, int32_t val) +{ + struct mrb_ast_int_node *n = NEW_NODE(int, NODE_INT); + n->value = val; + + return (node*)n; } static node* new_imaginary(parser_state *p, node *imaginary) { return new_fcall(p, MRB_SYM_2(p->mrb, Complex), - new_callargs(p, list2(list3((node*)NODE_INT, (node*)strdup("0"), nint(10)), imaginary), 0, 0)); + new_callargs(p, list2(new_int_n(p, 0), imaginary), 0, 0)); } static node* @@ -1118,26 +1365,121 @@ new_rational(parser_state *p, node *rational) return new_fcall(p, MRB_SYM_2(p->mrb, Rational), new_callargs(p, list1(rational), 0, 0)); } -/* (:int . i) */ +/* Read integer into int32_t with overflow detection */ +static mrb_bool +read_int32(const char *p, int base, int32_t *result) +{ + const char *e = p + strlen(p); + int32_t value = 0; + mrb_bool neg = FALSE; + + if (base < 2 || base > 16) { + return FALSE; + } + + if (*p == '+') { + p++; + } + else if (*p == '-') { + neg = TRUE; + p++; + } + + while (p < e) { + int n; + char c = *p; + + /* Skip underscores */ + if (c == '_') { + p++; + continue; + } + + /* Parse digit */ + if (c >= '0' && c <= '9') { + n = c - '0'; + } + else if (c >= 'a' && c <= 'f') { + n = c - 'a' + 10; + } + else if (c >= 'A' && c <= 'F') { + n = c - 'A' + 10; + } + else { + /* Invalid character */ + return FALSE; + } + + if (n >= base) { + /* Digit not valid for this base */ + return FALSE; + } + + /* Check for multiplication overflow */ + if (value > INT32_MAX / base) { + return FALSE; + } + + value *= base; + + /* Check for addition overflow */ + if (value > INT32_MAX - n) { + /* Special case: -INT32_MIN is valid */ + if (neg && value == (INT32_MAX - n + 1) && p + 1 == e) { + *result = INT32_MIN; + return TRUE; + } + return FALSE; + } + + value += n; + p++; + } + + *result = neg ? -value : value; + return TRUE; +} + static node* new_int(parser_state *p, const char *s, int base, int suffix) { - node* result = list3((node*)NODE_INT, (node*)strdup(s), nint(base)); + int32_t val; + node* result; + + /* Try to parse as int32_t first */ + if (read_int32(s, base, &val)) { + result = new_int_n(p, val); + } + else { + /* Big integer - create NODE_BIGINT */ + struct mrb_ast_bigint_node *n = NEW_NODE(bigint, NODE_BIGINT); + n->string = strdup(s); + n->base = base; + + result = (node*)n; + } + + /* Handle suffix modifiers */ if (suffix & NUM_SUFFIX_R) { result = new_rational(p, result); } if (suffix & NUM_SUFFIX_I) { result = new_imaginary(p, result); } + return result; } #ifndef MRB_NO_FLOAT -/* (:float . i) */ +/* struct: float_node(value) */ static node* new_float(parser_state *p, const char *s, int suffix) { - node* result = cons((node*)NODE_FLOAT, (node*)strdup(s)); + struct mrb_ast_float_node *n = NEW_NODE(float, NODE_FLOAT); + n->value = strdup(s); + + node* result = (node*)n; + if (suffix & NUM_SUFFIX_R) { result = new_rational(p, result); } @@ -1148,155 +1490,85 @@ new_float(parser_state *p, const char *s, int suffix) } #endif -/* (:str . (s . len)) */ +/* Create string node from cons list */ +/* struct: str_node(str) */ static node* -new_str(parser_state *p, const char *s, size_t len) +new_str(parser_state *p, node *a) { - return cons((node*)NODE_STR, cons((node*)strndup(s, len), nint(len))); + struct mrb_ast_str_node *n = NEW_NODE(str, NODE_STR); + n->list = a; + + return (node*)n; } -/* (:dstr . a) */ +/* struct: xstr_node(str) */ static node* -new_dstr(parser_state *p, node *a) +new_xstr(parser_state *p, node *a) { - return cons((node*)NODE_DSTR, a); + struct mrb_ast_xstr_node *n = NEW_NODE(xstr, NODE_XSTR); + n->list = a; + return (node*)n; } -static int -string_node_p(node *n) -{ - return (int)(typen(n->car) == NODE_STR); -} - -static node* -composite_string_node(parser_state *p, node *a, node *b) -{ - size_t newlen = (size_t)a->cdr + (size_t)b->cdr; - char *str = (char*)mempool_realloc(p->pool, a->car, (size_t)a->cdr + 1, newlen + 1); - memcpy(str + (size_t)a->cdr, b->car, (size_t)b->cdr); - str[newlen] = '\0'; - a->car = (node*)str; - a->cdr = (node*)newlen; - cons_free(b); - return a; -} - -static node* -concat_string(parser_state *p, node *a, node *b) -{ - if (string_node_p(a)) { - if (string_node_p(b)) { - /* a == NODE_STR && b == NODE_STR */ - composite_string_node(p, a->cdr, b->cdr); - cons_free(b); - return a; - } - else { - /* a == NODE_STR && b == NODE_DSTR */ - - if (string_node_p(b->cdr->car)) { - /* a == NODE_STR && b->[NODE_STR, ...] */ - composite_string_node(p, a->cdr, b->cdr->car->cdr); - cons_free(b->cdr->car); - b->cdr->car = a; - return b; - } - } - } - else { - node *c; /* last node of a */ - for (c = a; c->cdr != NULL; c = c->cdr) - ; - if (string_node_p(b)) { - /* a == NODE_DSTR && b == NODE_STR */ - if (string_node_p(c->car)) { - /* a->[..., NODE_STR] && b == NODE_STR */ - composite_string_node(p, c->car->cdr, b->cdr); - cons_free(b); - return a; - } - - push(a, b); - return a; - } - else { - /* a == NODE_DSTR && b == NODE_DSTR */ - if (string_node_p(c->car) && string_node_p(b->cdr->car)) { - /* a->[..., NODE_STR] && b->[NODE_STR, ...] */ - node *d = b->cdr; - cons_free(b); - composite_string_node(p, c->car->cdr, d->car->cdr); - cons_free(d->car); - c->cdr = d->cdr; - cons_free(d); - return a; - } - else { - c->cdr = b->cdr; - cons_free(b); - return a; - } - } - } - - return new_dstr(p, list2(a, b)); -} - -/* (:str . (s . len)) */ -static node* -new_xstr(parser_state *p, const char *s, int len) -{ - return cons((node*)NODE_XSTR, cons((node*)strndup(s, len), nint(len))); -} - -/* (:xstr . a) */ -static node* -new_dxstr(parser_state *p, node *a) -{ - return cons((node*)NODE_DXSTR, a); -} - -/* (:dsym . a) */ +/* struct: dsym_node(parts) - uses cons list */ static node* new_dsym(parser_state *p, node *a) { - return cons((node*)NODE_DSYM, a); + struct mrb_ast_str_node *n = NEW_NODE(str, NODE_DSYM); + n->list = a; + return (node*)n; } -/* (:regx . (s . (opt . enc))) */ +/* struct: regx_node(pattern, flags, encoding) */ static node* -new_regx(parser_state *p, const char *p1, const char* p2, const char* p3) +new_regx(parser_state *p, node *list, const char *flags, const char *encoding) { - return cons((node*)NODE_REGX, cons((node*)p1, cons((node*)p2, (node*)p3))); + struct mrb_ast_regx_node *n = NEW_NODE(regx, NODE_REGX); + n->list = list; + n->flags = flags; + n->encoding = encoding; + return (node*)n; } -/* (:dregx . (a . b)) */ -static node* -new_dregx(parser_state *p, node *a, node *b) -{ - return cons((node*)NODE_DREGX, cons(a, b)); -} - -/* (:backref . n) */ +/* struct: back_ref_node(n) */ static node* new_back_ref(parser_state *p, int n) { - return cons((node*)NODE_BACK_REF, nint(n)); + struct mrb_ast_back_ref_node *backref_node = NEW_NODE(back_ref, NODE_BACK_REF); + backref_node->type = n; + return (node*)backref_node; } -/* (:nthref . n) */ +/* struct: nth_ref_node(n) */ static node* new_nth_ref(parser_state *p, int n) { - return cons((node*)NODE_NTH_REF, nint(n)); + struct mrb_ast_nth_ref_node *nthref_node = NEW_NODE(nth_ref, NODE_NTH_REF); + nthref_node->nth = n; + return (node*)nthref_node; } -/* (:heredoc . a) */ +/* struct: heredoc_node(str) */ static node* -new_heredoc(parser_state *p) +new_heredoc(parser_state *p, struct mrb_parser_heredoc_info **infop) { - parser_heredoc_info *inf = (parser_heredoc_info*)parser_palloc(p, sizeof(parser_heredoc_info)); - return cons((node*)NODE_HEREDOC, (node*)inf); + struct mrb_ast_heredoc_node *n = NEW_NODE(heredoc, NODE_HEREDOC); + + /* Initialize embedded heredoc info struct */ + n->info.allow_indent = FALSE; + n->info.remove_indent = FALSE; + n->info.line_head = FALSE; + n->info.indent = 0; + n->info.indented = NULL; + n->info.type = str_not_parsing; // Will be set by heredoc processing + n->info.term = NULL; // Will be set by heredoc processing + n->info.term_len = 0; + n->info.doc = NULL; + + /* Return pointer to embedded info if requested */ + *infop = &n->info; + + return (node*)n; } static void @@ -1307,21 +1579,46 @@ new_bv(parser_state *p, mrb_sym id) static node* new_literal_delim(parser_state *p) { - return cons((node*)NODE_LITERAL_DELIM, 0); + return cons((node*)0, (node*)0); +} + +/* Helper for creating string representation cons (length . string_ptr) */ +static node* +new_str_rep(parser_state *p, const char *str, int len) +{ + return cons(int_to_node(len), (node*)strndup(str, len)); +} + +/* Helper for creating string representation from current token */ +static node* +new_str_tok(parser_state *p) +{ + return new_str_rep(p, tok(p), toklen(p)); +} + +/* Helper for creating empty string representation */ +static node* +new_str_empty(parser_state *p) +{ + return new_str_rep(p, "", 0); } /* (:words . a) */ static node* new_words(parser_state *p, node *a) { - return cons((node*)NODE_WORDS, a); + struct mrb_ast_words_node *words_node = NEW_NODE(words, NODE_WORDS); + words_node->args = a; + return (node*)words_node; } /* (:symbols . a) */ static node* new_symbols(parser_state *p, node *a) { - return cons((node*)NODE_SYMBOLS, a); + struct mrb_ast_symbols_node *symbols_node = NEW_NODE(symbols, NODE_SYMBOLS); + symbols_node->args = a; + return (node*)symbols_node; } /* xxx ----------------------------- */ @@ -1345,17 +1642,20 @@ static void args_with_block(parser_state *p, node *a, node *b) { if (b) { - if (a->cdr && a->cdr->cdr) { + /* Handle callargs structure - direct casting like new_args() */ + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)a; + if (callargs->block_arg) { yyerror(NULL, p, "both block arg and actual block given"); } - a->cdr->cdr = b; + callargs->block_arg = b; } } static void endless_method_name(parser_state *p, node *defn) { - mrb_sym sym = sym(defn->cdr->car); + struct mrb_ast_def_node *def = (struct mrb_ast_def_node*)defn; + mrb_sym sym = def->name; mrb_int len; const char *name = mrb_sym_name_len(p->mrb, sym, &len); @@ -1370,29 +1670,80 @@ endless_method_name(parser_state *p, node *defn) static void call_with_block(parser_state *p, node *a, node *b) { - node *n; + if (!a) return; - switch (typen(a->car)) { + /* Handle direct variable-sized nodes */ + struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)a; + + enum node_type var_type = (enum node_type)header->node_type; + switch (var_type) { case NODE_SUPER: case NODE_ZSUPER: - if (!a->cdr) a->cdr = new_callargs(p, 0, 0, b); - else args_with_block(p, a->cdr, b); + /* For variable-sized super/zsuper nodes, update the args field directly */ + { + struct mrb_ast_super_node *super_n = super_node(a); + if (!super_n->args) { + super_n->args = new_callargs(p, 0, 0, b); + } + else { + args_with_block(p, super_n->args, b); + } + } break; - case NODE_CALL: - case NODE_FCALL: - case NODE_SCALL: - /* (NODE_CALL recv mid (args kw . blk)) */ - n = a->cdr->cdr->cdr; /* (args kw . blk) */ - if (!n->car) n->car = new_callargs(p, 0, 0, b); - else args_with_block(p, n->car, b); + case NODE_YIELD: + /* Variable-sized yield nodes should generate an error when given a block */ + yyerror(NULL, p, "block given to yield"); break; case NODE_RETURN: + /* Variable-sized return nodes - recursively call with args */ + { + struct mrb_ast_return_node *return_n = return_node(a); + if (return_n->args != NULL) { + call_with_block(p, return_n->args, b); + } + } + break; case NODE_BREAK: + /* Variable-sized break nodes - recursively call with value */ + { + struct mrb_ast_break_node *break_n = (struct mrb_ast_break_node*)a; + if (break_n->value != NULL) { + call_with_block(p, break_n->value, b); + } + } + break; case NODE_NEXT: - if (a->cdr == NULL) return; - call_with_block(p, a->cdr, b); + /* Variable-sized next nodes - recursively call with value */ + { + struct mrb_ast_next_node *next_n = (struct mrb_ast_next_node*)a; + if (next_n->value != NULL) { + call_with_block(p, next_n->value, b); + } + } + break; + case NODE_CALL: + /* Variable-sized call nodes - add block to existing args */ + { + struct mrb_ast_call_node *call = call_node(a); + + if (call->args && callargs_node(call->args)->block_arg) { + yyerror(NULL, p, "both block arg and actual block given"); + return; + } + + /* Use existing args and add block */ + if (call->args) { + /* Modify existing callargs structure to add block */ + args_with_block(p, call->args, b); + } + else { + /* Create new callargs with just the block */ + call->args = new_callargs(p, NULL, NULL, b); + } + } break; default: + /* For other variable-sized nodes, do nothing */ break; } } @@ -1400,7 +1751,9 @@ call_with_block(parser_state *p, node *a, node *b) static node* new_negate(parser_state *p, node *n) { - return cons((node*)NODE_NEGATE, n); + struct mrb_ast_negate_node *negate_node = NEW_NODE(negate, NODE_NEGATE); + negate_node->operand = n; + return (node*)negate_node; } static node* @@ -1412,36 +1765,43 @@ cond(node *n) static node* ret_args(parser_state *p, node *n) { - if (n->cdr->cdr) { + /* Handle callargs structure - direct casting like new_args() */ + struct mrb_ast_callargs *callargs = (struct mrb_ast_callargs*)n; + if (callargs->block_arg) { yyerror(NULL, p, "block argument should not be given"); return NULL; } - if (!n->car) return NULL; - if (!n->car->cdr) return n->car->car; - return new_array(p, n->car); + if (!callargs->regular_args) return NULL; + if (!callargs->regular_args->cdr) return callargs->regular_args->car; + return new_array(p, callargs->regular_args); } static void assignable(parser_state *p, node *lhs) { - switch (intn(lhs->car)) { + switch (get_node_type(lhs)) { case NODE_LVAR: - local_add(p, sym(lhs->cdr)); + local_add(p, var_node(lhs)->symbol); break; case NODE_CONST: if (p->in_def) yyerror(NULL, p, "dynamic constant assignment"); break; + default: + /* Other node types don't need special handling in assignable */ + break; } } static node* var_reference(parser_state *p, node *lhs) { - if (intn(lhs->car) == NODE_LVAR) { - if (!local_var_p(p, sym(lhs->cdr))) { - node *n = new_fcall(p, sym(lhs->cdr), 0); - cons_free(lhs); + /* Check if this is a variable-sized node */ + if (node_type_p(lhs, NODE_LVAR)) { + mrb_sym sym = var_node(lhs)->symbol; + if (!local_var_p(p, sym)) { + node *n = new_fcall(p, sym, 0); + /* Don't free variable-sized nodes - they're managed by the parser allocator */ return n; } } @@ -1516,6 +1876,11 @@ parsing_heredoc_info(parser_state *p) node *nd = p->parsing_heredoc; if (nd == NULL) return NULL; /* mrb_assert(nd->car->car == NODE_HEREDOC); */ + if (get_node_type(nd->car) == NODE_HEREDOC) { + /* Variable-sized heredoc node - return address of embedded info struct */ + struct mrb_ast_heredoc_node *heredoc = (struct mrb_ast_heredoc_node*)nd->car; + return &heredoc->info; + } return (parser_heredoc_info*)nd->car->cdr; } @@ -1553,14 +1918,12 @@ prohibit_literals(parser_state *p, node *n) yyerror(NULL, p, "can't define singleton method for ()."); } else { - switch (typen(n->car)) { + enum node_type nt = get_node_type(n); + switch (nt) { case NODE_INT: case NODE_STR: - case NODE_DSTR: case NODE_XSTR: - case NODE_DXSTR: - case NODE_DREGX: - case NODE_MATCH: + case NODE_REGX: case NODE_FLOAT: case NODE_ARRAY: case NODE_HEREDOC: @@ -1574,7 +1937,7 @@ prohibit_literals(parser_state *p, node *n) /* xxx ----------------------------- */ -#line 1578 "mrbgems/mruby-compiler/core/y.tab.c" +#line 1941 "mrbgems/mruby-compiler/core/y.tab.c" # ifndef YY_CAST # ifdef __cplusplus @@ -1745,7 +2108,7 @@ extern int yydebug; #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED union YYSTYPE { -#line 1521 "mrbgems/mruby-compiler/core/parse.y" +#line 1884 "mrbgems/mruby-compiler/core/parse.y" node *nd; mrb_sym id; @@ -1753,7 +2116,7 @@ union YYSTYPE stack_type stack; const struct vtable *vars; -#line 1757 "mrbgems/mruby-compiler/core/y.tab.c" +#line 2120 "mrbgems/mruby-compiler/core/y.tab.c" }; typedef union YYSTYPE YYSTYPE; @@ -2512,69 +2875,69 @@ static const yytype_uint8 yytranslate[] = /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ static const yytype_int16 yyrline[] = { - 0, 1692, 1692, 1692, 1703, 1709, 1713, 1718, 1722, 1728, - 1730, 1729, 1743, 1770, 1776, 1780, 1785, 1789, 1795, 1795, - 1799, 1803, 1807, 1811, 1819, 1827, 1831, 1836, 1837, 1841, - 1845, 1849, 1853, 1859, 1862, 1866, 1870, 1874, 1878, 1882, - 1887, 1891, 1900, 1909, 1918, 1927, 1934, 1935, 1939, 1943, - 1944, 1948, 1952, 1956, 1960, 1964, 1974, 1973, 1988, 1997, - 1998, 2001, 2002, 2009, 2008, 2023, 2027, 2032, 2036, 2041, - 2045, 2050, 2054, 2058, 2062, 2066, 2072, 2076, 2082, 2083, - 2089, 2093, 2097, 2101, 2105, 2109, 2113, 2117, 2121, 2125, - 2131, 2132, 2138, 2142, 2148, 2152, 2158, 2162, 2166, 2170, - 2174, 2178, 2184, 2190, 2197, 2201, 2205, 2209, 2213, 2217, - 2223, 2229, 2234, 2240, 2244, 2247, 2251, 2255, 2262, 2263, - 2264, 2265, 2270, 2277, 2278, 2281, 2285, 2285, 2291, 2292, - 2293, 2294, 2295, 2296, 2297, 2298, 2299, 2300, 2301, 2302, - 2303, 2304, 2305, 2306, 2307, 2308, 2309, 2310, 2311, 2312, - 2313, 2314, 2315, 2316, 2317, 2318, 2319, 2320, 2323, 2323, - 2323, 2324, 2324, 2325, 2325, 2325, 2326, 2326, 2326, 2326, - 2327, 2327, 2327, 2328, 2328, 2328, 2329, 2329, 2329, 2329, - 2330, 2330, 2330, 2330, 2331, 2331, 2331, 2331, 2332, 2332, - 2332, 2332, 2333, 2333, 2333, 2333, 2334, 2334, 2337, 2341, - 2345, 2349, 2353, 2357, 2361, 2366, 2371, 2376, 2380, 2384, - 2388, 2392, 2396, 2400, 2404, 2408, 2412, 2416, 2420, 2424, - 2428, 2432, 2436, 2440, 2444, 2448, 2452, 2456, 2460, 2464, - 2468, 2472, 2476, 2480, 2484, 2488, 2492, 2496, 2500, 2504, - 2508, 2512, 2516, 2520, 2524, 2533, 2542, 2551, 2560, 2566, - 2567, 2572, 2576, 2583, 2587, 2594, 2598, 2607, 2624, 2625, - 2628, 2629, 2630, 2635, 2640, 2647, 2653, 2658, 2663, 2668, - 2675, 2675, 2686, 2690, 2696, 2700, 2706, 2709, 2715, 2719, - 2724, 2729, 2733, 2739, 2744, 2748, 2754, 2755, 2756, 2757, - 2758, 2759, 2760, 2761, 2766, 2765, 2777, 2781, 2776, 2786, - 2786, 2790, 2794, 2798, 2802, 2807, 2812, 2816, 2820, 2824, - 2828, 2832, 2833, 2839, 2846, 2838, 2859, 2867, 2875, 2875, - 2875, 2882, 2882, 2882, 2889, 2895, 2900, 2902, 2899, 2911, - 2909, 2927, 2932, 2925, 2949, 2947, 2963, 2973, 2984, 2988, - 2992, 2996, 3002, 3009, 3010, 3011, 3014, 3015, 3018, 3019, - 3027, 3028, 3034, 3038, 3041, 3045, 3049, 3053, 3058, 3062, - 3066, 3070, 3076, 3075, 3085, 3089, 3093, 3097, 3103, 3108, - 3113, 3117, 3121, 3125, 3129, 3133, 3137, 3141, 3145, 3149, - 3153, 3157, 3161, 3165, 3169, 3175, 3180, 3187, 3187, 3191, - 3196, 3203, 3207, 3213, 3214, 3217, 3222, 3225, 3229, 3235, - 3239, 3246, 3245, 3262, 3272, 3276, 3281, 3288, 3292, 3296, - 3300, 3304, 3308, 3312, 3316, 3320, 3327, 3326, 3341, 3340, - 3356, 3364, 3373, 3376, 3383, 3386, 3390, 3391, 3394, 3398, - 3401, 3405, 3408, 3409, 3410, 3411, 3414, 3415, 3421, 3422, - 3423, 3427, 3440, 3441, 3447, 3452, 3451, 3461, 3465, 3471, - 3475, 3488, 3492, 3498, 3501, 3502, 3505, 3511, 3517, 3518, - 3521, 3528, 3527, 3540, 3544, 3558, 3562, 3574, 3581, 3588, - 3589, 3590, 3591, 3592, 3596, 3602, 3606, 3616, 3617, 3618, - 3622, 3628, 3632, 3636, 3640, 3644, 3650, 3654, 3660, 3664, - 3668, 3672, 3676, 3680, 3684, 3692, 3699, 3705, 3706, 3710, - 3714, 3713, 3730, 3731, 3734, 3740, 3744, 3750, 3751, 3755, - 3759, 3765, 3769, 3775, 3781, 3788, 3794, 3801, 3805, 3811, - 3815, 3821, 3822, 3825, 3829, 3835, 3839, 3843, 3847, 3853, - 3858, 3863, 3867, 3871, 3875, 3879, 3883, 3887, 3891, 3895, - 3899, 3903, 3907, 3911, 3915, 3920, 3926, 3931, 3936, 3941, - 3946, 3953, 3957, 3964, 3969, 3968, 3980, 3984, 3990, 3998, - 4006, 4014, 4018, 4024, 4028, 4034, 4035, 4038, 4043, 4050, - 4051, 4054, 4058, 4064, 4068, 4074, 4080, 4080, 4087, 4088, - 4094, 4099, 4105, 4111, 4116, 4120, 4125, 4130, 4140, 4145, - 4151, 4152, 4153, 4156, 4157, 4158, 4159, 4162, 4163, 4164, - 4167, 4168, 4171, 4175, 4181, 4182, 4188, 4189, 4192, 4193, - 4196, 4199, 4200, 4201, 4204, 4205, 4208, 4213, 4216, 4217, - 4221 + 0, 2055, 2055, 2055, 2065, 2071, 2075, 2079, 2083, 2089, + 2091, 2090, 2104, 2130, 2136, 2140, 2144, 2148, 2154, 2154, + 2158, 2162, 2166, 2170, 2179, 2188, 2192, 2197, 2198, 2202, + 2206, 2210, 2214, 2220, 2223, 2227, 2231, 2235, 2239, 2243, + 2248, 2252, 2261, 2270, 2279, 2288, 2295, 2296, 2300, 2303, + 2304, 2308, 2312, 2316, 2320, 2323, 2333, 2332, 2347, 2356, + 2357, 2360, 2361, 2368, 2367, 2382, 2386, 2391, 2395, 2400, + 2404, 2409, 2413, 2417, 2421, 2425, 2431, 2435, 2441, 2442, + 2448, 2452, 2456, 2460, 2464, 2468, 2472, 2476, 2480, 2484, + 2490, 2491, 2497, 2501, 2507, 2511, 2517, 2521, 2525, 2529, + 2533, 2537, 2543, 2549, 2556, 2560, 2564, 2568, 2572, 2576, + 2582, 2588, 2593, 2599, 2603, 2606, 2610, 2614, 2621, 2622, + 2623, 2624, 2629, 2636, 2637, 2640, 2644, 2644, 2650, 2651, + 2652, 2653, 2654, 2655, 2656, 2657, 2658, 2659, 2660, 2661, + 2662, 2663, 2664, 2665, 2666, 2667, 2668, 2669, 2670, 2671, + 2672, 2673, 2674, 2675, 2676, 2677, 2678, 2679, 2682, 2682, + 2682, 2683, 2683, 2684, 2684, 2684, 2685, 2685, 2685, 2685, + 2686, 2686, 2686, 2687, 2687, 2687, 2688, 2688, 2688, 2688, + 2689, 2689, 2689, 2689, 2690, 2690, 2690, 2690, 2691, 2691, + 2691, 2691, 2692, 2692, 2692, 2692, 2693, 2693, 2696, 2700, + 2704, 2708, 2712, 2716, 2720, 2725, 2730, 2735, 2739, 2743, + 2747, 2751, 2755, 2759, 2763, 2767, 2771, 2775, 2779, 2783, + 2787, 2791, 2795, 2799, 2803, 2807, 2811, 2815, 2819, 2823, + 2827, 2831, 2835, 2839, 2843, 2847, 2851, 2855, 2859, 2863, + 2867, 2871, 2875, 2879, 2883, 2892, 2901, 2910, 2919, 2925, + 2926, 2930, 2934, 2940, 2944, 2951, 2955, 2964, 2981, 2982, + 2985, 2986, 2987, 2991, 2995, 3001, 3006, 3010, 3014, 3018, + 3024, 3024, 3035, 3039, 3045, 3049, 3055, 3058, 3063, 3067, + 3071, 3076, 3080, 3086, 3091, 3095, 3101, 3102, 3106, 3110, + 3111, 3112, 3113, 3114, 3119, 3118, 3130, 3134, 3129, 3139, + 3139, 3143, 3147, 3151, 3155, 3159, 3163, 3167, 3171, 3175, + 3179, 3183, 3184, 3190, 3197, 3189, 3210, 3218, 3226, 3226, + 3226, 3233, 3233, 3233, 3240, 3246, 3251, 3253, 3250, 3262, + 3260, 3278, 3283, 3276, 3300, 3298, 3314, 3324, 3335, 3339, + 3343, 3347, 3353, 3360, 3361, 3362, 3365, 3366, 3369, 3370, + 3378, 3379, 3385, 3389, 3392, 3396, 3400, 3404, 3409, 3413, + 3417, 3421, 3427, 3426, 3436, 3440, 3444, 3448, 3454, 3459, + 3464, 3468, 3472, 3476, 3480, 3484, 3488, 3492, 3496, 3500, + 3504, 3508, 3512, 3516, 3520, 3526, 3531, 3538, 3538, 3542, + 3547, 3553, 3557, 3563, 3564, 3567, 3572, 3575, 3579, 3585, + 3589, 3596, 3595, 3612, 3617, 3621, 3626, 3633, 3637, 3641, + 3645, 3649, 3653, 3657, 3661, 3665, 3672, 3671, 3686, 3685, + 3701, 3709, 3718, 3721, 3728, 3731, 3735, 3736, 3739, 3743, + 3746, 3750, 3753, 3754, 3755, 3756, 3759, 3760, 3766, 3771, + 3776, 3781, 3787, 3788, 3794, 3800, 3799, 3811, 3815, 3821, + 3825, 3831, 3840, 3851, 3854, 3855, 3858, 3864, 3870, 3871, + 3874, 3881, 3880, 3895, 3899, 3907, 3911, 3923, 3930, 3937, + 3938, 3939, 3940, 3941, 3945, 3951, 3955, 3963, 3964, 3965, + 3969, 3975, 3979, 3983, 3987, 3991, 3997, 4001, 4007, 4011, + 4015, 4019, 4023, 4027, 4031, 4039, 4046, 4052, 4053, 4057, + 4061, 4060, 4077, 4078, 4081, 4087, 4091, 4097, 4098, 4102, + 4106, 4112, 4116, 4122, 4128, 4135, 4141, 4148, 4152, 4158, + 4162, 4168, 4169, 4172, 4176, 4182, 4186, 4190, 4194, 4200, + 4205, 4210, 4214, 4218, 4222, 4226, 4230, 4234, 4238, 4242, + 4246, 4250, 4254, 4258, 4262, 4267, 4273, 4278, 4283, 4288, + 4293, 4300, 4304, 4311, 4316, 4315, 4327, 4331, 4337, 4345, + 4353, 4361, 4365, 4371, 4375, 4381, 4382, 4385, 4390, 4397, + 4398, 4401, 4405, 4411, 4415, 4421, 4427, 4427, 4434, 4435, + 4441, 4445, 4451, 4457, 4462, 4466, 4471, 4476, 4492, 4497, + 4503, 4504, 4505, 4508, 4509, 4510, 4511, 4514, 4515, 4516, + 4519, 4520, 4523, 4527, 4533, 4534, 4540, 4541, 4544, 4545, + 4548, 4551, 4552, 4553, 4556, 4557, 4560, 4565, 4568, 4569, + 4573 }; #endif @@ -6551,7 +6914,7 @@ YYLTYPE yylloc = yyloc_default; -#line 6555 "mrbgems/mruby-compiler/core/y.tab.c" +#line 6918 "mrbgems/mruby-compiler/core/y.tab.c" yylsp[0] = yylloc; goto yysetstate; @@ -6764,94 +7127,91 @@ yyreduce: switch (yyn) { case 2: /* $@1: %empty */ -#line 1692 "mrbgems/mruby-compiler/core/parse.y" +#line 2055 "mrbgems/mruby-compiler/core/parse.y" { p->lstate = EXPR_BEG; if (!p->locals) p->locals = cons(0,0); } -#line 6773 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7136 "mrbgems/mruby-compiler/core/y.tab.c" break; case 3: /* program: $@1 top_compstmt */ -#line 1697 "mrbgems/mruby-compiler/core/parse.y" +#line 2060 "mrbgems/mruby-compiler/core/parse.y" { p->tree = new_scope(p, (yyvsp[0].nd)); - NODE_LINENO(p->tree, (yyvsp[0].nd)); } -#line 6782 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7144 "mrbgems/mruby-compiler/core/y.tab.c" break; case 4: /* top_compstmt: top_stmts opt_terms */ -#line 1704 "mrbgems/mruby-compiler/core/parse.y" +#line 2066 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 6790 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7152 "mrbgems/mruby-compiler/core/y.tab.c" break; case 5: /* top_stmts: none */ -#line 1710 "mrbgems/mruby-compiler/core/parse.y" +#line 2072 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, 0); } -#line 6798 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7160 "mrbgems/mruby-compiler/core/y.tab.c" break; case 6: /* top_stmts: top_stmt */ -#line 1714 "mrbgems/mruby-compiler/core/parse.y" +#line 2076 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, (yyvsp[0].nd)); - NODE_LINENO((yyval.nd), (yyvsp[0].nd)); } -#line 6807 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7168 "mrbgems/mruby-compiler/core/y.tab.c" break; case 7: /* top_stmts: top_stmts terms top_stmt */ -#line 1719 "mrbgems/mruby-compiler/core/parse.y" +#line 2080 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = push((yyvsp[-2].nd), newline_node((yyvsp[0].nd))); + (yyval.nd) = stmts_push(p, (yyvsp[-2].nd), newline_node((yyvsp[0].nd))); } -#line 6815 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7176 "mrbgems/mruby-compiler/core/y.tab.c" break; case 8: /* top_stmts: error top_stmt */ -#line 1723 "mrbgems/mruby-compiler/core/parse.y" +#line 2084 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, 0); } -#line 6823 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7184 "mrbgems/mruby-compiler/core/y.tab.c" break; case 10: /* @2: %empty */ -#line 1730 "mrbgems/mruby-compiler/core/parse.y" +#line 2091 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = local_switch(p); nvars_block(p); } -#line 6832 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7193 "mrbgems/mruby-compiler/core/y.tab.c" break; case 11: /* top_stmt: "'BEGIN'" @2 '{' top_compstmt '}' */ -#line 1735 "mrbgems/mruby-compiler/core/parse.y" +#line 2096 "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 6843 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7204 "mrbgems/mruby-compiler/core/y.tab.c" break; case 12: /* bodystmt: compstmt opt_rescue opt_else opt_ensure */ -#line 1747 "mrbgems/mruby-compiler/core/parse.y" +#line 2108 "mrbgems/mruby-compiler/core/parse.y" { if ((yyvsp[-2].nd)) { (yyval.nd) = new_rescue(p, (yyvsp[-3].nd), (yyvsp[-2].nd), (yyvsp[-1].nd)); - NODE_LINENO((yyval.nd), (yyvsp[-3].nd)); } else if ((yyvsp[-1].nd)) { yywarning(p, "else without rescue is useless"); - (yyval.nd) = push((yyvsp[-3].nd), (yyvsp[-1].nd)); + (yyval.nd) = stmts_push(p, (yyvsp[-3].nd), (yyvsp[-1].nd)); } else { (yyval.nd) = (yyvsp[-3].nd); @@ -6865,230 +7225,231 @@ yyreduce: } } } -#line 6869 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7229 "mrbgems/mruby-compiler/core/y.tab.c" break; case 13: /* compstmt: stmts opt_terms */ -#line 1771 "mrbgems/mruby-compiler/core/parse.y" +#line 2131 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 6877 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7237 "mrbgems/mruby-compiler/core/y.tab.c" break; case 14: /* stmts: none */ -#line 1777 "mrbgems/mruby-compiler/core/parse.y" +#line 2137 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, 0); } -#line 6885 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7245 "mrbgems/mruby-compiler/core/y.tab.c" break; case 15: /* stmts: stmt */ -#line 1781 "mrbgems/mruby-compiler/core/parse.y" +#line 2141 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, (yyvsp[0].nd)); - NODE_LINENO((yyval.nd), (yyvsp[0].nd)); } -#line 6894 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7253 "mrbgems/mruby-compiler/core/y.tab.c" break; case 16: /* stmts: stmts terms stmt */ -#line 1786 "mrbgems/mruby-compiler/core/parse.y" +#line 2145 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = push((yyvsp[-2].nd), newline_node((yyvsp[0].nd))); + (yyval.nd) = stmts_push(p, (yyvsp[-2].nd), newline_node((yyvsp[0].nd))); } -#line 6902 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7261 "mrbgems/mruby-compiler/core/y.tab.c" break; case 17: /* stmts: error stmt */ -#line 1790 "mrbgems/mruby-compiler/core/parse.y" +#line 2149 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_stmts(p, (yyvsp[0].nd)); } -#line 6910 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7269 "mrbgems/mruby-compiler/core/y.tab.c" break; case 18: /* $@3: %empty */ -#line 1795 "mrbgems/mruby-compiler/core/parse.y" +#line 2154 "mrbgems/mruby-compiler/core/parse.y" {p->lstate = EXPR_FNAME;} -#line 6916 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7275 "mrbgems/mruby-compiler/core/y.tab.c" break; case 19: /* stmt: "'alias'" fsym $@3 fsym */ -#line 1796 "mrbgems/mruby-compiler/core/parse.y" +#line 2155 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_alias(p, (yyvsp[-2].id), (yyvsp[0].id)); } -#line 6924 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7283 "mrbgems/mruby-compiler/core/y.tab.c" break; case 20: /* stmt: "'undef'" undef_list */ -#line 1800 "mrbgems/mruby-compiler/core/parse.y" +#line 2159 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = (yyvsp[0].nd); + (yyval.nd) = new_undef(p, (yyvsp[0].nd)); } -#line 6932 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7291 "mrbgems/mruby-compiler/core/y.tab.c" break; case 21: /* stmt: stmt "'if' modifier" expr_value */ -#line 1804 "mrbgems/mruby-compiler/core/parse.y" +#line 2163 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[0].nd)), (yyvsp[-2].nd), 0); } -#line 6940 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7299 "mrbgems/mruby-compiler/core/y.tab.c" break; case 22: /* stmt: stmt "'unless' modifier" expr_value */ -#line 1808 "mrbgems/mruby-compiler/core/parse.y" +#line 2167 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_unless(p, cond((yyvsp[0].nd)), (yyvsp[-2].nd), 0); + (yyval.nd) = new_if(p, cond((yyvsp[0].nd)), 0, (yyvsp[-2].nd)); } -#line 6948 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7307 "mrbgems/mruby-compiler/core/y.tab.c" break; case 23: /* stmt: stmt "'while' modifier" expr_value */ -#line 1812 "mrbgems/mruby-compiler/core/parse.y" +#line 2171 "mrbgems/mruby-compiler/core/parse.y" { - if ((yyvsp[-2].nd) && typen((yyvsp[-2].nd)->car) == NODE_BEGIN) { + 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)); - } else { + } + else { (yyval.nd) = new_while(p, cond((yyvsp[0].nd)), (yyvsp[-2].nd)); } } -#line 6960 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7320 "mrbgems/mruby-compiler/core/y.tab.c" break; case 24: /* stmt: stmt "'until' modifier" expr_value */ -#line 1820 "mrbgems/mruby-compiler/core/parse.y" +#line 2180 "mrbgems/mruby-compiler/core/parse.y" { - if ((yyvsp[-2].nd) && typen((yyvsp[-2].nd)->car) == NODE_BEGIN) { + 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)); - } else { + } + else { (yyval.nd) = new_until(p, cond((yyvsp[0].nd)), (yyvsp[-2].nd)); } } -#line 6972 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7333 "mrbgems/mruby-compiler/core/y.tab.c" break; case 25: /* stmt: stmt "'rescue' modifier" stmt */ -#line 1828 "mrbgems/mruby-compiler/core/parse.y" +#line 2189 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_mod_rescue(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 6980 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7341 "mrbgems/mruby-compiler/core/y.tab.c" break; case 26: /* stmt: "'END'" '{' compstmt '}' */ -#line 1832 "mrbgems/mruby-compiler/core/parse.y" +#line 2193 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[-3]), p, "END not supported"); (yyval.nd) = new_postexe(p, (yyvsp[-1].nd)); } -#line 6989 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7350 "mrbgems/mruby-compiler/core/y.tab.c" break; case 28: /* stmt: mlhs '=' command_call */ -#line 1838 "mrbgems/mruby-compiler/core/parse.y" +#line 2199 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_masgn(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 6997 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7358 "mrbgems/mruby-compiler/core/y.tab.c" break; case 29: /* stmt: lhs '=' mrhs */ -#line 1842 "mrbgems/mruby-compiler/core/parse.y" +#line 2203 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_asgn(p, (yyvsp[-2].nd), new_array(p, (yyvsp[0].nd))); } -#line 7005 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7366 "mrbgems/mruby-compiler/core/y.tab.c" break; case 30: /* stmt: mlhs '=' arg */ -#line 1846 "mrbgems/mruby-compiler/core/parse.y" +#line 2207 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_masgn(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7013 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7374 "mrbgems/mruby-compiler/core/y.tab.c" break; case 31: /* stmt: mlhs '=' mrhs */ -#line 1850 "mrbgems/mruby-compiler/core/parse.y" +#line 2211 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_masgn(p, (yyvsp[-2].nd), new_array(p, (yyvsp[0].nd))); } -#line 7021 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7382 "mrbgems/mruby-compiler/core/y.tab.c" break; case 32: /* stmt: arg "=>" "local variable or method" */ -#line 1854 "mrbgems/mruby-compiler/core/parse.y" +#line 2215 "mrbgems/mruby-compiler/core/parse.y" { node *lhs = new_lvar(p, (yyvsp[0].id)); assignable(p, lhs); (yyval.nd) = new_asgn(p, lhs, (yyvsp[-2].nd)); } -#line 7031 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7392 "mrbgems/mruby-compiler/core/y.tab.c" break; case 34: /* command_asgn: lhs '=' command_rhs */ -#line 1863 "mrbgems/mruby-compiler/core/parse.y" +#line 2224 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_asgn(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7039 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7400 "mrbgems/mruby-compiler/core/y.tab.c" break; case 35: /* command_asgn: var_lhs tOP_ASGN command_rhs */ -#line 1867 "mrbgems/mruby-compiler/core/parse.y" +#line 2228 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_op_asgn(p, (yyvsp[-2].nd), (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 7047 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7408 "mrbgems/mruby-compiler/core/y.tab.c" break; case 36: /* command_asgn: primary_value '[' opt_call_args ']' tOP_ASGN command_rhs */ -#line 1871 "mrbgems/mruby-compiler/core/parse.y" +#line 2232 "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 7055 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7416 "mrbgems/mruby-compiler/core/y.tab.c" break; case 37: /* command_asgn: primary_value call_op "local variable or method" tOP_ASGN command_rhs */ -#line 1875 "mrbgems/mruby-compiler/core/parse.y" +#line 2236 "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 7063 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7424 "mrbgems/mruby-compiler/core/y.tab.c" break; case 38: /* command_asgn: primary_value call_op "constant" tOP_ASGN command_rhs */ -#line 1879 "mrbgems/mruby-compiler/core/parse.y" +#line 2240 "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 7071 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7432 "mrbgems/mruby-compiler/core/y.tab.c" break; case 39: /* command_asgn: primary_value "::" "constant" tOP_ASGN command_call */ -#line 1883 "mrbgems/mruby-compiler/core/parse.y" +#line 2244 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[-4]), p, "constant re-assignment"); (yyval.nd) = 0; } -#line 7080 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7441 "mrbgems/mruby-compiler/core/y.tab.c" break; case 40: /* command_asgn: primary_value "::" "local variable or method" tOP_ASGN command_rhs */ -#line 1888 "mrbgems/mruby-compiler/core/parse.y" +#line 2249 "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 7088 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7449 "mrbgems/mruby-compiler/core/y.tab.c" break; case 41: /* command_asgn: defn_head f_opt_arglist_paren '=' command */ -#line 1892 "mrbgems/mruby-compiler/core/parse.y" +#line 2253 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); endless_method_name(p, (yyvsp[-3].nd)); @@ -7097,11 +7458,11 @@ yyreduce: nvars_unnest(p); p->in_def--; } -#line 7101 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7462 "mrbgems/mruby-compiler/core/y.tab.c" break; case 42: /* command_asgn: defn_head f_opt_arglist_paren '=' command "'rescue' modifier" arg */ -#line 1901 "mrbgems/mruby-compiler/core/parse.y" +#line 2262 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-5].nd); endless_method_name(p, (yyvsp[-5].nd)); @@ -7110,1161 +7471,1161 @@ yyreduce: nvars_unnest(p); p->in_def--; } -#line 7114 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7475 "mrbgems/mruby-compiler/core/y.tab.c" break; case 43: /* command_asgn: defs_head f_opt_arglist_paren '=' command */ -#line 1910 "mrbgems/mruby-compiler/core/parse.y" +#line 2271 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); void_expr_error(p, (yyvsp[0].nd)); - defs_setup(p, (yyval.nd), (yyvsp[-2].nd), (yyvsp[0].nd)); + defn_setup(p, (yyval.nd), (yyvsp[-2].nd), (yyvsp[0].nd)); nvars_unnest(p); p->in_def--; p->in_single--; } -#line 7127 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7488 "mrbgems/mruby-compiler/core/y.tab.c" break; case 44: /* command_asgn: defs_head f_opt_arglist_paren '=' command "'rescue' modifier" arg */ -#line 1919 "mrbgems/mruby-compiler/core/parse.y" +#line 2280 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-5].nd); void_expr_error(p, (yyvsp[-2].nd)); - defs_setup(p, (yyval.nd), (yyvsp[-4].nd), new_mod_rescue(p, (yyvsp[-2].nd), (yyvsp[0].nd))); + defn_setup(p, (yyval.nd), (yyvsp[-4].nd), new_mod_rescue(p, (yyvsp[-2].nd), (yyvsp[0].nd))); nvars_unnest(p); p->in_def--; p->in_single--; } -#line 7140 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7501 "mrbgems/mruby-compiler/core/y.tab.c" break; case 45: /* command_asgn: backref tOP_ASGN command_rhs */ -#line 1928 "mrbgems/mruby-compiler/core/parse.y" +#line 2289 "mrbgems/mruby-compiler/core/parse.y" { backref_error(p, (yyvsp[-2].nd)); (yyval.nd) = new_stmts(p, 0); } -#line 7149 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7510 "mrbgems/mruby-compiler/core/y.tab.c" break; case 47: /* command_rhs: command_call "'rescue' modifier" stmt */ -#line 1936 "mrbgems/mruby-compiler/core/parse.y" +#line 2297 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_mod_rescue(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7157 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7518 "mrbgems/mruby-compiler/core/y.tab.c" break; case 50: /* expr: expr "'and'" expr */ -#line 1945 "mrbgems/mruby-compiler/core/parse.y" +#line 2305 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_and(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7165 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7526 "mrbgems/mruby-compiler/core/y.tab.c" break; case 51: /* expr: expr "'or'" expr */ -#line 1949 "mrbgems/mruby-compiler/core/parse.y" +#line 2309 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_or(p, (yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 7173 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7534 "mrbgems/mruby-compiler/core/y.tab.c" break; case 52: /* expr: "'not'" opt_nl expr */ -#line 1953 "mrbgems/mruby-compiler/core/parse.y" +#line 2313 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, cond((yyvsp[0].nd)), "!"); } -#line 7181 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7542 "mrbgems/mruby-compiler/core/y.tab.c" break; case 53: /* expr: '!' command_call */ -#line 1957 "mrbgems/mruby-compiler/core/parse.y" +#line 2317 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, cond((yyvsp[0].nd)), "!"); } -#line 7189 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7550 "mrbgems/mruby-compiler/core/y.tab.c" break; case 55: /* defn_head: "'def'" fname */ -#line 1965 "mrbgems/mruby-compiler/core/parse.y" +#line 2324 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_def(p, (yyvsp[0].id), nint(p->cmdarg_stack), local_switch(p)); + (yyval.nd) = new_def(p, (yyvsp[0].id)); p->cmdarg_stack = 0; p->in_def++; nvars_block(p); } -#line 7200 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7561 "mrbgems/mruby-compiler/core/y.tab.c" break; case 56: /* $@4: %empty */ -#line 1974 "mrbgems/mruby-compiler/core/parse.y" +#line 2333 "mrbgems/mruby-compiler/core/parse.y" { p->lstate = EXPR_FNAME; } -#line 7208 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7569 "mrbgems/mruby-compiler/core/y.tab.c" break; case 57: /* defs_head: "'def'" singleton dot_or_colon $@4 fname */ -#line 1978 "mrbgems/mruby-compiler/core/parse.y" +#line 2337 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_sdef(p, (yyvsp[-3].nd), (yyvsp[0].id), nint(p->cmdarg_stack), local_switch(p)); + (yyval.nd) = new_sdef(p, (yyvsp[-3].nd), (yyvsp[0].id)); p->cmdarg_stack = 0; p->in_def++; p->in_single++; nvars_block(p); p->lstate = EXPR_ENDFN; /* force for args */ } -#line 7221 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7582 "mrbgems/mruby-compiler/core/y.tab.c" break; case 58: /* expr_value: expr */ -#line 1989 "mrbgems/mruby-compiler/core/parse.y" +#line 2348 "mrbgems/mruby-compiler/core/parse.y" { if (!(yyvsp[0].nd)) (yyval.nd) = new_nil(p); else { (yyval.nd) = (yyvsp[0].nd); } } -#line 7232 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7593 "mrbgems/mruby-compiler/core/y.tab.c" break; case 62: /* block_command: block_call call_op2 operation2 command_args */ -#line 2003 "mrbgems/mruby-compiler/core/parse.y" +#line 2362 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), (yyvsp[-2].num)); } -#line 7240 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7601 "mrbgems/mruby-compiler/core/y.tab.c" break; case 63: /* $@5: %empty */ -#line 2009 "mrbgems/mruby-compiler/core/parse.y" +#line 2368 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); nvars_nest(p); } -#line 7249 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7610 "mrbgems/mruby-compiler/core/y.tab.c" break; case 64: /* cmd_brace_block: "{" $@5 opt_block_param compstmt '}' */ -#line 2016 "mrbgems/mruby-compiler/core/parse.y" +#line 2375 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_block(p, (yyvsp[-2].nd), (yyvsp[-1].nd)); local_unnest(p); nvars_unnest(p); } -#line 7259 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7620 "mrbgems/mruby-compiler/core/y.tab.c" break; case 65: /* command: operation command_args */ -#line 2024 "mrbgems/mruby-compiler/core/parse.y" +#line 2383 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_fcall(p, (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 7267 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7628 "mrbgems/mruby-compiler/core/y.tab.c" break; case 66: /* command: operation command_args cmd_brace_block */ -#line 2028 "mrbgems/mruby-compiler/core/parse.y" +#line 2387 "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 7276 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7637 "mrbgems/mruby-compiler/core/y.tab.c" break; case 67: /* command: primary_value call_op operation2 command_args */ -#line 2033 "mrbgems/mruby-compiler/core/parse.y" +#line 2392 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), (yyvsp[-2].num)); } -#line 7284 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7645 "mrbgems/mruby-compiler/core/y.tab.c" break; case 68: /* command: primary_value call_op operation2 command_args cmd_brace_block */ -#line 2037 "mrbgems/mruby-compiler/core/parse.y" +#line 2396 "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 7293 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7654 "mrbgems/mruby-compiler/core/y.tab.c" break; case 69: /* command: primary_value "::" operation2 command_args */ -#line 2042 "mrbgems/mruby-compiler/core/parse.y" +#line 2401 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), tCOLON2); } -#line 7301 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7662 "mrbgems/mruby-compiler/core/y.tab.c" break; case 70: /* command: primary_value "::" operation2 command_args cmd_brace_block */ -#line 2046 "mrbgems/mruby-compiler/core/parse.y" +#line 2405 "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 7310 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7671 "mrbgems/mruby-compiler/core/y.tab.c" break; case 71: /* command: "'super'" command_args */ -#line 2051 "mrbgems/mruby-compiler/core/parse.y" +#line 2410 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_super(p, (yyvsp[0].nd)); } -#line 7318 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7679 "mrbgems/mruby-compiler/core/y.tab.c" break; case 72: /* command: "'yield'" command_args */ -#line 2055 "mrbgems/mruby-compiler/core/parse.y" +#line 2414 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_yield(p, (yyvsp[0].nd)); } -#line 7326 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7687 "mrbgems/mruby-compiler/core/y.tab.c" break; case 73: /* command: "'return'" call_args */ -#line 2059 "mrbgems/mruby-compiler/core/parse.y" +#line 2418 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_return(p, ret_args(p, (yyvsp[0].nd))); } -#line 7334 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7695 "mrbgems/mruby-compiler/core/y.tab.c" break; case 74: /* command: "'break'" call_args */ -#line 2063 "mrbgems/mruby-compiler/core/parse.y" +#line 2422 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_break(p, ret_args(p, (yyvsp[0].nd))); } -#line 7342 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7703 "mrbgems/mruby-compiler/core/y.tab.c" break; case 75: /* command: "'next'" call_args */ -#line 2067 "mrbgems/mruby-compiler/core/parse.y" +#line 2426 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_next(p, ret_args(p, (yyvsp[0].nd))); } -#line 7350 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7711 "mrbgems/mruby-compiler/core/y.tab.c" break; case 76: /* mlhs: mlhs_basic */ -#line 2073 "mrbgems/mruby-compiler/core/parse.y" +#line 2432 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 7358 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 77: /* mlhs: tLPAREN mlhs_inner rparen */ -#line 2077 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = (yyvsp[-1].nd); - } -#line 7366 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 79: /* mlhs_inner: tLPAREN mlhs_inner rparen */ -#line 2084 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = (yyvsp[-1].nd); - } -#line 7374 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 80: /* mlhs_basic: mlhs_list */ -#line 2090 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list1((yyvsp[0].nd)); - } -#line 7382 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 81: /* mlhs_basic: mlhs_list mlhs_item */ -#line 2094 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list1(push((yyvsp[-1].nd),(yyvsp[0].nd))); - } -#line 7390 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 82: /* mlhs_basic: mlhs_list "*" mlhs_node */ -#line 2098 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list2((yyvsp[-2].nd), (yyvsp[0].nd)); - } -#line 7398 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 83: /* mlhs_basic: mlhs_list "*" mlhs_node ',' mlhs_post */ -#line 2102 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list3((yyvsp[-4].nd), (yyvsp[-2].nd), (yyvsp[0].nd)); - } -#line 7406 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 84: /* mlhs_basic: mlhs_list "*" */ -#line 2106 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list2((yyvsp[-1].nd), new_nil(p)); - } -#line 7414 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 85: /* mlhs_basic: mlhs_list "*" ',' mlhs_post */ -#line 2110 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list3((yyvsp[-3].nd), new_nil(p), (yyvsp[0].nd)); - } -#line 7422 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 86: /* mlhs_basic: "*" mlhs_node */ -#line 2114 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list2(0, (yyvsp[0].nd)); - } -#line 7430 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 87: /* mlhs_basic: "*" mlhs_node ',' mlhs_post */ -#line 2118 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list3(0, (yyvsp[-2].nd), (yyvsp[0].nd)); - } -#line 7438 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 88: /* mlhs_basic: "*" */ -#line 2122 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list2(0, new_nil(p)); - } -#line 7446 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 89: /* mlhs_basic: "*" ',' mlhs_post */ -#line 2126 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list3(0, new_nil(p), (yyvsp[0].nd)); - } -#line 7454 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 91: /* mlhs_item: tLPAREN mlhs_inner rparen */ -#line 2133 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_masgn(p, (yyvsp[-1].nd), NULL); - } -#line 7462 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 92: /* mlhs_list: mlhs_item ',' */ -#line 2139 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list1((yyvsp[-1].nd)); - } -#line 7470 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 93: /* mlhs_list: mlhs_list mlhs_item ',' */ -#line 2143 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[-1].nd)); - } -#line 7478 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 94: /* mlhs_post: mlhs_item */ -#line 2149 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list1((yyvsp[0].nd)); - } -#line 7486 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 95: /* mlhs_post: mlhs_list mlhs_item */ -#line 2153 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = push((yyvsp[-1].nd), (yyvsp[0].nd)); - } -#line 7494 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 96: /* mlhs_node: variable */ -#line 2159 "mrbgems/mruby-compiler/core/parse.y" - { - assignable(p, (yyvsp[0].nd)); - } -#line 7502 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 97: /* mlhs_node: primary_value '[' opt_call_args ']' */ -#line 2163 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_call(p, (yyvsp[-3].nd), intern_op(aref), (yyvsp[-1].nd), '.'); - } -#line 7510 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 98: /* mlhs_node: primary_value call_op "local variable or method" */ -#line 2167 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); - } -#line 7518 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 99: /* mlhs_node: primary_value "::" "local variable or method" */ -#line 2171 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, tCOLON2); - } -#line 7526 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 100: /* mlhs_node: primary_value call_op "constant" */ -#line 2175 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); - } -#line 7534 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 101: /* mlhs_node: primary_value "::" "constant" */ -#line 2179 "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 7544 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 102: /* mlhs_node: tCOLON3 "constant" */ -#line 2185 "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 7554 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 103: /* mlhs_node: backref */ -#line 2191 "mrbgems/mruby-compiler/core/parse.y" - { - backref_error(p, (yyvsp[0].nd)); - (yyval.nd) = 0; - } -#line 7563 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 104: /* lhs: variable */ -#line 2198 "mrbgems/mruby-compiler/core/parse.y" - { - assignable(p, (yyvsp[0].nd)); - } -#line 7571 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 105: /* lhs: primary_value '[' opt_call_args ']' */ -#line 2202 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_call(p, (yyvsp[-3].nd), intern_op(aref), (yyvsp[-1].nd), '.'); - } -#line 7579 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 106: /* lhs: primary_value call_op "local variable or method" */ -#line 2206 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); - } -#line 7587 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 107: /* lhs: primary_value "::" "local variable or method" */ -#line 2210 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, tCOLON2); - } -#line 7595 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 108: /* lhs: primary_value call_op "constant" */ -#line 2214 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); - } -#line 7603 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 109: /* lhs: primary_value "::" "constant" */ -#line 2218 "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 7613 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 110: /* lhs: tCOLON3 "constant" */ -#line 2224 "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 7623 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 111: /* lhs: backref */ -#line 2230 "mrbgems/mruby-compiler/core/parse.y" - { - backref_error(p, (yyvsp[0].nd)); - (yyval.nd) = 0; - } -#line 7632 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 112: /* lhs: "numbered parameter" */ -#line 2235 "mrbgems/mruby-compiler/core/parse.y" - { - yyerror(&(yylsp[0]), p, "can't assign to numbered parameter"); - } -#line 7640 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 113: /* cname: "local variable or method" */ -#line 2241 "mrbgems/mruby-compiler/core/parse.y" - { - yyerror(&(yylsp[0]), p, "class/module name must be CONSTANT"); - } -#line 7648 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 115: /* cpath: tCOLON3 cname */ -#line 2248 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = cons(nint(1), nsym((yyvsp[0].id))); - } -#line 7656 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 116: /* cpath: cname */ -#line 2252 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = cons(nint(0), nsym((yyvsp[0].id))); - } -#line 7664 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 117: /* cpath: primary_value "::" cname */ -#line 2256 "mrbgems/mruby-compiler/core/parse.y" - { - void_expr_error(p, (yyvsp[-2].nd)); - (yyval.nd) = cons((yyvsp[-2].nd), nsym((yyvsp[0].id))); - } -#line 7673 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 121: /* fname: op */ -#line 2266 "mrbgems/mruby-compiler/core/parse.y" - { - p->lstate = EXPR_ENDFN; - (yyval.id) = (yyvsp[0].id); - } -#line 7682 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 122: /* fname: reswords */ -#line 2271 "mrbgems/mruby-compiler/core/parse.y" - { - p->lstate = EXPR_ENDFN; - (yyval.id) = (yyvsp[0].id); - } -#line 7691 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 125: /* undef_list: fsym */ -#line 2282 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_undef(p, (yyvsp[0].id)); - } -#line 7699 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 126: /* $@6: %empty */ -#line 2285 "mrbgems/mruby-compiler/core/parse.y" - {p->lstate = EXPR_FNAME;} -#line 7705 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 127: /* undef_list: undef_list ',' $@6 fsym */ -#line 2286 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = push((yyvsp[-3].nd), nsym((yyvsp[0].id))); - } -#line 7713 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 128: /* op: '|' */ -#line 2291 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(or); } #line 7719 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 129: /* op: '^' */ -#line 2292 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(xor); } -#line 7725 "mrbgems/mruby-compiler/core/y.tab.c" + case 77: /* mlhs: tLPAREN mlhs_inner rparen */ +#line 2436 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = (yyvsp[-1].nd); + } +#line 7727 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 130: /* op: '&' */ -#line 2293 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(and); } -#line 7731 "mrbgems/mruby-compiler/core/y.tab.c" + case 79: /* mlhs_inner: tLPAREN mlhs_inner rparen */ +#line 2443 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = (yyvsp[-1].nd); + } +#line 7735 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 131: /* op: "<=>" */ -#line 2294 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(cmp); } -#line 7737 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 132: /* op: "==" */ -#line 2295 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(eq); } + case 80: /* mlhs_basic: mlhs_list */ +#line 2449 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list1((yyvsp[0].nd)); + } #line 7743 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 133: /* op: "===" */ -#line 2296 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(eqq); } -#line 7749 "mrbgems/mruby-compiler/core/y.tab.c" + case 81: /* mlhs_basic: mlhs_list mlhs_item */ +#line 2453 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list1(push((yyvsp[-1].nd),(yyvsp[0].nd))); + } +#line 7751 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 134: /* op: "=~" */ -#line 2297 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(match); } -#line 7755 "mrbgems/mruby-compiler/core/y.tab.c" + case 82: /* mlhs_basic: mlhs_list "*" mlhs_node */ +#line 2457 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list2((yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 7759 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 135: /* op: "!~" */ -#line 2298 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(nmatch); } -#line 7761 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 136: /* op: '>' */ -#line 2299 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(gt); } + case 83: /* mlhs_basic: mlhs_list "*" mlhs_node ',' mlhs_post */ +#line 2461 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list3((yyvsp[-4].nd), (yyvsp[-2].nd), (yyvsp[0].nd)); + } #line 7767 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 137: /* op: ">=" */ -#line 2300 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(ge); } -#line 7773 "mrbgems/mruby-compiler/core/y.tab.c" + case 84: /* mlhs_basic: mlhs_list "*" */ +#line 2465 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list2((yyvsp[-1].nd), new_nil(p)); + } +#line 7775 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 138: /* op: '<' */ -#line 2301 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(lt); } -#line 7779 "mrbgems/mruby-compiler/core/y.tab.c" + case 85: /* mlhs_basic: mlhs_list "*" ',' mlhs_post */ +#line 2469 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list3((yyvsp[-3].nd), new_nil(p), (yyvsp[0].nd)); + } +#line 7783 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 139: /* op: "<=" */ -#line 2302 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(le); } -#line 7785 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 140: /* op: "!=" */ -#line 2303 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(neq); } + case 86: /* mlhs_basic: "*" mlhs_node */ +#line 2473 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list2(0, (yyvsp[0].nd)); + } #line 7791 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 141: /* op: "<<" */ -#line 2304 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(lshift); } -#line 7797 "mrbgems/mruby-compiler/core/y.tab.c" + case 87: /* mlhs_basic: "*" mlhs_node ',' mlhs_post */ +#line 2477 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list3(0, (yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 7799 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 142: /* op: ">>" */ -#line 2305 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(rshift); } -#line 7803 "mrbgems/mruby-compiler/core/y.tab.c" + case 88: /* mlhs_basic: "*" */ +#line 2481 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list2(0, new_nil(p)); + } +#line 7807 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 143: /* op: '+' */ -#line 2306 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(add); } -#line 7809 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 144: /* op: '-' */ -#line 2307 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(sub); } + case 89: /* mlhs_basic: "*" ',' mlhs_post */ +#line 2485 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list3(0, new_nil(p), (yyvsp[0].nd)); + } #line 7815 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 145: /* op: '*' */ -#line 2308 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(mul); } -#line 7821 "mrbgems/mruby-compiler/core/y.tab.c" + case 91: /* mlhs_item: tLPAREN mlhs_inner rparen */ +#line 2492 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_masgn(p, (yyvsp[-1].nd), NULL); + } +#line 7823 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 146: /* op: "*" */ -#line 2309 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(mul); } -#line 7827 "mrbgems/mruby-compiler/core/y.tab.c" + case 92: /* mlhs_list: mlhs_item ',' */ +#line 2498 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list1((yyvsp[-1].nd)); + } +#line 7831 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 147: /* op: '/' */ -#line 2310 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(div); } -#line 7833 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 148: /* op: '%' */ -#line 2311 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(mod); } + case 93: /* mlhs_list: mlhs_list mlhs_item ',' */ +#line 2502 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[-1].nd)); + } #line 7839 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 149: /* op: tPOW */ -#line 2312 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(pow); } -#line 7845 "mrbgems/mruby-compiler/core/y.tab.c" + case 94: /* mlhs_post: mlhs_item */ +#line 2508 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list1((yyvsp[0].nd)); + } +#line 7847 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 150: /* op: "**" */ -#line 2313 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(pow); } -#line 7851 "mrbgems/mruby-compiler/core/y.tab.c" + case 95: /* mlhs_post: mlhs_list mlhs_item */ +#line 2512 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = push((yyvsp[-1].nd), (yyvsp[0].nd)); + } +#line 7855 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 151: /* op: '!' */ -#line 2314 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(not); } -#line 7857 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 152: /* op: '~' */ -#line 2315 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(neg); } + case 96: /* mlhs_node: variable */ +#line 2518 "mrbgems/mruby-compiler/core/parse.y" + { + assignable(p, (yyvsp[0].nd)); + } #line 7863 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 153: /* op: "unary plus" */ -#line 2316 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(plus); } -#line 7869 "mrbgems/mruby-compiler/core/y.tab.c" + case 97: /* mlhs_node: primary_value '[' opt_call_args ']' */ +#line 2522 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_call(p, (yyvsp[-3].nd), intern_op(aref), (yyvsp[-1].nd), '.'); + } +#line 7871 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 154: /* op: "unary minus" */ -#line 2317 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(minus); } -#line 7875 "mrbgems/mruby-compiler/core/y.tab.c" + case 98: /* mlhs_node: primary_value call_op "local variable or method" */ +#line 2526 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); + } +#line 7879 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 155: /* op: tAREF */ -#line 2318 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(aref); } -#line 7881 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 156: /* op: tASET */ -#line 2319 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(aset); } + case 99: /* mlhs_node: primary_value "::" "local variable or method" */ +#line 2530 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, tCOLON2); + } #line 7887 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 157: /* op: '`' */ -#line 2320 "mrbgems/mruby-compiler/core/parse.y" - { (yyval.id) = intern_op(tick); } -#line 7893 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 198: /* arg: lhs '=' arg_rhs */ -#line 2338 "mrbgems/mruby-compiler/core/parse.y" + case 100: /* mlhs_node: primary_value call_op "constant" */ +#line 2534 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_asgn(p, (yyvsp[-2].nd), (yyvsp[0].nd)); + (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); } -#line 7901 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7895 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 199: /* arg: var_lhs tOP_ASGN arg_rhs */ -#line 2342 "mrbgems/mruby-compiler/core/parse.y" + case 101: /* mlhs_node: primary_value "::" "constant" */ +#line 2538 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_op_asgn(p, (yyvsp[-2].nd), (yyvsp[-1].id), (yyvsp[0].nd)); + 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 7909 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7905 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 200: /* arg: primary_value '[' opt_call_args ']' tOP_ASGN arg_rhs */ -#line 2346 "mrbgems/mruby-compiler/core/parse.y" + case 102: /* mlhs_node: tCOLON3 "constant" */ +#line 2544 "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)); + if (p->in_def || p->in_single) + yyerror(&(yylsp[-1]), p, "dynamic constant assignment"); + (yyval.nd) = new_colon3(p, (yyvsp[0].id)); } -#line 7917 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7915 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 201: /* arg: primary_value call_op "local variable or method" tOP_ASGN arg_rhs */ -#line 2350 "mrbgems/mruby-compiler/core/parse.y" + case 103: /* mlhs_node: backref */ +#line 2550 "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)); + backref_error(p, (yyvsp[0].nd)); + (yyval.nd) = 0; } -#line 7925 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7924 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 202: /* arg: primary_value call_op "constant" tOP_ASGN arg_rhs */ -#line 2354 "mrbgems/mruby-compiler/core/parse.y" + case 104: /* lhs: variable */ +#line 2557 "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)); + assignable(p, (yyvsp[0].nd)); } -#line 7933 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7932 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 203: /* arg: primary_value "::" "local variable or method" tOP_ASGN arg_rhs */ -#line 2358 "mrbgems/mruby-compiler/core/parse.y" + case 105: /* lhs: primary_value '[' opt_call_args ']' */ +#line 2561 "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)); + (yyval.nd) = new_call(p, (yyvsp[-3].nd), intern_op(aref), (yyvsp[-1].nd), '.'); } -#line 7941 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7940 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 204: /* arg: primary_value "::" "constant" tOP_ASGN arg_rhs */ -#line 2362 "mrbgems/mruby-compiler/core/parse.y" + case 106: /* lhs: primary_value call_op "local variable or method" */ +#line 2565 "mrbgems/mruby-compiler/core/parse.y" { - yyerror(&(yylsp[-4]), p, "constant re-assignment"); - (yyval.nd) = new_stmts(p, 0); + (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); } -#line 7950 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7948 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 205: /* arg: tCOLON3 "constant" tOP_ASGN arg_rhs */ -#line 2367 "mrbgems/mruby-compiler/core/parse.y" + case 107: /* lhs: primary_value "::" "local variable or method" */ +#line 2569 "mrbgems/mruby-compiler/core/parse.y" { - yyerror(&(yylsp[-3]), p, "constant re-assignment"); - (yyval.nd) = new_stmts(p, 0); + (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, tCOLON2); } -#line 7959 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7956 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 206: /* arg: backref tOP_ASGN arg_rhs */ -#line 2372 "mrbgems/mruby-compiler/core/parse.y" + case 108: /* lhs: primary_value call_op "constant" */ +#line 2573 "mrbgems/mruby-compiler/core/parse.y" { - backref_error(p, (yyvsp[-2].nd)); - (yyval.nd) = new_stmts(p, 0); + (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, (yyvsp[-1].num)); } -#line 7968 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7964 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 207: /* arg: arg ".." arg */ -#line 2377 "mrbgems/mruby-compiler/core/parse.y" + case 109: /* lhs: primary_value "::" "constant" */ +#line 2577 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_dot2(p, (yyvsp[-2].nd), (yyvsp[0].nd)); + 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 7976 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7974 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 208: /* arg: arg ".." */ -#line 2381 "mrbgems/mruby-compiler/core/parse.y" + case 110: /* lhs: tCOLON3 "constant" */ +#line 2583 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_dot2(p, (yyvsp[-1].nd), new_nil(p)); + if (p->in_def || p->in_single) + yyerror(&(yylsp[-1]), p, "dynamic constant assignment"); + (yyval.nd) = new_colon3(p, (yyvsp[0].id)); } #line 7984 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 209: /* arg: tBDOT2 arg */ -#line 2385 "mrbgems/mruby-compiler/core/parse.y" + case 111: /* lhs: backref */ +#line 2589 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_dot2(p, new_nil(p), (yyvsp[0].nd)); + backref_error(p, (yyvsp[0].nd)); + (yyval.nd) = 0; } -#line 7992 "mrbgems/mruby-compiler/core/y.tab.c" +#line 7993 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 210: /* arg: arg "..." arg */ -#line 2389 "mrbgems/mruby-compiler/core/parse.y" + case 112: /* lhs: "numbered parameter" */ +#line 2594 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_dot3(p, (yyvsp[-2].nd), (yyvsp[0].nd)); + yyerror(&(yylsp[0]), p, "can't assign to numbered parameter"); } -#line 8000 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8001 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 211: /* arg: arg "..." */ -#line 2393 "mrbgems/mruby-compiler/core/parse.y" + case 113: /* cname: "local variable or method" */ +#line 2600 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_dot3(p, (yyvsp[-1].nd), new_nil(p)); + yyerror(&(yylsp[0]), p, "class/module name must be CONSTANT"); } -#line 8008 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8009 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 212: /* arg: tBDOT3 arg */ -#line 2397 "mrbgems/mruby-compiler/core/parse.y" + case 115: /* cpath: tCOLON3 cname */ +#line 2607 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_dot3(p, new_nil(p), (yyvsp[0].nd)); + (yyval.nd) = cons(int_to_node(1), sym_to_node((yyvsp[0].id))); } -#line 8016 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8017 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 213: /* arg: arg '+' arg */ -#line 2401 "mrbgems/mruby-compiler/core/parse.y" + case 116: /* cpath: cname */ +#line 2611 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "+", (yyvsp[0].nd)); + (yyval.nd) = cons(int_to_node(0), sym_to_node((yyvsp[0].id))); } -#line 8024 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8025 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 214: /* arg: arg '-' arg */ -#line 2405 "mrbgems/mruby-compiler/core/parse.y" + case 117: /* cpath: primary_value "::" cname */ +#line 2615 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "-", (yyvsp[0].nd)); + void_expr_error(p, (yyvsp[-2].nd)); + (yyval.nd) = cons((yyvsp[-2].nd), sym_to_node((yyvsp[0].id))); } -#line 8032 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8034 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 215: /* arg: arg '*' arg */ -#line 2409 "mrbgems/mruby-compiler/core/parse.y" + case 121: /* fname: op */ +#line 2625 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "*", (yyvsp[0].nd)); + p->lstate = EXPR_ENDFN; + (yyval.id) = (yyvsp[0].id); } -#line 8040 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8043 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 216: /* arg: arg '/' arg */ -#line 2413 "mrbgems/mruby-compiler/core/parse.y" + case 122: /* fname: reswords */ +#line 2630 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "/", (yyvsp[0].nd)); + p->lstate = EXPR_ENDFN; + (yyval.id) = (yyvsp[0].id); } -#line 8048 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8052 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 217: /* arg: arg '%' arg */ -#line 2417 "mrbgems/mruby-compiler/core/parse.y" + case 125: /* undef_list: fsym */ +#line 2641 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "%", (yyvsp[0].nd)); + (yyval.nd) = cons(sym_to_node((yyvsp[0].id)), 0); } -#line 8056 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8060 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 218: /* arg: arg tPOW arg */ -#line 2421 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "**", (yyvsp[0].nd)); - } -#line 8064 "mrbgems/mruby-compiler/core/y.tab.c" + case 126: /* $@6: %empty */ +#line 2644 "mrbgems/mruby-compiler/core/parse.y" + {p->lstate = EXPR_FNAME;} +#line 8066 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 219: /* arg: tUMINUS_NUM "integer literal" tPOW arg */ -#line 2425 "mrbgems/mruby-compiler/core/parse.y" + case 127: /* undef_list: undef_list ',' $@6 fsym */ +#line 2645 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_negate(p, call_bin_op(p, (yyvsp[-2].nd), "**", (yyvsp[0].nd))); + (yyval.nd) = push((yyvsp[-3].nd), sym_to_node((yyvsp[0].id))); } -#line 8072 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8074 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 220: /* arg: tUMINUS_NUM "float literal" tPOW arg */ -#line 2429 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_negate(p, call_bin_op(p, (yyvsp[-2].nd), "**", (yyvsp[0].nd))); - } + case 128: /* op: '|' */ +#line 2650 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(or); } #line 8080 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 221: /* arg: "unary plus" arg */ -#line 2433 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_uni_op(p, (yyvsp[0].nd), "+@"); - } -#line 8088 "mrbgems/mruby-compiler/core/y.tab.c" + case 129: /* op: '^' */ +#line 2651 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(xor); } +#line 8086 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 222: /* arg: "unary minus" arg */ -#line 2437 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_negate(p, (yyvsp[0].nd)); - } -#line 8096 "mrbgems/mruby-compiler/core/y.tab.c" + case 130: /* op: '&' */ +#line 2652 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(and); } +#line 8092 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 223: /* arg: arg '|' arg */ -#line 2441 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "|", (yyvsp[0].nd)); - } + case 131: /* op: "<=>" */ +#line 2653 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(cmp); } +#line 8098 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 132: /* op: "==" */ +#line 2654 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(eq); } #line 8104 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 224: /* arg: arg '^' arg */ -#line 2445 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "^", (yyvsp[0].nd)); - } -#line 8112 "mrbgems/mruby-compiler/core/y.tab.c" + case 133: /* op: "===" */ +#line 2655 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(eqq); } +#line 8110 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 225: /* arg: arg '&' arg */ -#line 2449 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "&", (yyvsp[0].nd)); - } -#line 8120 "mrbgems/mruby-compiler/core/y.tab.c" + case 134: /* op: "=~" */ +#line 2656 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(match); } +#line 8116 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 226: /* arg: arg "<=>" arg */ -#line 2453 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<=>", (yyvsp[0].nd)); - } + case 135: /* op: "!~" */ +#line 2657 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(nmatch); } +#line 8122 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 136: /* op: '>' */ +#line 2658 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(gt); } #line 8128 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 227: /* arg: arg '>' arg */ -#line 2457 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), ">", (yyvsp[0].nd)); - } -#line 8136 "mrbgems/mruby-compiler/core/y.tab.c" + case 137: /* op: ">=" */ +#line 2659 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(ge); } +#line 8134 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 228: /* arg: arg ">=" arg */ -#line 2461 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), ">=", (yyvsp[0].nd)); - } -#line 8144 "mrbgems/mruby-compiler/core/y.tab.c" + case 138: /* op: '<' */ +#line 2660 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(lt); } +#line 8140 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 229: /* arg: arg '<' arg */ -#line 2465 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<", (yyvsp[0].nd)); - } + case 139: /* op: "<=" */ +#line 2661 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(le); } +#line 8146 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 140: /* op: "!=" */ +#line 2662 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(neq); } #line 8152 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 230: /* arg: arg "<=" arg */ -#line 2469 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<=", (yyvsp[0].nd)); - } -#line 8160 "mrbgems/mruby-compiler/core/y.tab.c" + case 141: /* op: "<<" */ +#line 2663 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(lshift); } +#line 8158 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 231: /* arg: arg "==" arg */ -#line 2473 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "==", (yyvsp[0].nd)); - } -#line 8168 "mrbgems/mruby-compiler/core/y.tab.c" + case 142: /* op: ">>" */ +#line 2664 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(rshift); } +#line 8164 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 232: /* arg: arg "===" arg */ -#line 2477 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "===", (yyvsp[0].nd)); - } + case 143: /* op: '+' */ +#line 2665 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(add); } +#line 8170 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 144: /* op: '-' */ +#line 2666 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(sub); } #line 8176 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 233: /* arg: arg "!=" arg */ -#line 2481 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "!=", (yyvsp[0].nd)); - } -#line 8184 "mrbgems/mruby-compiler/core/y.tab.c" + case 145: /* op: '*' */ +#line 2667 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(mul); } +#line 8182 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 234: /* arg: arg "=~" arg */ -#line 2485 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "=~", (yyvsp[0].nd)); - } -#line 8192 "mrbgems/mruby-compiler/core/y.tab.c" + case 146: /* op: "*" */ +#line 2668 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(mul); } +#line 8188 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 235: /* arg: arg "!~" arg */ -#line 2489 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "!~", (yyvsp[0].nd)); - } + case 147: /* op: '/' */ +#line 2669 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(div); } +#line 8194 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 148: /* op: '%' */ +#line 2670 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(mod); } #line 8200 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 236: /* arg: '!' arg */ -#line 2493 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_uni_op(p, cond((yyvsp[0].nd)), "!"); - } -#line 8208 "mrbgems/mruby-compiler/core/y.tab.c" + case 149: /* op: tPOW */ +#line 2671 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(pow); } +#line 8206 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 237: /* arg: '~' arg */ -#line 2497 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_uni_op(p, cond((yyvsp[0].nd)), "~"); - } -#line 8216 "mrbgems/mruby-compiler/core/y.tab.c" + case 150: /* op: "**" */ +#line 2672 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(pow); } +#line 8212 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 238: /* arg: arg "<<" arg */ -#line 2501 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<<", (yyvsp[0].nd)); - } + case 151: /* op: '!' */ +#line 2673 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(not); } +#line 8218 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 152: /* op: '~' */ +#line 2674 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(neg); } #line 8224 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 239: /* arg: arg ">>" arg */ -#line 2505 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), ">>", (yyvsp[0].nd)); - } -#line 8232 "mrbgems/mruby-compiler/core/y.tab.c" + case 153: /* op: "unary plus" */ +#line 2675 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(plus); } +#line 8230 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 240: /* arg: arg "&&" arg */ -#line 2509 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_and(p, (yyvsp[-2].nd), (yyvsp[0].nd)); - } -#line 8240 "mrbgems/mruby-compiler/core/y.tab.c" + case 154: /* op: "unary minus" */ +#line 2676 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(minus); } +#line 8236 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 241: /* arg: arg "||" arg */ -#line 2513 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_or(p, (yyvsp[-2].nd), (yyvsp[0].nd)); - } + case 155: /* op: tAREF */ +#line 2677 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(aref); } +#line 8242 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 156: /* op: tASET */ +#line 2678 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(aset); } #line 8248 "mrbgems/mruby-compiler/core/y.tab.c" break; + case 157: /* op: '`' */ +#line 2679 "mrbgems/mruby-compiler/core/parse.y" + { (yyval.id) = intern_op(tick); } +#line 8254 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 198: /* arg: lhs '=' arg_rhs */ +#line 2697 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_asgn(p, (yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 8262 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 199: /* arg: var_lhs tOP_ASGN arg_rhs */ +#line 2701 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_op_asgn(p, (yyvsp[-2].nd), (yyvsp[-1].id), (yyvsp[0].nd)); + } +#line 8270 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 200: /* arg: primary_value '[' opt_call_args ']' tOP_ASGN arg_rhs */ +#line 2705 "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 8278 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 201: /* arg: primary_value call_op "local variable or method" tOP_ASGN arg_rhs */ +#line 2709 "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 8286 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 202: /* arg: primary_value call_op "constant" tOP_ASGN arg_rhs */ +#line 2713 "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 8294 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 203: /* arg: primary_value "::" "local variable or method" tOP_ASGN arg_rhs */ +#line 2717 "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 8302 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 204: /* arg: primary_value "::" "constant" tOP_ASGN arg_rhs */ +#line 2721 "mrbgems/mruby-compiler/core/parse.y" + { + yyerror(&(yylsp[-4]), p, "constant re-assignment"); + (yyval.nd) = new_stmts(p, 0); + } +#line 8311 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 205: /* arg: tCOLON3 "constant" tOP_ASGN arg_rhs */ +#line 2726 "mrbgems/mruby-compiler/core/parse.y" + { + yyerror(&(yylsp[-3]), p, "constant re-assignment"); + (yyval.nd) = new_stmts(p, 0); + } +#line 8320 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 206: /* arg: backref tOP_ASGN arg_rhs */ +#line 2731 "mrbgems/mruby-compiler/core/parse.y" + { + backref_error(p, (yyvsp[-2].nd)); + (yyval.nd) = new_stmts(p, 0); + } +#line 8329 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 207: /* arg: arg ".." arg */ +#line 2736 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_dot2(p, (yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 8337 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 208: /* arg: arg ".." */ +#line 2740 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_dot2(p, (yyvsp[-1].nd), new_nil(p)); + } +#line 8345 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 209: /* arg: tBDOT2 arg */ +#line 2744 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_dot2(p, new_nil(p), (yyvsp[0].nd)); + } +#line 8353 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 210: /* arg: arg "..." arg */ +#line 2748 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_dot3(p, (yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 8361 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 211: /* arg: arg "..." */ +#line 2752 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_dot3(p, (yyvsp[-1].nd), new_nil(p)); + } +#line 8369 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 212: /* arg: tBDOT3 arg */ +#line 2756 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_dot3(p, new_nil(p), (yyvsp[0].nd)); + } +#line 8377 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 213: /* arg: arg '+' arg */ +#line 2760 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "+", (yyvsp[0].nd)); + } +#line 8385 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 214: /* arg: arg '-' arg */ +#line 2764 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "-", (yyvsp[0].nd)); + } +#line 8393 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 215: /* arg: arg '*' arg */ +#line 2768 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "*", (yyvsp[0].nd)); + } +#line 8401 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 216: /* arg: arg '/' arg */ +#line 2772 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "/", (yyvsp[0].nd)); + } +#line 8409 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 217: /* arg: arg '%' arg */ +#line 2776 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "%", (yyvsp[0].nd)); + } +#line 8417 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 218: /* arg: arg tPOW arg */ +#line 2780 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "**", (yyvsp[0].nd)); + } +#line 8425 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 219: /* arg: tUMINUS_NUM "integer literal" tPOW arg */ +#line 2784 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_negate(p, call_bin_op(p, (yyvsp[-2].nd), "**", (yyvsp[0].nd))); + } +#line 8433 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 220: /* arg: tUMINUS_NUM "float literal" tPOW arg */ +#line 2788 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_negate(p, call_bin_op(p, (yyvsp[-2].nd), "**", (yyvsp[0].nd))); + } +#line 8441 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 221: /* arg: "unary plus" arg */ +#line 2792 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_uni_op(p, (yyvsp[0].nd), "+@"); + } +#line 8449 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 222: /* arg: "unary minus" arg */ +#line 2796 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_negate(p, (yyvsp[0].nd)); + } +#line 8457 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 223: /* arg: arg '|' arg */ +#line 2800 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "|", (yyvsp[0].nd)); + } +#line 8465 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 224: /* arg: arg '^' arg */ +#line 2804 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "^", (yyvsp[0].nd)); + } +#line 8473 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 225: /* arg: arg '&' arg */ +#line 2808 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "&", (yyvsp[0].nd)); + } +#line 8481 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 226: /* arg: arg "<=>" arg */ +#line 2812 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<=>", (yyvsp[0].nd)); + } +#line 8489 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 227: /* arg: arg '>' arg */ +#line 2816 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), ">", (yyvsp[0].nd)); + } +#line 8497 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 228: /* arg: arg ">=" arg */ +#line 2820 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), ">=", (yyvsp[0].nd)); + } +#line 8505 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 229: /* arg: arg '<' arg */ +#line 2824 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<", (yyvsp[0].nd)); + } +#line 8513 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 230: /* arg: arg "<=" arg */ +#line 2828 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<=", (yyvsp[0].nd)); + } +#line 8521 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 231: /* arg: arg "==" arg */ +#line 2832 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "==", (yyvsp[0].nd)); + } +#line 8529 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 232: /* arg: arg "===" arg */ +#line 2836 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "===", (yyvsp[0].nd)); + } +#line 8537 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 233: /* arg: arg "!=" arg */ +#line 2840 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "!=", (yyvsp[0].nd)); + } +#line 8545 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 234: /* arg: arg "=~" arg */ +#line 2844 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "=~", (yyvsp[0].nd)); + } +#line 8553 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 235: /* arg: arg "!~" arg */ +#line 2848 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "!~", (yyvsp[0].nd)); + } +#line 8561 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 236: /* arg: '!' arg */ +#line 2852 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_uni_op(p, cond((yyvsp[0].nd)), "!"); + } +#line 8569 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 237: /* arg: '~' arg */ +#line 2856 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_uni_op(p, cond((yyvsp[0].nd)), "~"); + } +#line 8577 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 238: /* arg: arg "<<" arg */ +#line 2860 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), "<<", (yyvsp[0].nd)); + } +#line 8585 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 239: /* arg: arg ">>" arg */ +#line 2864 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = call_bin_op(p, (yyvsp[-2].nd), ">>", (yyvsp[0].nd)); + } +#line 8593 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 240: /* arg: arg "&&" arg */ +#line 2868 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_and(p, (yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 8601 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 241: /* arg: arg "||" arg */ +#line 2872 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_or(p, (yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 8609 "mrbgems/mruby-compiler/core/y.tab.c" + break; + case 242: /* arg: arg '?' arg opt_nl ':' arg */ -#line 2517 "mrbgems/mruby-compiler/core/parse.y" +#line 2876 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[-5].nd)), (yyvsp[-3].nd), (yyvsp[0].nd)); } -#line 8256 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8617 "mrbgems/mruby-compiler/core/y.tab.c" break; case 243: /* arg: arg '?' arg opt_nl "label" arg */ -#line 2521 "mrbgems/mruby-compiler/core/parse.y" +#line 2880 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[-5].nd)), (yyvsp[-3].nd), (yyvsp[0].nd)); } -#line 8264 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8625 "mrbgems/mruby-compiler/core/y.tab.c" break; case 244: /* arg: defn_head f_opt_arglist_paren '=' arg */ -#line 2525 "mrbgems/mruby-compiler/core/parse.y" +#line 2884 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); endless_method_name(p, (yyvsp[-3].nd)); @@ -8273,11 +8634,11 @@ yyreduce: nvars_unnest(p); p->in_def--; } -#line 8277 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8638 "mrbgems/mruby-compiler/core/y.tab.c" break; case 245: /* arg: defn_head f_opt_arglist_paren '=' arg "'rescue' modifier" arg */ -#line 2534 "mrbgems/mruby-compiler/core/parse.y" +#line 2893 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-5].nd); endless_method_name(p, (yyvsp[-5].nd)); @@ -8286,116 +8647,114 @@ yyreduce: nvars_unnest(p); p->in_def--; } -#line 8290 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8651 "mrbgems/mruby-compiler/core/y.tab.c" break; case 246: /* arg: defs_head f_opt_arglist_paren '=' arg */ -#line 2543 "mrbgems/mruby-compiler/core/parse.y" +#line 2902 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); void_expr_error(p, (yyvsp[0].nd)); - defs_setup(p, (yyval.nd), (yyvsp[-2].nd), (yyvsp[0].nd)); + defn_setup(p, (yyval.nd), (yyvsp[-2].nd), (yyvsp[0].nd)); nvars_unnest(p); p->in_def--; p->in_single--; } -#line 8303 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8664 "mrbgems/mruby-compiler/core/y.tab.c" break; case 247: /* arg: defs_head f_opt_arglist_paren '=' arg "'rescue' modifier" arg */ -#line 2552 "mrbgems/mruby-compiler/core/parse.y" +#line 2911 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-5].nd); void_expr_error(p, (yyvsp[-2].nd)); - defs_setup(p, (yyval.nd), (yyvsp[-4].nd), new_mod_rescue(p, (yyvsp[-2].nd), (yyvsp[0].nd))); + defn_setup(p, (yyval.nd), (yyvsp[-4].nd), new_mod_rescue(p, (yyvsp[-2].nd), (yyvsp[0].nd))); nvars_unnest(p); p->in_def--; p->in_single--; } -#line 8316 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8677 "mrbgems/mruby-compiler/core/y.tab.c" break; case 248: /* arg: primary */ -#line 2561 "mrbgems/mruby-compiler/core/parse.y" +#line 2920 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 8324 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8685 "mrbgems/mruby-compiler/core/y.tab.c" break; case 250: /* aref_args: args trailer */ -#line 2568 "mrbgems/mruby-compiler/core/parse.y" +#line 2927 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); - NODE_LINENO((yyval.nd), (yyvsp[-1].nd)); } -#line 8333 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8693 "mrbgems/mruby-compiler/core/y.tab.c" break; case 251: /* aref_args: args comma assocs trailer */ -#line 2573 "mrbgems/mruby-compiler/core/parse.y" +#line 2931 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-3].nd), new_hash(p, (yyvsp[-1].nd))); } -#line 8341 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8701 "mrbgems/mruby-compiler/core/y.tab.c" break; case 252: /* aref_args: assocs trailer */ -#line 2577 "mrbgems/mruby-compiler/core/parse.y" +#line 2935 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = cons(new_kw_hash(p, (yyvsp[-1].nd)), 0); - NODE_LINENO((yyval.nd), (yyvsp[-1].nd)); + (yyval.nd) = cons(new_hash(p, (yyvsp[-1].nd)), 0); } -#line 8350 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8709 "mrbgems/mruby-compiler/core/y.tab.c" break; case 253: /* arg_rhs: arg */ -#line 2584 "mrbgems/mruby-compiler/core/parse.y" +#line 2941 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 8358 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8717 "mrbgems/mruby-compiler/core/y.tab.c" break; case 254: /* arg_rhs: arg "'rescue' modifier" arg */ -#line 2588 "mrbgems/mruby-compiler/core/parse.y" +#line 2945 "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 8367 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8726 "mrbgems/mruby-compiler/core/y.tab.c" break; case 255: /* paren_args: '(' opt_call_args ')' */ -#line 2595 "mrbgems/mruby-compiler/core/parse.y" +#line 2952 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 8375 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8734 "mrbgems/mruby-compiler/core/y.tab.c" break; case 256: /* paren_args: '(' args comma tBDOT3 rparen */ -#line 2599 "mrbgems/mruby-compiler/core/parse.y" +#line 2956 "mrbgems/mruby-compiler/core/parse.y" { mrb_sym r = intern_op(mul); mrb_sym k = intern_op(pow); mrb_sym b = intern_op(and); (yyval.nd) = new_callargs(p, push((yyvsp[-3].nd), new_splat(p, new_lvar(p, r))), - new_kw_hash(p, list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k)))), + list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k))), new_block_arg(p, new_lvar(p, b))); } -#line 8388 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8747 "mrbgems/mruby-compiler/core/y.tab.c" break; case 257: /* paren_args: '(' tBDOT3 rparen */ -#line 2608 "mrbgems/mruby-compiler/core/parse.y" +#line 2965 "mrbgems/mruby-compiler/core/parse.y" { mrb_sym r = intern_op(mul); mrb_sym k = intern_op(pow); mrb_sym b = intern_op(and); if (local_var_p(p, r) && local_var_p(p, k) && local_var_p(p, b)) { (yyval.nd) = new_callargs(p, list1(new_splat(p, new_lvar(p, r))), - new_kw_hash(p, list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k)))), + list1(cons(new_kw_rest_args(p, 0), new_lvar(p, k))), new_block_arg(p, new_lvar(p, b))); } else { @@ -8403,386 +8762,390 @@ yyreduce: (yyval.nd) = 0; } } -#line 8407 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8766 "mrbgems/mruby-compiler/core/y.tab.c" break; case 262: /* opt_call_args: args comma */ -#line 2631 "mrbgems/mruby-compiler/core/parse.y" +#line 2988 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_callargs(p,(yyvsp[-1].nd),0,0); - NODE_LINENO((yyval.nd), (yyvsp[-1].nd)); } -#line 8416 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8774 "mrbgems/mruby-compiler/core/y.tab.c" break; case 263: /* opt_call_args: args comma assocs comma */ -#line 2636 "mrbgems/mruby-compiler/core/parse.y" +#line 2992 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_callargs(p,(yyvsp[-3].nd),new_kw_hash(p,(yyvsp[-1].nd)),0); - NODE_LINENO((yyval.nd), (yyvsp[-3].nd)); + (yyval.nd) = new_callargs(p,(yyvsp[-3].nd),(yyvsp[-1].nd),0); } -#line 8425 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8782 "mrbgems/mruby-compiler/core/y.tab.c" break; case 264: /* opt_call_args: assocs comma */ -#line 2641 "mrbgems/mruby-compiler/core/parse.y" +#line 2996 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_callargs(p,0,new_kw_hash(p,(yyvsp[-1].nd)),0); - NODE_LINENO((yyval.nd), (yyvsp[-1].nd)); + (yyval.nd) = new_callargs(p,0,(yyvsp[-1].nd),0); } -#line 8434 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8790 "mrbgems/mruby-compiler/core/y.tab.c" break; case 265: /* call_args: command */ -#line 2648 "mrbgems/mruby-compiler/core/parse.y" +#line 3002 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = new_callargs(p, list1((yyvsp[0].nd)), 0, 0); - NODE_LINENO((yyval.nd), (yyvsp[0].nd)); } -#line 8444 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8799 "mrbgems/mruby-compiler/core/y.tab.c" break; case 266: /* call_args: args opt_block_arg */ -#line 2654 "mrbgems/mruby-compiler/core/parse.y" +#line 3007 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_callargs(p, (yyvsp[-1].nd), 0, (yyvsp[0].nd)); - NODE_LINENO((yyval.nd), (yyvsp[-1].nd)); } -#line 8453 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8807 "mrbgems/mruby-compiler/core/y.tab.c" break; case 267: /* call_args: assocs opt_block_arg */ -#line 2659 "mrbgems/mruby-compiler/core/parse.y" +#line 3011 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_callargs(p, 0, new_kw_hash(p, (yyvsp[-1].nd)), (yyvsp[0].nd)); - NODE_LINENO((yyval.nd), (yyvsp[-1].nd)); + (yyval.nd) = new_callargs(p, 0, (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 8462 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8815 "mrbgems/mruby-compiler/core/y.tab.c" break; case 268: /* call_args: args comma assocs opt_block_arg */ -#line 2664 "mrbgems/mruby-compiler/core/parse.y" +#line 3015 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_callargs(p, (yyvsp[-3].nd), new_kw_hash(p, (yyvsp[-1].nd)), (yyvsp[0].nd)); - NODE_LINENO((yyval.nd), (yyvsp[-3].nd)); + (yyval.nd) = new_callargs(p, (yyvsp[-3].nd), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 8471 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8823 "mrbgems/mruby-compiler/core/y.tab.c" break; case 269: /* call_args: block_arg */ -#line 2669 "mrbgems/mruby-compiler/core/parse.y" +#line 3019 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_callargs(p, 0, 0, (yyvsp[0].nd)); - NODE_LINENO((yyval.nd), (yyvsp[0].nd)); } -#line 8480 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8831 "mrbgems/mruby-compiler/core/y.tab.c" break; case 270: /* @7: %empty */ -#line 2675 "mrbgems/mruby-compiler/core/parse.y" +#line 3024 "mrbgems/mruby-compiler/core/parse.y" { (yyval.stack) = p->cmdarg_stack; CMDARG_PUSH(1); } -#line 8489 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8840 "mrbgems/mruby-compiler/core/y.tab.c" break; case 271: /* command_args: @7 call_args */ -#line 2680 "mrbgems/mruby-compiler/core/parse.y" +#line 3029 "mrbgems/mruby-compiler/core/parse.y" { p->cmdarg_stack = (yyvsp[-1].stack); (yyval.nd) = (yyvsp[0].nd); } -#line 8498 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8849 "mrbgems/mruby-compiler/core/y.tab.c" break; case 272: /* block_arg: "&" arg */ -#line 2687 "mrbgems/mruby-compiler/core/parse.y" +#line 3036 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_block_arg(p, (yyvsp[0].nd)); } -#line 8506 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8857 "mrbgems/mruby-compiler/core/y.tab.c" break; case 273: /* block_arg: "&" */ -#line 2691 "mrbgems/mruby-compiler/core/parse.y" +#line 3040 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_block_arg(p, 0); } -#line 8514 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8865 "mrbgems/mruby-compiler/core/y.tab.c" break; case 274: /* opt_block_arg: comma block_arg */ -#line 2697 "mrbgems/mruby-compiler/core/parse.y" +#line 3046 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 8522 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8873 "mrbgems/mruby-compiler/core/y.tab.c" break; case 275: /* opt_block_arg: none */ -#line 2701 "mrbgems/mruby-compiler/core/parse.y" +#line 3050 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 8530 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8881 "mrbgems/mruby-compiler/core/y.tab.c" break; case 277: /* args: arg */ -#line 2710 "mrbgems/mruby-compiler/core/parse.y" +#line 3059 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = list1((yyvsp[0].nd)); - NODE_LINENO((yyval.nd), (yyvsp[0].nd)); } -#line 8540 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8890 "mrbgems/mruby-compiler/core/y.tab.c" break; case 278: /* args: "*" */ -#line 2716 "mrbgems/mruby-compiler/core/parse.y" +#line 3064 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(new_splat(p, new_lvar(p, intern_op(mul)))); } -#line 8548 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8898 "mrbgems/mruby-compiler/core/y.tab.c" break; case 279: /* args: "*" arg */ -#line 2720 "mrbgems/mruby-compiler/core/parse.y" +#line 3068 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(new_splat(p, (yyvsp[0].nd))); - NODE_LINENO((yyval.nd), (yyvsp[0].nd)); } -#line 8557 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8906 "mrbgems/mruby-compiler/core/y.tab.c" break; case 280: /* args: args comma arg */ -#line 2725 "mrbgems/mruby-compiler/core/parse.y" +#line 3072 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 8566 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8915 "mrbgems/mruby-compiler/core/y.tab.c" break; case 281: /* args: args comma "*" */ -#line 2730 "mrbgems/mruby-compiler/core/parse.y" +#line 3077 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), new_splat(p, new_lvar(p, intern_op(mul)))); } -#line 8574 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8923 "mrbgems/mruby-compiler/core/y.tab.c" break; case 282: /* args: args comma "*" arg */ -#line 2734 "mrbgems/mruby-compiler/core/parse.y" +#line 3081 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-3].nd), new_splat(p, (yyvsp[0].nd))); } -#line 8582 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8931 "mrbgems/mruby-compiler/core/y.tab.c" break; case 283: /* mrhs: args comma arg */ -#line 2740 "mrbgems/mruby-compiler/core/parse.y" +#line 3087 "mrbgems/mruby-compiler/core/parse.y" { void_expr_error(p, (yyvsp[0].nd)); (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 8591 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8940 "mrbgems/mruby-compiler/core/y.tab.c" break; case 284: /* mrhs: args comma "*" arg */ -#line 2745 "mrbgems/mruby-compiler/core/parse.y" +#line 3092 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-3].nd), new_splat(p, (yyvsp[0].nd))); } -#line 8599 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8948 "mrbgems/mruby-compiler/core/y.tab.c" break; case 285: /* mrhs: "*" arg */ -#line 2749 "mrbgems/mruby-compiler/core/parse.y" +#line 3096 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(new_splat(p, (yyvsp[0].nd))); } -#line 8607 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8956 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 287: /* primary: string */ +#line 3103 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_str(p, (yyvsp[0].nd)); + } +#line 8964 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 288: /* primary: xstring */ +#line 3107 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_xstr(p, (yyvsp[0].nd)); + } +#line 8972 "mrbgems/mruby-compiler/core/y.tab.c" break; case 293: /* primary: "method" */ -#line 2762 "mrbgems/mruby-compiler/core/parse.y" +#line 3115 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_fcall(p, (yyvsp[0].id), 0); } -#line 8615 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8980 "mrbgems/mruby-compiler/core/y.tab.c" break; case 294: /* @8: %empty */ -#line 2766 "mrbgems/mruby-compiler/core/parse.y" +#line 3119 "mrbgems/mruby-compiler/core/parse.y" { (yyval.stack) = p->cmdarg_stack; p->cmdarg_stack = 0; } -#line 8624 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8989 "mrbgems/mruby-compiler/core/y.tab.c" break; case 295: /* primary: "'begin'" @8 bodystmt "'end'" */ -#line 2772 "mrbgems/mruby-compiler/core/parse.y" +#line 3125 "mrbgems/mruby-compiler/core/parse.y" { p->cmdarg_stack = (yyvsp[-2].stack); (yyval.nd) = new_begin(p, (yyvsp[-1].nd)); } -#line 8633 "mrbgems/mruby-compiler/core/y.tab.c" +#line 8998 "mrbgems/mruby-compiler/core/y.tab.c" break; case 296: /* @9: %empty */ -#line 2777 "mrbgems/mruby-compiler/core/parse.y" +#line 3130 "mrbgems/mruby-compiler/core/parse.y" { (yyval.stack) = p->cmdarg_stack; p->cmdarg_stack = 0; } -#line 8642 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9007 "mrbgems/mruby-compiler/core/y.tab.c" break; case 297: /* $@10: %empty */ -#line 2781 "mrbgems/mruby-compiler/core/parse.y" +#line 3134 "mrbgems/mruby-compiler/core/parse.y" {p->lstate = EXPR_ENDARG;} -#line 8648 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9013 "mrbgems/mruby-compiler/core/y.tab.c" break; case 298: /* primary: "(" @9 stmt $@10 rparen */ -#line 2782 "mrbgems/mruby-compiler/core/parse.y" +#line 3135 "mrbgems/mruby-compiler/core/parse.y" { p->cmdarg_stack = (yyvsp[-3].stack); (yyval.nd) = (yyvsp[-2].nd); } -#line 8657 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9022 "mrbgems/mruby-compiler/core/y.tab.c" break; case 299: /* $@11: %empty */ -#line 2786 "mrbgems/mruby-compiler/core/parse.y" +#line 3139 "mrbgems/mruby-compiler/core/parse.y" {p->lstate = EXPR_ENDARG;} -#line 8663 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9028 "mrbgems/mruby-compiler/core/y.tab.c" break; case 300: /* primary: "(" $@11 rparen */ -#line 2787 "mrbgems/mruby-compiler/core/parse.y" +#line 3140 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_nil(p); } -#line 8671 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9036 "mrbgems/mruby-compiler/core/y.tab.c" break; case 301: /* primary: tLPAREN compstmt ')' */ -#line 2791 "mrbgems/mruby-compiler/core/parse.y" +#line 3144 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 8679 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9044 "mrbgems/mruby-compiler/core/y.tab.c" break; case 302: /* primary: primary_value "::" "constant" */ -#line 2795 "mrbgems/mruby-compiler/core/parse.y" +#line 3148 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_colon2(p, (yyvsp[-2].nd), (yyvsp[0].id)); } -#line 8687 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9052 "mrbgems/mruby-compiler/core/y.tab.c" break; case 303: /* primary: tCOLON3 "constant" */ -#line 2799 "mrbgems/mruby-compiler/core/parse.y" +#line 3152 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_colon3(p, (yyvsp[0].id)); } -#line 8695 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9060 "mrbgems/mruby-compiler/core/y.tab.c" break; case 304: /* primary: "[" aref_args ']' */ -#line 2803 "mrbgems/mruby-compiler/core/parse.y" +#line 3156 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_array(p, (yyvsp[-1].nd)); - NODE_LINENO((yyval.nd), (yyvsp[-1].nd)); } -#line 8704 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9068 "mrbgems/mruby-compiler/core/y.tab.c" break; case 305: /* primary: tLBRACE assoc_list '}' */ -#line 2808 "mrbgems/mruby-compiler/core/parse.y" +#line 3160 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_hash(p, (yyvsp[-1].nd)); - NODE_LINENO((yyval.nd), (yyvsp[-1].nd)); } -#line 8713 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9076 "mrbgems/mruby-compiler/core/y.tab.c" break; case 306: /* primary: "'return'" */ -#line 2813 "mrbgems/mruby-compiler/core/parse.y" +#line 3164 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_return(p, 0); } -#line 8721 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9084 "mrbgems/mruby-compiler/core/y.tab.c" break; case 307: /* primary: "'yield'" opt_paren_args */ -#line 2817 "mrbgems/mruby-compiler/core/parse.y" +#line 3168 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_yield(p, (yyvsp[0].nd)); } -#line 8729 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9092 "mrbgems/mruby-compiler/core/y.tab.c" break; case 308: /* primary: "'not'" '(' expr rparen */ -#line 2821 "mrbgems/mruby-compiler/core/parse.y" +#line 3172 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, cond((yyvsp[-1].nd)), "!"); } -#line 8737 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9100 "mrbgems/mruby-compiler/core/y.tab.c" break; case 309: /* primary: "'not'" '(' rparen */ -#line 2825 "mrbgems/mruby-compiler/core/parse.y" +#line 3176 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = call_uni_op(p, new_nil(p), "!"); } -#line 8745 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9108 "mrbgems/mruby-compiler/core/y.tab.c" break; case 310: /* primary: operation brace_block */ -#line 2829 "mrbgems/mruby-compiler/core/parse.y" +#line 3180 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_fcall(p, (yyvsp[-1].id), new_callargs(p, 0, 0, (yyvsp[0].nd))); } -#line 8753 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9116 "mrbgems/mruby-compiler/core/y.tab.c" break; case 312: /* primary: method_call brace_block */ -#line 2834 "mrbgems/mruby-compiler/core/parse.y" +#line 3185 "mrbgems/mruby-compiler/core/parse.y" { call_with_block(p, (yyvsp[-1].nd), (yyvsp[0].nd)); (yyval.nd) = (yyvsp[-1].nd); } -#line 8762 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9125 "mrbgems/mruby-compiler/core/y.tab.c" break; case 313: /* @12: %empty */ -#line 2839 "mrbgems/mruby-compiler/core/parse.y" +#line 3190 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); nvars_nest(p); (yyval.num) = p->lpar_beg; p->lpar_beg = ++p->paren_nest; } -#line 8773 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9136 "mrbgems/mruby-compiler/core/y.tab.c" break; case 314: /* @13: %empty */ -#line 2846 "mrbgems/mruby-compiler/core/parse.y" +#line 3197 "mrbgems/mruby-compiler/core/parse.y" { (yyval.stack) = p->cmdarg_stack; p->cmdarg_stack = 0; } -#line 8782 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9145 "mrbgems/mruby-compiler/core/y.tab.c" break; case 315: /* primary: "->" @12 f_larglist @13 lambda_body */ -#line 2851 "mrbgems/mruby-compiler/core/parse.y" +#line 3202 "mrbgems/mruby-compiler/core/parse.y" { p->lpar_beg = (yyvsp[-3].num); (yyval.nd) = new_lambda(p, (yyvsp[-2].nd), (yyvsp[0].nd)); @@ -8791,811 +9154,806 @@ yyreduce: p->cmdarg_stack = (yyvsp[-1].stack); CMDARG_LEXPOP(); } -#line 8795 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9158 "mrbgems/mruby-compiler/core/y.tab.c" break; case 316: /* primary: "'if'" expr_value then compstmt if_tail "'end'" */ -#line 2863 "mrbgems/mruby-compiler/core/parse.y" +#line 3214 "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 8804 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9167 "mrbgems/mruby-compiler/core/y.tab.c" break; case 317: /* primary: "'unless'" expr_value then compstmt opt_else "'end'" */ -#line 2871 "mrbgems/mruby-compiler/core/parse.y" +#line 3222 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_unless(p, cond((yyvsp[-4].nd)), (yyvsp[-2].nd), (yyvsp[-1].nd)); + (yyval.nd) = new_if(p, cond((yyvsp[-4].nd)), (yyvsp[-1].nd), (yyvsp[-2].nd)); SET_LINENO((yyval.nd), (yyvsp[-5].num)); } -#line 8813 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9176 "mrbgems/mruby-compiler/core/y.tab.c" break; case 318: /* $@14: %empty */ -#line 2875 "mrbgems/mruby-compiler/core/parse.y" +#line 3226 "mrbgems/mruby-compiler/core/parse.y" {COND_PUSH(1);} -#line 8819 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9182 "mrbgems/mruby-compiler/core/y.tab.c" break; case 319: /* $@15: %empty */ -#line 2875 "mrbgems/mruby-compiler/core/parse.y" +#line 3226 "mrbgems/mruby-compiler/core/parse.y" {COND_POP();} -#line 8825 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9188 "mrbgems/mruby-compiler/core/y.tab.c" break; case 320: /* primary: "'while'" $@14 expr_value do $@15 compstmt "'end'" */ -#line 2878 "mrbgems/mruby-compiler/core/parse.y" +#line 3229 "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 8834 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9197 "mrbgems/mruby-compiler/core/y.tab.c" break; case 321: /* $@16: %empty */ -#line 2882 "mrbgems/mruby-compiler/core/parse.y" +#line 3233 "mrbgems/mruby-compiler/core/parse.y" {COND_PUSH(1);} -#line 8840 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9203 "mrbgems/mruby-compiler/core/y.tab.c" break; case 322: /* $@17: %empty */ -#line 2882 "mrbgems/mruby-compiler/core/parse.y" +#line 3233 "mrbgems/mruby-compiler/core/parse.y" {COND_POP();} -#line 8846 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9209 "mrbgems/mruby-compiler/core/y.tab.c" break; case 323: /* primary: "'until'" $@16 expr_value do $@17 compstmt "'end'" */ -#line 2885 "mrbgems/mruby-compiler/core/parse.y" +#line 3236 "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 8855 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9218 "mrbgems/mruby-compiler/core/y.tab.c" break; case 324: /* primary: "'case'" expr_value opt_terms case_body "'end'" */ -#line 2892 "mrbgems/mruby-compiler/core/parse.y" +#line 3243 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_case(p, (yyvsp[-3].nd), (yyvsp[-1].nd)); } -#line 8863 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9226 "mrbgems/mruby-compiler/core/y.tab.c" break; case 325: /* primary: "'case'" opt_terms case_body "'end'" */ -#line 2896 "mrbgems/mruby-compiler/core/parse.y" +#line 3247 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_case(p, 0, (yyvsp[-1].nd)); } -#line 8871 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9234 "mrbgems/mruby-compiler/core/y.tab.c" break; case 326: /* $@18: %empty */ -#line 2900 "mrbgems/mruby-compiler/core/parse.y" +#line 3251 "mrbgems/mruby-compiler/core/parse.y" {COND_PUSH(1);} -#line 8877 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9240 "mrbgems/mruby-compiler/core/y.tab.c" break; case 327: /* $@19: %empty */ -#line 2902 "mrbgems/mruby-compiler/core/parse.y" +#line 3253 "mrbgems/mruby-compiler/core/parse.y" {COND_POP();} -#line 8883 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9246 "mrbgems/mruby-compiler/core/y.tab.c" break; case 328: /* primary: "'for'" for_var "'in'" $@18 expr_value do $@19 compstmt "'end'" */ -#line 2905 "mrbgems/mruby-compiler/core/parse.y" +#line 3256 "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 8892 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9255 "mrbgems/mruby-compiler/core/y.tab.c" break; case 329: /* @20: %empty */ -#line 2911 "mrbgems/mruby-compiler/core/parse.y" +#line 3262 "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 8903 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9266 "mrbgems/mruby-compiler/core/y.tab.c" break; case 330: /* primary: "'class'" cpath superclass @20 bodystmt "'end'" */ -#line 2919 "mrbgems/mruby-compiler/core/parse.y" +#line 3270 "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 8914 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9277 "mrbgems/mruby-compiler/core/y.tab.c" break; case 331: /* @21: %empty */ -#line 2927 "mrbgems/mruby-compiler/core/parse.y" +#line 3278 "mrbgems/mruby-compiler/core/parse.y" { (yyval.num) = p->in_def; p->in_def = 0; } -#line 8923 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9286 "mrbgems/mruby-compiler/core/y.tab.c" break; case 332: /* @22: %empty */ -#line 2932 "mrbgems/mruby-compiler/core/parse.y" +#line 3283 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = cons(local_switch(p), nint(p->in_single)); + (yyval.nd) = cons(local_switch(p), int_to_node(p->in_single)); nvars_block(p); p->in_single = 0; } -#line 8933 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9296 "mrbgems/mruby-compiler/core/y.tab.c" break; case 333: /* primary: "'class'" "<<" expr @21 term @22 bodystmt "'end'" */ -#line 2939 "mrbgems/mruby-compiler/core/parse.y" +#line 3290 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_sclass(p, (yyvsp[-5].nd), (yyvsp[-1].nd)); SET_LINENO((yyval.nd), (yyvsp[-7].num)); local_resume(p, (yyvsp[-2].nd)->car); nvars_unnest(p); p->in_def = (yyvsp[-4].num); - p->in_single = intn((yyvsp[-2].nd)->cdr); + p->in_single = node_to_int((yyvsp[-2].nd)->cdr); } -#line 8946 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9309 "mrbgems/mruby-compiler/core/y.tab.c" break; case 334: /* @23: %empty */ -#line 2949 "mrbgems/mruby-compiler/core/parse.y" +#line 3300 "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 8957 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9320 "mrbgems/mruby-compiler/core/y.tab.c" break; case 335: /* primary: "'module'" cpath @23 bodystmt "'end'" */ -#line 2957 "mrbgems/mruby-compiler/core/parse.y" +#line 3308 "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 8968 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9331 "mrbgems/mruby-compiler/core/y.tab.c" break; case 336: /* primary: defn_head f_arglist bodystmt "'end'" */ -#line 2967 "mrbgems/mruby-compiler/core/parse.y" +#line 3318 "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 8979 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9342 "mrbgems/mruby-compiler/core/y.tab.c" break; case 337: /* primary: defs_head f_arglist bodystmt "'end'" */ -#line 2977 "mrbgems/mruby-compiler/core/parse.y" +#line 3328 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-3].nd); - defs_setup(p, (yyval.nd), (yyvsp[-2].nd), (yyvsp[-1].nd)); + defn_setup(p, (yyval.nd), (yyvsp[-2].nd), (yyvsp[-1].nd)); nvars_unnest(p); p->in_def--; p->in_single--; } -#line 8991 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9354 "mrbgems/mruby-compiler/core/y.tab.c" break; case 338: /* primary: "'break'" */ -#line 2985 "mrbgems/mruby-compiler/core/parse.y" +#line 3336 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_break(p, 0); } -#line 8999 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9362 "mrbgems/mruby-compiler/core/y.tab.c" break; case 339: /* primary: "'next'" */ -#line 2989 "mrbgems/mruby-compiler/core/parse.y" +#line 3340 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_next(p, 0); } -#line 9007 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9370 "mrbgems/mruby-compiler/core/y.tab.c" break; case 340: /* primary: "'redo'" */ -#line 2993 "mrbgems/mruby-compiler/core/parse.y" +#line 3344 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_redo(p); } -#line 9015 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9378 "mrbgems/mruby-compiler/core/y.tab.c" break; case 341: /* primary: "'retry'" */ -#line 2997 "mrbgems/mruby-compiler/core/parse.y" +#line 3348 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_retry(p); } -#line 9023 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9386 "mrbgems/mruby-compiler/core/y.tab.c" break; case 342: /* primary_value: primary */ -#line 3003 "mrbgems/mruby-compiler/core/parse.y" +#line 3354 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); if (!(yyval.nd)) (yyval.nd) = new_nil(p); } -#line 9032 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9395 "mrbgems/mruby-compiler/core/y.tab.c" break; case 349: /* if_tail: "'elsif'" expr_value then compstmt if_tail */ -#line 3022 "mrbgems/mruby-compiler/core/parse.y" +#line 3373 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_if(p, cond((yyvsp[-3].nd)), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9040 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9403 "mrbgems/mruby-compiler/core/y.tab.c" break; case 351: /* opt_else: "'else'" compstmt */ -#line 3029 "mrbgems/mruby-compiler/core/parse.y" +#line 3380 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 9048 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9411 "mrbgems/mruby-compiler/core/y.tab.c" break; case 352: /* for_var: lhs */ -#line 3035 "mrbgems/mruby-compiler/core/parse.y" +#line 3386 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(list1((yyvsp[0].nd))); } -#line 9056 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9419 "mrbgems/mruby-compiler/core/y.tab.c" break; case 354: /* f_margs: f_arg */ -#line 3042 "mrbgems/mruby-compiler/core/parse.y" +#line 3393 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list3((yyvsp[0].nd),0,0); } -#line 9064 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9427 "mrbgems/mruby-compiler/core/y.tab.c" break; case 355: /* f_margs: f_arg ',' "*" f_norm_arg */ -#line 3046 "mrbgems/mruby-compiler/core/parse.y" +#line 3397 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = list3((yyvsp[-3].nd), new_arg(p, (yyvsp[0].id)), 0); + (yyval.nd) = list3((yyvsp[-3].nd), new_lvar(p, (yyvsp[0].id)), 0); } -#line 9072 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9435 "mrbgems/mruby-compiler/core/y.tab.c" break; case 356: /* f_margs: f_arg ',' "*" f_norm_arg ',' f_arg */ -#line 3050 "mrbgems/mruby-compiler/core/parse.y" +#line 3401 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = list3((yyvsp[-5].nd), new_arg(p, (yyvsp[-2].id)), (yyvsp[0].nd)); + (yyval.nd) = list3((yyvsp[-5].nd), new_lvar(p, (yyvsp[-2].id)), (yyvsp[0].nd)); } -#line 9080 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9443 "mrbgems/mruby-compiler/core/y.tab.c" break; case 357: /* f_margs: f_arg ',' "*" */ -#line 3054 "mrbgems/mruby-compiler/core/parse.y" +#line 3405 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, intern_op(mul)); - (yyval.nd) = list3((yyvsp[-2].nd), nint(-1), 0); + (yyval.nd) = list3((yyvsp[-2].nd), int_to_node(-1), 0); } -#line 9089 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9452 "mrbgems/mruby-compiler/core/y.tab.c" break; case 358: /* f_margs: f_arg ',' "*" ',' f_arg */ -#line 3059 "mrbgems/mruby-compiler/core/parse.y" +#line 3410 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = list3((yyvsp[-4].nd), nint(-1), (yyvsp[0].nd)); + (yyval.nd) = list3((yyvsp[-4].nd), int_to_node(-1), (yyvsp[0].nd)); } -#line 9097 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9460 "mrbgems/mruby-compiler/core/y.tab.c" break; case 359: /* f_margs: "*" f_norm_arg */ -#line 3063 "mrbgems/mruby-compiler/core/parse.y" +#line 3414 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = list3(0, new_arg(p, (yyvsp[0].id)), 0); + (yyval.nd) = list3(0, new_lvar(p, (yyvsp[0].id)), 0); } -#line 9105 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9468 "mrbgems/mruby-compiler/core/y.tab.c" break; case 360: /* f_margs: "*" f_norm_arg ',' f_arg */ -#line 3067 "mrbgems/mruby-compiler/core/parse.y" +#line 3418 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = list3(0, new_arg(p, (yyvsp[-2].id)), (yyvsp[0].nd)); + (yyval.nd) = list3(0, new_lvar(p, (yyvsp[-2].id)), (yyvsp[0].nd)); } -#line 9113 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9476 "mrbgems/mruby-compiler/core/y.tab.c" break; case 361: /* f_margs: "*" */ -#line 3071 "mrbgems/mruby-compiler/core/parse.y" +#line 3422 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, intern_op(mul)); - (yyval.nd) = list3(0, nint(-1), 0); + (yyval.nd) = list3(0, int_to_node(-1), 0); } -#line 9122 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9485 "mrbgems/mruby-compiler/core/y.tab.c" break; case 362: /* $@24: %empty */ -#line 3076 "mrbgems/mruby-compiler/core/parse.y" +#line 3427 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, intern_op(mul)); } -#line 9130 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9493 "mrbgems/mruby-compiler/core/y.tab.c" break; case 363: /* f_margs: "*" ',' $@24 f_arg */ -#line 3080 "mrbgems/mruby-compiler/core/parse.y" +#line 3431 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = list3(0, nint(-1), (yyvsp[0].nd)); + (yyval.nd) = list3(0, int_to_node(-1), (yyvsp[0].nd)); } -#line 9138 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9501 "mrbgems/mruby-compiler/core/y.tab.c" break; case 364: /* block_args_tail: f_block_kwarg ',' f_kwrest opt_f_block_arg */ -#line 3086 "mrbgems/mruby-compiler/core/parse.y" +#line 3437 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_args_tail(p, (yyvsp[-3].nd), (yyvsp[-1].nd), (yyvsp[0].id)); + (yyval.nd) = new_args_tail(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].id)); } -#line 9146 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9509 "mrbgems/mruby-compiler/core/y.tab.c" break; case 365: /* block_args_tail: f_block_kwarg opt_f_block_arg */ -#line 3090 "mrbgems/mruby-compiler/core/parse.y" +#line 3441 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, (yyvsp[-1].nd), 0, (yyvsp[0].id)); } -#line 9154 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9517 "mrbgems/mruby-compiler/core/y.tab.c" break; case 366: /* block_args_tail: f_kwrest opt_f_block_arg */ -#line 3094 "mrbgems/mruby-compiler/core/parse.y" +#line 3445 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_args_tail(p, 0, (yyvsp[-1].nd), (yyvsp[0].id)); + (yyval.nd) = new_args_tail(p, 0, (yyvsp[-1].id), (yyvsp[0].id)); } -#line 9162 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9525 "mrbgems/mruby-compiler/core/y.tab.c" break; case 367: /* block_args_tail: f_block_arg */ -#line 3098 "mrbgems/mruby-compiler/core/parse.y" +#line 3449 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, 0, 0, (yyvsp[0].id)); } -#line 9170 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9533 "mrbgems/mruby-compiler/core/y.tab.c" break; case 368: /* opt_block_args_tail: ',' block_args_tail */ -#line 3104 "mrbgems/mruby-compiler/core/parse.y" +#line 3455 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 9178 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9541 "mrbgems/mruby-compiler/core/y.tab.c" break; case 369: /* opt_block_args_tail: %empty */ -#line 3108 "mrbgems/mruby-compiler/core/parse.y" +#line 3459 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, 0, 0, 0); } -#line 9186 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9549 "mrbgems/mruby-compiler/core/y.tab.c" break; case 370: /* block_param: f_arg ',' f_block_optarg ',' f_rest_arg opt_block_args_tail */ -#line 3114 "mrbgems/mruby-compiler/core/parse.y" +#line 3465 "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 9194 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9557 "mrbgems/mruby-compiler/core/y.tab.c" break; case 371: /* block_param: f_arg ',' f_block_optarg ',' f_rest_arg ',' f_arg opt_block_args_tail */ -#line 3118 "mrbgems/mruby-compiler/core/parse.y" +#line 3469 "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 9202 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9565 "mrbgems/mruby-compiler/core/y.tab.c" break; case 372: /* block_param: f_arg ',' f_block_optarg opt_block_args_tail */ -#line 3122 "mrbgems/mruby-compiler/core/parse.y" +#line 3473 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-3].nd), (yyvsp[-1].nd), 0, 0, (yyvsp[0].nd)); } -#line 9210 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9573 "mrbgems/mruby-compiler/core/y.tab.c" break; case 373: /* block_param: f_arg ',' f_block_optarg ',' f_arg opt_block_args_tail */ -#line 3126 "mrbgems/mruby-compiler/core/parse.y" +#line 3477 "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 9218 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9581 "mrbgems/mruby-compiler/core/y.tab.c" break; case 374: /* block_param: f_arg ',' f_rest_arg opt_block_args_tail */ -#line 3130 "mrbgems/mruby-compiler/core/parse.y" +#line 3481 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-3].nd), 0, (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 9226 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9589 "mrbgems/mruby-compiler/core/y.tab.c" break; case 375: /* block_param: f_arg ',' opt_block_args_tail */ -#line 3134 "mrbgems/mruby-compiler/core/parse.y" +#line 3485 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-2].nd), 0, 0, 0, (yyvsp[0].nd)); } -#line 9234 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9597 "mrbgems/mruby-compiler/core/y.tab.c" break; case 376: /* block_param: f_arg ',' f_rest_arg ',' f_arg opt_block_args_tail */ -#line 3138 "mrbgems/mruby-compiler/core/parse.y" +#line 3489 "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 9242 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9605 "mrbgems/mruby-compiler/core/y.tab.c" break; case 377: /* block_param: f_arg opt_block_args_tail */ -#line 3142 "mrbgems/mruby-compiler/core/parse.y" +#line 3493 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, (yyvsp[-1].nd), 0, 0, 0, (yyvsp[0].nd)); } -#line 9250 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9613 "mrbgems/mruby-compiler/core/y.tab.c" break; case 378: /* block_param: f_block_optarg ',' f_rest_arg opt_block_args_tail */ -#line 3146 "mrbgems/mruby-compiler/core/parse.y" +#line 3497 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-3].nd), (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 9258 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9621 "mrbgems/mruby-compiler/core/y.tab.c" break; case 379: /* block_param: f_block_optarg ',' f_rest_arg ',' f_arg opt_block_args_tail */ -#line 3150 "mrbgems/mruby-compiler/core/parse.y" +#line 3501 "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 9266 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9629 "mrbgems/mruby-compiler/core/y.tab.c" break; case 380: /* block_param: f_block_optarg opt_block_args_tail */ -#line 3154 "mrbgems/mruby-compiler/core/parse.y" +#line 3505 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-1].nd), 0, 0, (yyvsp[0].nd)); } -#line 9274 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9637 "mrbgems/mruby-compiler/core/y.tab.c" break; case 381: /* block_param: f_block_optarg ',' f_arg opt_block_args_tail */ -#line 3158 "mrbgems/mruby-compiler/core/parse.y" +#line 3509 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, (yyvsp[-3].nd), 0, (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9282 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9645 "mrbgems/mruby-compiler/core/y.tab.c" break; case 382: /* block_param: f_rest_arg opt_block_args_tail */ -#line 3162 "mrbgems/mruby-compiler/core/parse.y" +#line 3513 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, 0, (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 9290 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9653 "mrbgems/mruby-compiler/core/y.tab.c" break; case 383: /* block_param: f_rest_arg ',' f_arg opt_block_args_tail */ -#line 3166 "mrbgems/mruby-compiler/core/parse.y" +#line 3517 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, 0, (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9298 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9661 "mrbgems/mruby-compiler/core/y.tab.c" break; case 384: /* block_param: block_args_tail */ -#line 3170 "mrbgems/mruby-compiler/core/parse.y" +#line 3521 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args(p, 0, 0, 0, 0, (yyvsp[0].nd)); } -#line 9306 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9669 "mrbgems/mruby-compiler/core/y.tab.c" break; case 385: /* opt_block_param: none */ -#line 3176 "mrbgems/mruby-compiler/core/parse.y" +#line 3527 "mrbgems/mruby-compiler/core/parse.y" { local_add_blk(p); (yyval.nd) = 0; } -#line 9315 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9678 "mrbgems/mruby-compiler/core/y.tab.c" break; case 386: /* opt_block_param: block_param_def */ -#line 3181 "mrbgems/mruby-compiler/core/parse.y" +#line 3532 "mrbgems/mruby-compiler/core/parse.y" { p->cmd_start = TRUE; (yyval.nd) = (yyvsp[0].nd); } -#line 9324 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9687 "mrbgems/mruby-compiler/core/y.tab.c" break; case 387: /* $@25: %empty */ -#line 3187 "mrbgems/mruby-compiler/core/parse.y" +#line 3538 "mrbgems/mruby-compiler/core/parse.y" {local_add_blk(p);} -#line 9330 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9693 "mrbgems/mruby-compiler/core/y.tab.c" break; case 388: /* block_param_def: '|' $@25 opt_bv_decl '|' */ -#line 3188 "mrbgems/mruby-compiler/core/parse.y" +#line 3539 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 9338 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9701 "mrbgems/mruby-compiler/core/y.tab.c" break; case 389: /* block_param_def: "||" */ -#line 3192 "mrbgems/mruby-compiler/core/parse.y" +#line 3543 "mrbgems/mruby-compiler/core/parse.y" { local_add_blk(p); (yyval.nd) = 0; } -#line 9347 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9710 "mrbgems/mruby-compiler/core/y.tab.c" break; case 390: /* block_param_def: '|' block_param opt_bv_decl '|' */ -#line 3197 "mrbgems/mruby-compiler/core/parse.y" +#line 3548 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-2].nd); } -#line 9355 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9718 "mrbgems/mruby-compiler/core/y.tab.c" break; case 391: /* opt_bv_decl: opt_nl */ -#line 3204 "mrbgems/mruby-compiler/core/parse.y" +#line 3554 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 9363 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9726 "mrbgems/mruby-compiler/core/y.tab.c" break; case 392: /* opt_bv_decl: opt_nl ';' bv_decls opt_nl */ -#line 3208 "mrbgems/mruby-compiler/core/parse.y" +#line 3558 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 9371 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9734 "mrbgems/mruby-compiler/core/y.tab.c" break; case 395: /* bvar: "local variable or method" */ -#line 3218 "mrbgems/mruby-compiler/core/parse.y" +#line 3568 "mrbgems/mruby-compiler/core/parse.y" { local_add_f(p, (yyvsp[0].id)); new_bv(p, (yyvsp[0].id)); } -#line 9380 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9743 "mrbgems/mruby-compiler/core/y.tab.c" break; case 397: /* f_larglist: '(' f_args opt_bv_decl ')' */ -#line 3226 "mrbgems/mruby-compiler/core/parse.y" +#line 3576 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-2].nd); } -#line 9388 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9751 "mrbgems/mruby-compiler/core/y.tab.c" break; case 398: /* f_larglist: f_args */ -#line 3230 "mrbgems/mruby-compiler/core/parse.y" +#line 3580 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 9396 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9759 "mrbgems/mruby-compiler/core/y.tab.c" break; case 399: /* lambda_body: tLAMBEG compstmt '}' */ -#line 3236 "mrbgems/mruby-compiler/core/parse.y" +#line 3586 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 9404 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9767 "mrbgems/mruby-compiler/core/y.tab.c" break; case 400: /* lambda_body: "'do' for lambda" bodystmt "'end'" */ -#line 3240 "mrbgems/mruby-compiler/core/parse.y" +#line 3590 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 9412 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9775 "mrbgems/mruby-compiler/core/y.tab.c" break; case 401: /* @26: %empty */ -#line 3246 "mrbgems/mruby-compiler/core/parse.y" +#line 3596 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); nvars_nest(p); (yyval.num) = p->lineno; } -#line 9422 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9785 "mrbgems/mruby-compiler/core/y.tab.c" break; case 402: /* do_block: "'do' for block" @26 opt_block_param bodystmt "'end'" */ -#line 3254 "mrbgems/mruby-compiler/core/parse.y" +#line 3604 "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 9433 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9796 "mrbgems/mruby-compiler/core/y.tab.c" break; case 403: /* block_call: command do_block */ -#line 3263 "mrbgems/mruby-compiler/core/parse.y" +#line 3613 "mrbgems/mruby-compiler/core/parse.y" { - if (typen((yyvsp[-1].nd)->car) == NODE_YIELD) { - yyerror(&(yylsp[-1]), p, "block given to yield"); - } - else { - call_with_block(p, (yyvsp[-1].nd), (yyvsp[0].nd)); - } + call_with_block(p, (yyvsp[-1].nd), (yyvsp[0].nd)); (yyval.nd) = (yyvsp[-1].nd); } -#line 9447 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9805 "mrbgems/mruby-compiler/core/y.tab.c" break; case 404: /* block_call: block_call call_op2 operation2 opt_paren_args */ -#line 3273 "mrbgems/mruby-compiler/core/parse.y" +#line 3618 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), (yyvsp[-2].num)); } -#line 9455 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9813 "mrbgems/mruby-compiler/core/y.tab.c" break; case 405: /* block_call: block_call call_op2 operation2 opt_paren_args brace_block */ -#line 3277 "mrbgems/mruby-compiler/core/parse.y" +#line 3622 "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 9464 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9822 "mrbgems/mruby-compiler/core/y.tab.c" break; case 406: /* block_call: block_call call_op2 operation2 command_args do_block */ -#line 3282 "mrbgems/mruby-compiler/core/parse.y" +#line 3627 "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 9473 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9831 "mrbgems/mruby-compiler/core/y.tab.c" break; case 407: /* method_call: operation paren_args */ -#line 3289 "mrbgems/mruby-compiler/core/parse.y" +#line 3634 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_fcall(p, (yyvsp[-1].id), (yyvsp[0].nd)); } -#line 9481 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9839 "mrbgems/mruby-compiler/core/y.tab.c" break; case 408: /* method_call: primary_value call_op operation2 opt_paren_args */ -#line 3293 "mrbgems/mruby-compiler/core/parse.y" +#line 3638 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), (yyvsp[-2].num)); } -#line 9489 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9847 "mrbgems/mruby-compiler/core/y.tab.c" break; case 409: /* method_call: primary_value "::" operation2 paren_args */ -#line 3297 "mrbgems/mruby-compiler/core/parse.y" +#line 3642 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].nd), tCOLON2); } -#line 9497 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9855 "mrbgems/mruby-compiler/core/y.tab.c" break; case 410: /* method_call: primary_value "::" operation3 */ -#line 3301 "mrbgems/mruby-compiler/core/parse.y" +#line 3646 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), (yyvsp[0].id), 0, tCOLON2); } -#line 9505 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9863 "mrbgems/mruby-compiler/core/y.tab.c" break; case 411: /* method_call: primary_value call_op paren_args */ -#line 3305 "mrbgems/mruby-compiler/core/parse.y" +#line 3650 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), MRB_SYM_2(p->mrb, call), (yyvsp[0].nd), (yyvsp[-1].num)); } -#line 9513 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9871 "mrbgems/mruby-compiler/core/y.tab.c" break; case 412: /* method_call: primary_value "::" paren_args */ -#line 3309 "mrbgems/mruby-compiler/core/parse.y" +#line 3654 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-2].nd), MRB_SYM_2(p->mrb, call), (yyvsp[0].nd), tCOLON2); } -#line 9521 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9879 "mrbgems/mruby-compiler/core/y.tab.c" break; case 413: /* method_call: "'super'" paren_args */ -#line 3313 "mrbgems/mruby-compiler/core/parse.y" +#line 3658 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_super(p, (yyvsp[0].nd)); } -#line 9529 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9887 "mrbgems/mruby-compiler/core/y.tab.c" break; case 414: /* method_call: "'super'" */ -#line 3317 "mrbgems/mruby-compiler/core/parse.y" +#line 3662 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_zsuper(p); } -#line 9537 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9895 "mrbgems/mruby-compiler/core/y.tab.c" break; case 415: /* method_call: primary_value '[' opt_call_args ']' */ -#line 3321 "mrbgems/mruby-compiler/core/parse.y" +#line 3666 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_call(p, (yyvsp[-3].nd), intern_op(aref), (yyvsp[-1].nd), '.'); } -#line 9545 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9903 "mrbgems/mruby-compiler/core/y.tab.c" break; case 416: /* @27: %empty */ -#line 3327 "mrbgems/mruby-compiler/core/parse.y" +#line 3672 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); nvars_nest(p); (yyval.num) = p->lineno; } -#line 9555 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9913 "mrbgems/mruby-compiler/core/y.tab.c" break; case 417: /* brace_block: '{' @27 opt_block_param compstmt '}' */ -#line 3334 "mrbgems/mruby-compiler/core/parse.y" +#line 3679 "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 9566 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9924 "mrbgems/mruby-compiler/core/y.tab.c" break; case 418: /* @28: %empty */ -#line 3341 "mrbgems/mruby-compiler/core/parse.y" +#line 3686 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); nvars_nest(p); (yyval.num) = p->lineno; } -#line 9576 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9934 "mrbgems/mruby-compiler/core/y.tab.c" break; case 419: /* brace_block: "'do'" @28 opt_block_param bodystmt "'end'" */ -#line 3348 "mrbgems/mruby-compiler/core/parse.y" +#line 3693 "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 9587 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9945 "mrbgems/mruby-compiler/core/y.tab.c" break; case 420: /* case_body: "'when'" args then compstmt cases */ -#line 3359 "mrbgems/mruby-compiler/core/parse.y" +#line 3704 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = cons(cons((yyvsp[-3].nd), (yyvsp[-1].nd)), (yyvsp[0].nd)); } -#line 9595 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9953 "mrbgems/mruby-compiler/core/y.tab.c" break; case 421: /* cases: opt_else */ -#line 3365 "mrbgems/mruby-compiler/core/parse.y" +#line 3710 "mrbgems/mruby-compiler/core/parse.y" { if ((yyvsp[0].nd)) { (yyval.nd) = cons(cons(0, (yyvsp[0].nd)), 0); @@ -9604,1196 +9962,1214 @@ yyreduce: (yyval.nd) = 0; } } -#line 9608 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9966 "mrbgems/mruby-compiler/core/y.tab.c" break; case 423: /* opt_rescue: "'rescue'" exc_list exc_var then compstmt opt_rescue */ -#line 3379 "mrbgems/mruby-compiler/core/parse.y" +#line 3724 "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 9617 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9975 "mrbgems/mruby-compiler/core/y.tab.c" break; case 425: /* exc_list: arg */ -#line 3387 "mrbgems/mruby-compiler/core/parse.y" +#line 3732 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 9625 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9983 "mrbgems/mruby-compiler/core/y.tab.c" break; case 428: /* exc_var: "=>" lhs */ -#line 3395 "mrbgems/mruby-compiler/core/parse.y" +#line 3740 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 9633 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9991 "mrbgems/mruby-compiler/core/y.tab.c" break; case 430: /* opt_ensure: "'ensure'" compstmt */ -#line 3402 "mrbgems/mruby-compiler/core/parse.y" +#line 3747 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[0].nd); } -#line 9641 "mrbgems/mruby-compiler/core/y.tab.c" +#line 9999 "mrbgems/mruby-compiler/core/y.tab.c" break; case 437: /* string: string string_fragment */ -#line 3416 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = concat_string(p, (yyvsp[-1].nd), (yyvsp[0].nd)); - } -#line 9649 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 440: /* string_fragment: "string literal" tSTRING */ -#line 3424 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = (yyvsp[0].nd); - } -#line 9657 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 441: /* string_fragment: "string literal" string_rep tSTRING */ -#line 3428 "mrbgems/mruby-compiler/core/parse.y" - { - node *n = (yyvsp[-1].nd); - if (intn((yyvsp[0].nd)->cdr->cdr) > 0) { - n = push(n, (yyvsp[0].nd)); - } - else { - cons_free((yyvsp[0].nd)); - } - (yyval.nd) = new_dstr(p, n); - } -#line 9672 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 443: /* string_rep: string_rep string_interp */ -#line 3442 "mrbgems/mruby-compiler/core/parse.y" +#line 3761 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = append((yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9680 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10007 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 438: /* string_fragment: "character literal" */ +#line 3767 "mrbgems/mruby-compiler/core/parse.y" + { + /* tCHAR is (len . str), wrap as cons list */ + (yyval.nd) = list1((yyvsp[0].nd)); + } +#line 10016 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 439: /* string_fragment: tSTRING */ +#line 3772 "mrbgems/mruby-compiler/core/parse.y" + { + /* tSTRING is (len . str), wrap as cons list */ + (yyval.nd) = list1((yyvsp[0].nd)); + } +#line 10025 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 440: /* string_fragment: "string literal" tSTRING */ +#line 3777 "mrbgems/mruby-compiler/core/parse.y" + { + /* $2 is (len . str), wrap as cons list */ + (yyval.nd) = list1((yyvsp[0].nd)); + } +#line 10034 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 441: /* string_fragment: "string literal" string_rep tSTRING */ +#line 3782 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = push((yyvsp[-1].nd), (yyvsp[0].nd)); + } +#line 10042 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 443: /* string_rep: string_rep string_interp */ +#line 3789 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = append((yyvsp[-1].nd), (yyvsp[0].nd)); + } +#line 10050 "mrbgems/mruby-compiler/core/y.tab.c" break; case 444: /* string_interp: tSTRING_MID */ -#line 3448 "mrbgems/mruby-compiler/core/parse.y" +#line 3795 "mrbgems/mruby-compiler/core/parse.y" { + /* $1 is already in (len . str) format */ (yyval.nd) = list1((yyvsp[0].nd)); } -#line 9688 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10059 "mrbgems/mruby-compiler/core/y.tab.c" break; case 445: /* @29: %empty */ -#line 3452 "mrbgems/mruby-compiler/core/parse.y" +#line 3800 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push_strterm(p); } -#line 9696 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10067 "mrbgems/mruby-compiler/core/y.tab.c" break; case 446: /* string_interp: tSTRING_PART @29 compstmt '}' */ -#line 3457 "mrbgems/mruby-compiler/core/parse.y" +#line 3805 "mrbgems/mruby-compiler/core/parse.y" { pop_strterm(p,(yyvsp[-2].nd)); - (yyval.nd) = list2((yyvsp[-3].nd), (yyvsp[-1].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 9705 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10078 "mrbgems/mruby-compiler/core/y.tab.c" break; case 447: /* string_interp: tLITERAL_DELIM */ -#line 3462 "mrbgems/mruby-compiler/core/parse.y" +#line 3812 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(new_literal_delim(p)); } -#line 9713 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10086 "mrbgems/mruby-compiler/core/y.tab.c" break; case 448: /* string_interp: tHD_LITERAL_DELIM heredoc_bodies */ -#line 3466 "mrbgems/mruby-compiler/core/parse.y" +#line 3816 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1(new_literal_delim(p)); } -#line 9721 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10094 "mrbgems/mruby-compiler/core/y.tab.c" break; case 449: /* xstring: tXSTRING_BEG tXSTRING */ -#line 3472 "mrbgems/mruby-compiler/core/parse.y" +#line 3822 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = (yyvsp[0].nd); + (yyval.nd) = cons((yyvsp[0].nd), (node*)NULL); } -#line 9729 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10102 "mrbgems/mruby-compiler/core/y.tab.c" break; case 450: /* xstring: tXSTRING_BEG string_rep tXSTRING */ -#line 3476 "mrbgems/mruby-compiler/core/parse.y" +#line 3826 "mrbgems/mruby-compiler/core/parse.y" { - node *n = (yyvsp[-1].nd); - if (intn((yyvsp[0].nd)->cdr->cdr) > 0) { - n = push(n, (yyvsp[0].nd)); - } - else { - cons_free((yyvsp[0].nd)); - } - (yyval.nd) = new_dxstr(p, n); + (yyval.nd) = push((yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 9744 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10110 "mrbgems/mruby-compiler/core/y.tab.c" break; case 451: /* regexp: tREGEXP_BEG tREGEXP */ -#line 3489 "mrbgems/mruby-compiler/core/parse.y" +#line 3832 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = (yyvsp[0].nd); + node *data = (yyvsp[0].nd); /* ((len . pattern) . (flags . encoding)) */ + const char *flags = (const char*)data->cdr->car; + const char *encoding = (const char*)data->cdr->cdr; + /* Use data->car directly as pattern_list: (len . pattern) */ + node *pattern_list = cons(data->car, (node*)NULL); + (yyval.nd) = new_regx(p, pattern_list, flags, encoding); } -#line 9752 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10123 "mrbgems/mruby-compiler/core/y.tab.c" break; case 452: /* regexp: tREGEXP_BEG string_rep tREGEXP */ -#line 3493 "mrbgems/mruby-compiler/core/parse.y" +#line 3841 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_dregx(p, (yyvsp[-1].nd), (yyvsp[0].nd)); + node *data = (yyvsp[0].nd); /* ((len . pattern) . (flags . encoding)) */ + const char *flags = (const char*)data->cdr->car; + const char *encoding = (const char*)data->cdr->cdr; + /* Append the pattern from $3->car to the string list $2 */ + node *complete_list = push((yyvsp[-1].nd), data->car); + (yyval.nd) = new_regx(p, complete_list, flags, encoding); } -#line 9760 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10136 "mrbgems/mruby-compiler/core/y.tab.c" break; case 456: /* heredoc_body: tHEREDOC_END */ -#line 3506 "mrbgems/mruby-compiler/core/parse.y" +#line 3859 "mrbgems/mruby-compiler/core/parse.y" { parser_heredoc_info *info = parsing_heredoc_info(p); - info->doc = push(info->doc, new_str(p, "", 0)); + info->doc = push(info->doc, new_str_empty(p)); heredoc_end(p); } -#line 9770 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10146 "mrbgems/mruby-compiler/core/y.tab.c" break; case 457: /* heredoc_body: heredoc_string_rep tHEREDOC_END */ -#line 3512 "mrbgems/mruby-compiler/core/parse.y" +#line 3865 "mrbgems/mruby-compiler/core/parse.y" { heredoc_end(p); } -#line 9778 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10154 "mrbgems/mruby-compiler/core/y.tab.c" break; case 460: /* heredoc_string_interp: tHD_STRING_MID */ -#line 3522 "mrbgems/mruby-compiler/core/parse.y" +#line 3875 "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 9788 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10164 "mrbgems/mruby-compiler/core/y.tab.c" break; case 461: /* @30: %empty */ -#line 3528 "mrbgems/mruby-compiler/core/parse.y" +#line 3881 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push_strterm(p); } -#line 9796 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10172 "mrbgems/mruby-compiler/core/y.tab.c" break; case 462: /* heredoc_string_interp: tHD_STRING_PART @30 compstmt '}' */ -#line 3533 "mrbgems/mruby-compiler/core/parse.y" +#line 3886 "mrbgems/mruby-compiler/core/parse.y" { pop_strterm(p, (yyvsp[-2].nd)); parser_heredoc_info *info = parsing_heredoc_info(p); - info->doc = push(push(info->doc, (yyvsp[-3].nd)), (yyvsp[-1].nd)); + /* $1 is already in (len . str) format, create (-1 . node) for expression */ + node *expr_elem = cons(int_to_node(-1), (yyvsp[-1].nd)); + info->doc = push(push(info->doc, (yyvsp[-3].nd)), expr_elem); } -#line 9806 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10184 "mrbgems/mruby-compiler/core/y.tab.c" break; case 463: /* words: tWORDS_BEG tSTRING */ -#line 3541 "mrbgems/mruby-compiler/core/parse.y" +#line 3896 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_words(p, list1((yyvsp[0].nd))); } -#line 9814 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10192 "mrbgems/mruby-compiler/core/y.tab.c" break; case 464: /* words: tWORDS_BEG string_rep tSTRING */ -#line 3545 "mrbgems/mruby-compiler/core/parse.y" +#line 3900 "mrbgems/mruby-compiler/core/parse.y" { node *n = (yyvsp[-1].nd); - if (intn((yyvsp[0].nd)->cdr->cdr) > 0) { - n = push(n, (yyvsp[0].nd)); - } - else { - cons_free((yyvsp[0].nd)); - } + n = push(n, (yyvsp[0].nd)); (yyval.nd) = new_words(p, n); } -#line 9829 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10202 "mrbgems/mruby-compiler/core/y.tab.c" break; case 465: /* symbol: basic_symbol */ -#line 3559 "mrbgems/mruby-compiler/core/parse.y" +#line 3908 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_sym(p, (yyvsp[0].id)); } -#line 9837 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10210 "mrbgems/mruby-compiler/core/y.tab.c" break; case 466: /* symbol: "symbol" "string literal" string_rep tSTRING */ -#line 3563 "mrbgems/mruby-compiler/core/parse.y" +#line 3912 "mrbgems/mruby-compiler/core/parse.y" { node *n = (yyvsp[-1].nd); p->lstate = EXPR_ENDARG; - if (intn((yyvsp[0].nd)->cdr->cdr) > 0) { + if (node_to_int((yyvsp[0].nd)->car) > 0) { n = push(n, (yyvsp[0].nd)); } else { cons_free((yyvsp[0].nd)); } - (yyval.nd) = new_dsym(p, new_dstr(p, n)); + (yyval.nd) = new_dsym(p, n); } -#line 9853 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10226 "mrbgems/mruby-compiler/core/y.tab.c" break; case 467: /* symbol: "symbol" "numbered parameter" */ -#line 3575 "mrbgems/mruby-compiler/core/parse.y" +#line 3924 "mrbgems/mruby-compiler/core/parse.y" { mrb_sym sym = intern_numparam((yyvsp[0].num)); (yyval.nd) = new_sym(p, sym); } -#line 9862 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10235 "mrbgems/mruby-compiler/core/y.tab.c" break; case 468: /* basic_symbol: "symbol" sym */ -#line 3582 "mrbgems/mruby-compiler/core/parse.y" +#line 3931 "mrbgems/mruby-compiler/core/parse.y" { p->lstate = EXPR_END; (yyval.id) = (yyvsp[0].id); } -#line 9871 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10244 "mrbgems/mruby-compiler/core/y.tab.c" break; case 473: /* sym: tSTRING */ -#line 3593 "mrbgems/mruby-compiler/core/parse.y" +#line 3942 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = new_strsym(p, (yyvsp[0].nd)); } -#line 9879 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10252 "mrbgems/mruby-compiler/core/y.tab.c" break; case 474: /* sym: "string literal" tSTRING */ -#line 3597 "mrbgems/mruby-compiler/core/parse.y" +#line 3946 "mrbgems/mruby-compiler/core/parse.y" { (yyval.id) = new_strsym(p, (yyvsp[0].nd)); } -#line 9887 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10260 "mrbgems/mruby-compiler/core/y.tab.c" break; case 475: /* symbols: tSYMBOLS_BEG tSTRING */ -#line 3603 "mrbgems/mruby-compiler/core/parse.y" +#line 3952 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_symbols(p, list1((yyvsp[0].nd))); } -#line 9895 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10268 "mrbgems/mruby-compiler/core/y.tab.c" break; case 476: /* symbols: tSYMBOLS_BEG string_rep tSTRING */ -#line 3607 "mrbgems/mruby-compiler/core/parse.y" +#line 3956 "mrbgems/mruby-compiler/core/parse.y" { node *n = (yyvsp[-1].nd); - if (intn((yyvsp[0].nd)->cdr->cdr) > 0) { - n = push(n, (yyvsp[0].nd)); - } + n = push(n, (yyvsp[0].nd)); (yyval.nd) = new_symbols(p, n); } -#line 9907 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10278 "mrbgems/mruby-compiler/core/y.tab.c" break; case 479: /* numeric: tUMINUS_NUM "integer literal" */ -#line 3619 "mrbgems/mruby-compiler/core/parse.y" +#line 3966 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_negate(p, (yyvsp[0].nd)); } -#line 9915 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10286 "mrbgems/mruby-compiler/core/y.tab.c" break; case 480: /* numeric: tUMINUS_NUM "float literal" */ -#line 3623 "mrbgems/mruby-compiler/core/parse.y" +#line 3970 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_negate(p, (yyvsp[0].nd)); } -#line 9923 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10294 "mrbgems/mruby-compiler/core/y.tab.c" break; case 481: /* variable: "local variable or method" */ -#line 3629 "mrbgems/mruby-compiler/core/parse.y" +#line 3976 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_lvar(p, (yyvsp[0].id)); } -#line 9931 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10302 "mrbgems/mruby-compiler/core/y.tab.c" break; case 482: /* variable: "instance variable" */ -#line 3633 "mrbgems/mruby-compiler/core/parse.y" +#line 3980 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_ivar(p, (yyvsp[0].id)); } -#line 9939 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10310 "mrbgems/mruby-compiler/core/y.tab.c" break; case 483: /* variable: "global variable" */ -#line 3637 "mrbgems/mruby-compiler/core/parse.y" +#line 3984 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_gvar(p, (yyvsp[0].id)); } -#line 9947 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10318 "mrbgems/mruby-compiler/core/y.tab.c" break; case 484: /* variable: "class variable" */ -#line 3641 "mrbgems/mruby-compiler/core/parse.y" +#line 3988 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_cvar(p, (yyvsp[0].id)); } -#line 9955 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10326 "mrbgems/mruby-compiler/core/y.tab.c" break; case 485: /* variable: "constant" */ -#line 3645 "mrbgems/mruby-compiler/core/parse.y" +#line 3992 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_const(p, (yyvsp[0].id)); } -#line 9963 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10334 "mrbgems/mruby-compiler/core/y.tab.c" break; case 486: /* var_lhs: variable */ -#line 3651 "mrbgems/mruby-compiler/core/parse.y" +#line 3998 "mrbgems/mruby-compiler/core/parse.y" { assignable(p, (yyvsp[0].nd)); } -#line 9971 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10342 "mrbgems/mruby-compiler/core/y.tab.c" break; case 487: /* var_lhs: "numbered parameter" */ -#line 3655 "mrbgems/mruby-compiler/core/parse.y" +#line 4002 "mrbgems/mruby-compiler/core/parse.y" { yyerror(&(yylsp[0]), p, "can't assign to numbered parameter"); } -#line 9979 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10350 "mrbgems/mruby-compiler/core/y.tab.c" break; case 488: /* var_ref: variable */ -#line 3661 "mrbgems/mruby-compiler/core/parse.y" +#line 4008 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = var_reference(p, (yyvsp[0].nd)); } -#line 9987 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10358 "mrbgems/mruby-compiler/core/y.tab.c" break; case 489: /* var_ref: "numbered parameter" */ -#line 3665 "mrbgems/mruby-compiler/core/parse.y" +#line 4012 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_nvar(p, (yyvsp[0].num)); } -#line 9995 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10366 "mrbgems/mruby-compiler/core/y.tab.c" break; case 490: /* var_ref: "'nil'" */ -#line 3669 "mrbgems/mruby-compiler/core/parse.y" +#line 4016 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_nil(p); } -#line 10003 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10374 "mrbgems/mruby-compiler/core/y.tab.c" break; case 491: /* var_ref: "'self'" */ -#line 3673 "mrbgems/mruby-compiler/core/parse.y" +#line 4020 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_self(p); } -#line 10011 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10382 "mrbgems/mruby-compiler/core/y.tab.c" break; case 492: /* var_ref: "'true'" */ -#line 3677 "mrbgems/mruby-compiler/core/parse.y" +#line 4024 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_true(p); } -#line 10019 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10390 "mrbgems/mruby-compiler/core/y.tab.c" break; case 493: /* var_ref: "'false'" */ -#line 3681 "mrbgems/mruby-compiler/core/parse.y" +#line 4028 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_false(p); } -#line 10027 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10398 "mrbgems/mruby-compiler/core/y.tab.c" break; case 494: /* var_ref: "'__FILE__'" */ -#line 3685 "mrbgems/mruby-compiler/core/parse.y" +#line 4032 "mrbgems/mruby-compiler/core/parse.y" { const char *fn = mrb_sym_name_len(p->mrb, p->filename_sym, NULL); if (!fn) { fn = "(null)"; } - (yyval.nd) = new_str(p, fn, strlen(fn)); + (yyval.nd) = new_str(p, cons(cons(int_to_node(strlen(fn)), (node*)fn), (node*)NULL)); } -#line 10039 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10410 "mrbgems/mruby-compiler/core/y.tab.c" break; case 495: /* var_ref: "'__LINE__'" */ -#line 3693 "mrbgems/mruby-compiler/core/parse.y" +#line 4040 "mrbgems/mruby-compiler/core/parse.y" { char buf[16]; dump_int(p->lineno, buf); (yyval.nd) = new_int(p, buf, 10, 0); } -#line 10050 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10421 "mrbgems/mruby-compiler/core/y.tab.c" break; case 496: /* var_ref: "'__ENCODING__'" */ -#line 3700 "mrbgems/mruby-compiler/core/parse.y" +#line 4047 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_fcall(p, MRB_SYM_2(p->mrb, __ENCODING__), 0); } -#line 10058 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10429 "mrbgems/mruby-compiler/core/y.tab.c" break; case 499: /* superclass: %empty */ -#line 3710 "mrbgems/mruby-compiler/core/parse.y" +#line 4057 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = 0; } -#line 10066 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10437 "mrbgems/mruby-compiler/core/y.tab.c" break; case 500: /* $@31: %empty */ -#line 3714 "mrbgems/mruby-compiler/core/parse.y" +#line 4061 "mrbgems/mruby-compiler/core/parse.y" { p->lstate = EXPR_BEG; p->cmd_start = TRUE; } -#line 10075 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10446 "mrbgems/mruby-compiler/core/y.tab.c" break; case 501: /* superclass: '<' $@31 expr_value term */ -#line 3719 "mrbgems/mruby-compiler/core/parse.y" +#line 4066 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 10083 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10454 "mrbgems/mruby-compiler/core/y.tab.c" break; case 504: /* f_arglist_paren: '(' f_args rparen */ -#line 3735 "mrbgems/mruby-compiler/core/parse.y" +#line 4082 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); p->lstate = EXPR_BEG; p->cmd_start = TRUE; } -#line 10093 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10464 "mrbgems/mruby-compiler/core/y.tab.c" break; case 505: /* f_arglist_paren: '(' f_arg ',' tBDOT3 rparen */ -#line 3741 "mrbgems/mruby-compiler/core/parse.y" +#line 4088 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_dots(p, (yyvsp[-3].nd)); } -#line 10101 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10472 "mrbgems/mruby-compiler/core/y.tab.c" break; case 506: /* f_arglist_paren: '(' tBDOT3 rparen */ -#line 3745 "mrbgems/mruby-compiler/core/parse.y" +#line 4092 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_dots(p, 0); } -#line 10109 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10480 "mrbgems/mruby-compiler/core/y.tab.c" break; case 508: /* f_arglist: f_args term */ -#line 3752 "mrbgems/mruby-compiler/core/parse.y" +#line 4099 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = (yyvsp[-1].nd); } -#line 10117 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10488 "mrbgems/mruby-compiler/core/y.tab.c" break; case 509: /* f_arglist: f_arg ',' tBDOT3 term */ -#line 3756 "mrbgems/mruby-compiler/core/parse.y" +#line 4103 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_dots(p, (yyvsp[-3].nd)); } -#line 10125 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10496 "mrbgems/mruby-compiler/core/y.tab.c" break; case 510: /* f_arglist: "..." term */ -#line 3760 "mrbgems/mruby-compiler/core/parse.y" +#line 4107 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_dots(p, 0); } -#line 10133 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10504 "mrbgems/mruby-compiler/core/y.tab.c" break; case 511: /* f_label: "local variable or method" "label" */ -#line 3766 "mrbgems/mruby-compiler/core/parse.y" +#line 4113 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); } -#line 10141 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10512 "mrbgems/mruby-compiler/core/y.tab.c" break; case 512: /* f_label: "numbered parameter" "label" */ -#line 3770 "mrbgems/mruby-compiler/core/parse.y" +#line 4117 "mrbgems/mruby-compiler/core/parse.y" { local_nest(p); } -#line 10149 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10520 "mrbgems/mruby-compiler/core/y.tab.c" break; case 513: /* f_kw: f_label arg */ -#line 3776 "mrbgems/mruby-compiler/core/parse.y" +#line 4123 "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 10159 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10530 "mrbgems/mruby-compiler/core/y.tab.c" break; case 514: /* f_kw: f_label */ -#line 3782 "mrbgems/mruby-compiler/core/parse.y" +#line 4129 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_kw_arg(p, (yyvsp[0].id), 0); local_unnest(p); } -#line 10168 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10539 "mrbgems/mruby-compiler/core/y.tab.c" break; case 515: /* f_block_kw: f_label primary_value */ -#line 3789 "mrbgems/mruby-compiler/core/parse.y" +#line 4136 "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 10178 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10549 "mrbgems/mruby-compiler/core/y.tab.c" break; case 516: /* f_block_kw: f_label */ -#line 3795 "mrbgems/mruby-compiler/core/parse.y" +#line 4142 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_kw_arg(p, (yyvsp[0].id), 0); local_unnest(p); } -#line 10187 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10558 "mrbgems/mruby-compiler/core/y.tab.c" break; case 517: /* f_block_kwarg: f_block_kw */ -#line 3802 "mrbgems/mruby-compiler/core/parse.y" +#line 4149 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 10195 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10566 "mrbgems/mruby-compiler/core/y.tab.c" break; case 518: /* f_block_kwarg: f_block_kwarg ',' f_block_kw */ -#line 3806 "mrbgems/mruby-compiler/core/parse.y" +#line 4153 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10203 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10574 "mrbgems/mruby-compiler/core/y.tab.c" break; case 519: /* f_kwarg: f_kw */ -#line 3812 "mrbgems/mruby-compiler/core/parse.y" +#line 4159 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = list1((yyvsp[0].nd)); } -#line 10211 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10582 "mrbgems/mruby-compiler/core/y.tab.c" break; case 520: /* f_kwarg: f_kwarg ',' f_kw */ -#line 3816 "mrbgems/mruby-compiler/core/parse.y" +#line 4163 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); } -#line 10219 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10590 "mrbgems/mruby-compiler/core/y.tab.c" break; case 523: /* f_kwrest: kwrest_mark "local variable or method" */ -#line 3826 "mrbgems/mruby-compiler/core/parse.y" +#line 4173 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_kw_rest_args(p, (yyvsp[0].id)); + (yyval.id) = (yyvsp[0].id); } -#line 10227 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10598 "mrbgems/mruby-compiler/core/y.tab.c" break; case 524: /* f_kwrest: kwrest_mark */ -#line 3830 "mrbgems/mruby-compiler/core/parse.y" +#line 4177 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_kw_rest_args(p, 0); + (yyval.id) = intern_op(pow); } -#line 10235 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10606 "mrbgems/mruby-compiler/core/y.tab.c" break; case 525: /* args_tail: f_kwarg ',' f_kwrest opt_f_block_arg */ -#line 3836 "mrbgems/mruby-compiler/core/parse.y" +#line 4183 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_args_tail(p, (yyvsp[-3].nd), (yyvsp[-1].nd), (yyvsp[0].id)); + (yyval.nd) = new_args_tail(p, (yyvsp[-3].nd), (yyvsp[-1].id), (yyvsp[0].id)); } -#line 10243 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10614 "mrbgems/mruby-compiler/core/y.tab.c" break; case 526: /* args_tail: f_kwarg opt_f_block_arg */ -#line 3840 "mrbgems/mruby-compiler/core/parse.y" +#line 4187 "mrbgems/mruby-compiler/core/parse.y" { (yyval.nd) = new_args_tail(p, (yyvsp[-1].nd), 0, (yyvsp[0].id)); } -#line 10251 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10622 "mrbgems/mruby-compiler/core/y.tab.c" break; case 527: /* args_tail: f_kwrest opt_f_block_arg */ -#line 3844 "mrbgems/mruby-compiler/core/parse.y" +#line 4191 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = new_args_tail(p, 0, (yyvsp[-1].nd), (yyvsp[0].id)); - } -#line 10259 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 528: /* args_tail: f_block_arg */ -#line 3848 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args_tail(p, 0, 0, (yyvsp[0].id)); - } -#line 10267 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 529: /* opt_args_tail: ',' args_tail */ -#line 3854 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = (yyvsp[0].nd); - } -#line 10275 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 530: /* opt_args_tail: %empty */ -#line 3858 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args_tail(p, 0, 0, 0); - } -#line 10283 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 531: /* f_args: f_arg ',' f_optarg ',' f_rest_arg opt_args_tail */ -#line 3864 "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 10291 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 532: /* f_args: f_arg ',' f_optarg ',' f_rest_arg ',' f_arg opt_args_tail */ -#line 3868 "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 10299 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 533: /* f_args: f_arg ',' f_optarg opt_args_tail */ -#line 3872 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args(p, (yyvsp[-3].nd), (yyvsp[-1].nd), 0, 0, (yyvsp[0].nd)); - } -#line 10307 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 534: /* f_args: f_arg ',' f_optarg ',' f_arg opt_args_tail */ -#line 3876 "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 10315 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 535: /* f_args: f_arg ',' f_rest_arg opt_args_tail */ -#line 3880 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args(p, (yyvsp[-3].nd), 0, (yyvsp[-1].id), 0, (yyvsp[0].nd)); - } -#line 10323 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 536: /* f_args: f_arg ',' f_rest_arg ',' f_arg opt_args_tail */ -#line 3884 "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 10331 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 537: /* f_args: f_arg opt_args_tail */ -#line 3888 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args(p, (yyvsp[-1].nd), 0, 0, 0, (yyvsp[0].nd)); - } -#line 10339 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 538: /* f_args: f_optarg ',' f_rest_arg opt_args_tail */ -#line 3892 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args(p, 0, (yyvsp[-3].nd), (yyvsp[-1].id), 0, (yyvsp[0].nd)); - } -#line 10347 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 539: /* f_args: f_optarg ',' f_rest_arg ',' f_arg opt_args_tail */ -#line 3896 "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 10355 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 540: /* f_args: f_optarg opt_args_tail */ -#line 3900 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args(p, 0, (yyvsp[-1].nd), 0, 0, (yyvsp[0].nd)); - } -#line 10363 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 541: /* f_args: f_optarg ',' f_arg opt_args_tail */ -#line 3904 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args(p, 0, (yyvsp[-3].nd), 0, (yyvsp[-1].nd), (yyvsp[0].nd)); - } -#line 10371 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 542: /* f_args: f_rest_arg opt_args_tail */ -#line 3908 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args(p, 0, 0, (yyvsp[-1].id), 0, (yyvsp[0].nd)); - } -#line 10379 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 543: /* f_args: f_rest_arg ',' f_arg opt_args_tail */ -#line 3912 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args(p, 0, 0, (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); - } -#line 10387 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 544: /* f_args: args_tail */ -#line 3916 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_args(p, 0, 0, 0, 0, (yyvsp[0].nd)); - } -#line 10395 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 545: /* f_args: %empty */ -#line 3920 "mrbgems/mruby-compiler/core/parse.y" - { - local_add_f(p, 0); - (yyval.nd) = new_args(p, 0, 0, 0, 0, 0); - } -#line 10404 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 546: /* f_bad_arg: "constant" */ -#line 3927 "mrbgems/mruby-compiler/core/parse.y" - { - yyerror(&(yylsp[0]), p, "formal argument cannot be a constant"); - (yyval.nd) = 0; - } -#line 10413 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 547: /* f_bad_arg: "instance variable" */ -#line 3932 "mrbgems/mruby-compiler/core/parse.y" - { - yyerror(&(yylsp[0]), p, "formal argument cannot be an instance variable"); - (yyval.nd) = 0; - } -#line 10422 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 548: /* f_bad_arg: "global variable" */ -#line 3937 "mrbgems/mruby-compiler/core/parse.y" - { - yyerror(&(yylsp[0]), p, "formal argument cannot be a global variable"); - (yyval.nd) = 0; - } -#line 10431 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 549: /* f_bad_arg: "class variable" */ -#line 3942 "mrbgems/mruby-compiler/core/parse.y" - { - yyerror(&(yylsp[0]), p, "formal argument cannot be a class variable"); - (yyval.nd) = 0; - } -#line 10440 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 550: /* f_bad_arg: "numbered parameter" */ -#line 3947 "mrbgems/mruby-compiler/core/parse.y" - { - yyerror(&(yylsp[0]), p, "formal argument cannot be a numbered parameter"); - (yyval.nd) = 0; - } -#line 10449 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 551: /* f_norm_arg: f_bad_arg */ -#line 3954 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.id) = 0; - } -#line 10457 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 552: /* f_norm_arg: "local variable or method" */ -#line 3958 "mrbgems/mruby-compiler/core/parse.y" - { - local_add_f(p, (yyvsp[0].id)); - (yyval.id) = (yyvsp[0].id); - } -#line 10466 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 553: /* f_arg_item: f_norm_arg */ -#line 3965 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_arg(p, (yyvsp[0].id)); - } -#line 10474 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 554: /* @32: %empty */ -#line 3969 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = local_switch(p); - } -#line 10482 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 555: /* f_arg_item: tLPAREN @32 f_margs rparen */ -#line 3973 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = new_masgn_param(p, (yyvsp[-1].nd), p->locals->car); - local_resume(p, (yyvsp[-2].nd)); - local_add_f(p, 0); - } -#line 10492 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 556: /* f_arg: f_arg_item */ -#line 3981 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list1((yyvsp[0].nd)); - } -#line 10500 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 557: /* f_arg: f_arg ',' f_arg_item */ -#line 3985 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); - } -#line 10508 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 558: /* f_opt_asgn: "local variable or method" '=' */ -#line 3991 "mrbgems/mruby-compiler/core/parse.y" - { - local_add_f(p, (yyvsp[-1].id)); - local_nest(p); - (yyval.id) = (yyvsp[-1].id); - } -#line 10518 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 559: /* f_opt: f_opt_asgn arg */ -#line 3999 "mrbgems/mruby-compiler/core/parse.y" - { - void_expr_error(p, (yyvsp[0].nd)); - (yyval.nd) = cons(nsym((yyvsp[-1].id)), cons((yyvsp[0].nd), locals_node(p))); - local_unnest(p); - } -#line 10528 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 560: /* f_block_opt: f_opt_asgn primary_value */ -#line 4007 "mrbgems/mruby-compiler/core/parse.y" - { - void_expr_error(p, (yyvsp[0].nd)); - (yyval.nd) = cons(nsym((yyvsp[-1].id)), cons((yyvsp[0].nd), locals_node(p))); - local_unnest(p); - } -#line 10538 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 561: /* f_block_optarg: f_block_opt */ -#line 4015 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list1((yyvsp[0].nd)); - } -#line 10546 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 562: /* f_block_optarg: f_block_optarg ',' f_block_opt */ -#line 4019 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); - } -#line 10554 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 563: /* f_optarg: f_opt */ -#line 4025 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = list1((yyvsp[0].nd)); - } -#line 10562 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 564: /* f_optarg: f_optarg ',' f_opt */ -#line 4029 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); - } -#line 10570 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 567: /* f_rest_arg: restarg_mark "local variable or method" */ -#line 4039 "mrbgems/mruby-compiler/core/parse.y" - { - local_add_f(p, (yyvsp[0].id)); - (yyval.id) = (yyvsp[0].id); - } -#line 10579 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 568: /* f_rest_arg: restarg_mark */ -#line 4044 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.id) = intern_op(mul); - local_add_f(p, (yyval.id)); - } -#line 10588 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 571: /* f_block_arg: blkarg_mark "local variable or method" */ -#line 4055 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.id) = (yyvsp[0].id); - } -#line 10596 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 572: /* f_block_arg: blkarg_mark */ -#line 4059 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.id) = intern_op(and); - } -#line 10604 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 573: /* opt_f_block_arg: ',' f_block_arg */ -#line 4065 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.id) = (yyvsp[0].id); - } -#line 10612 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 574: /* opt_f_block_arg: none */ -#line 4069 "mrbgems/mruby-compiler/core/parse.y" - { - (yyval.id) = 0; - } -#line 10620 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 575: /* singleton: var_ref */ -#line 4075 "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); + (yyval.nd) = new_args_tail(p, 0, (yyvsp[-1].id), (yyvsp[0].id)); } #line 10630 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 576: /* $@33: %empty */ -#line 4080 "mrbgems/mruby-compiler/core/parse.y" - {p->lstate = EXPR_BEG;} -#line 10636 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 577: /* singleton: '(' $@33 expr rparen */ -#line 4081 "mrbgems/mruby-compiler/core/parse.y" + case 528: /* args_tail: f_block_arg */ +#line 4195 "mrbgems/mruby-compiler/core/parse.y" { - prohibit_literals(p, (yyvsp[-1].nd)); - (yyval.nd) = (yyvsp[-1].nd); + (yyval.nd) = new_args_tail(p, 0, 0, (yyvsp[0].id)); } -#line 10645 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10638 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 579: /* assoc_list: assocs trailer */ -#line 4089 "mrbgems/mruby-compiler/core/parse.y" + case 529: /* opt_args_tail: ',' args_tail */ +#line 4201 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = (yyvsp[-1].nd); + (yyval.nd) = (yyvsp[0].nd); } -#line 10653 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10646 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 580: /* assocs: assoc */ -#line 4095 "mrbgems/mruby-compiler/core/parse.y" + case 530: /* opt_args_tail: %empty */ +#line 4205 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = list1((yyvsp[0].nd)); - NODE_LINENO((yyval.nd), (yyvsp[0].nd)); + (yyval.nd) = new_args_tail(p, 0, 0, 0); + } +#line 10654 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 531: /* f_args: f_arg ',' f_optarg ',' f_rest_arg opt_args_tail */ +#line 4211 "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 10662 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 581: /* assocs: assocs comma assoc */ -#line 4100 "mrbgems/mruby-compiler/core/parse.y" + case 532: /* f_args: f_arg ',' f_optarg ',' f_rest_arg ',' f_arg opt_args_tail */ +#line 4215 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); + (yyval.nd) = new_args(p, (yyvsp[-7].nd), (yyvsp[-5].nd), (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } #line 10670 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 582: /* assoc: arg "=>" arg */ -#line 4106 "mrbgems/mruby-compiler/core/parse.y" + case 533: /* f_args: f_arg ',' f_optarg opt_args_tail */ +#line 4219 "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)); + (yyval.nd) = new_args(p, (yyvsp[-3].nd), (yyvsp[-1].nd), 0, 0, (yyvsp[0].nd)); } -#line 10680 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10678 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 583: /* assoc: "local variable or method" "label" arg */ -#line 4112 "mrbgems/mruby-compiler/core/parse.y" + case 534: /* f_args: f_arg ',' f_optarg ',' f_arg opt_args_tail */ +#line 4223 "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)); + (yyval.nd) = new_args(p, (yyvsp[-5].nd), (yyvsp[-3].nd), 0, (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 10689 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10686 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 584: /* assoc: "local variable or method" "label" */ -#line 4117 "mrbgems/mruby-compiler/core/parse.y" + case 535: /* f_args: f_arg ',' f_rest_arg opt_args_tail */ +#line 4227 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = cons(new_sym(p, (yyvsp[-1].id)), label_reference(p, (yyvsp[-1].id))); + (yyval.nd) = new_args(p, (yyvsp[-3].nd), 0, (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 10697 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10694 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 585: /* assoc: "numbered parameter" "label" */ -#line 4121 "mrbgems/mruby-compiler/core/parse.y" + case 536: /* f_args: f_arg ',' f_rest_arg ',' f_arg opt_args_tail */ +#line 4231 "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)); + (yyval.nd) = new_args(p, (yyvsp[-5].nd), 0, (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 10706 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10702 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 586: /* assoc: "numbered parameter" "label" arg */ -#line 4126 "mrbgems/mruby-compiler/core/parse.y" + case 537: /* f_args: f_arg opt_args_tail */ +#line 4235 "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)); + (yyval.nd) = new_args(p, (yyvsp[-1].nd), 0, 0, 0, (yyvsp[0].nd)); } -#line 10715 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10710 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 587: /* assoc: string_fragment "label" arg */ -#line 4131 "mrbgems/mruby-compiler/core/parse.y" + case 538: /* f_args: f_optarg ',' f_rest_arg opt_args_tail */ +#line 4239 "mrbgems/mruby-compiler/core/parse.y" { - void_expr_error(p, (yyvsp[0].nd)); - if (typen((yyvsp[-2].nd)->car) == NODE_DSTR) { - (yyval.nd) = cons(new_dsym(p, (yyvsp[-2].nd)), (yyvsp[0].nd)); - } - else { - (yyval.nd) = cons(new_sym(p, new_strsym(p, (yyvsp[-2].nd))), (yyvsp[0].nd)); - } + (yyval.nd) = new_args(p, 0, (yyvsp[-3].nd), (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 10729 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10718 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 588: /* assoc: "**" arg */ -#line 4141 "mrbgems/mruby-compiler/core/parse.y" + case 539: /* f_args: f_optarg ',' f_rest_arg ',' f_arg opt_args_tail */ +#line 4243 "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)); + (yyval.nd) = new_args(p, 0, (yyvsp[-5].nd), (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 10738 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10726 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 589: /* assoc: "**" */ -#line 4146 "mrbgems/mruby-compiler/core/parse.y" + case 540: /* f_args: f_optarg opt_args_tail */ +#line 4247 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.nd) = cons(new_kw_rest_args(p, 0), new_lvar(p, intern_op(pow))); + (yyval.nd) = new_args(p, 0, (yyvsp[-1].nd), 0, 0, (yyvsp[0].nd)); } -#line 10746 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10734 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 602: /* call_op: '.' */ -#line 4172 "mrbgems/mruby-compiler/core/parse.y" + case 541: /* f_args: f_optarg ',' f_arg opt_args_tail */ +#line 4251 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.num) = '.'; + (yyval.nd) = new_args(p, 0, (yyvsp[-3].nd), 0, (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 10754 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10742 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 603: /* call_op: "&." */ -#line 4176 "mrbgems/mruby-compiler/core/parse.y" + case 542: /* f_args: f_rest_arg opt_args_tail */ +#line 4255 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.num) = 0; + (yyval.nd) = new_args(p, 0, 0, (yyvsp[-1].id), 0, (yyvsp[0].nd)); } -#line 10762 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10750 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 605: /* call_op2: "::" */ -#line 4183 "mrbgems/mruby-compiler/core/parse.y" + case 543: /* f_args: f_rest_arg ',' f_arg opt_args_tail */ +#line 4259 "mrbgems/mruby-compiler/core/parse.y" { - (yyval.num) = tCOLON2; + (yyval.nd) = new_args(p, 0, 0, (yyvsp[-3].id), (yyvsp[-1].nd), (yyvsp[0].nd)); } -#line 10770 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10758 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 614: /* term: ';' */ -#line 4204 "mrbgems/mruby-compiler/core/parse.y" - {yyerrok;} -#line 10776 "mrbgems/mruby-compiler/core/y.tab.c" - break; - - case 616: /* nl: '\n' */ -#line 4209 "mrbgems/mruby-compiler/core/parse.y" + case 544: /* f_args: args_tail */ +#line 4263 "mrbgems/mruby-compiler/core/parse.y" { - p->lineno += (yyvsp[0].num); - p->column = 0; + (yyval.nd) = new_args(p, 0, 0, 0, 0, (yyvsp[0].nd)); } -#line 10785 "mrbgems/mruby-compiler/core/y.tab.c" +#line 10766 "mrbgems/mruby-compiler/core/y.tab.c" break; - case 620: /* none: %empty */ -#line 4221 "mrbgems/mruby-compiler/core/parse.y" + case 545: /* f_args: %empty */ +#line 4267 "mrbgems/mruby-compiler/core/parse.y" { + local_add_f(p, 0); + (yyval.nd) = new_args(p, 0, 0, 0, 0, 0); + } +#line 10775 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 546: /* f_bad_arg: "constant" */ +#line 4274 "mrbgems/mruby-compiler/core/parse.y" + { + yyerror(&(yylsp[0]), p, "formal argument cannot be a constant"); + (yyval.nd) = 0; + } +#line 10784 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 547: /* f_bad_arg: "instance variable" */ +#line 4279 "mrbgems/mruby-compiler/core/parse.y" + { + yyerror(&(yylsp[0]), p, "formal argument cannot be an instance variable"); (yyval.nd) = 0; } #line 10793 "mrbgems/mruby-compiler/core/y.tab.c" break; + case 548: /* f_bad_arg: "global variable" */ +#line 4284 "mrbgems/mruby-compiler/core/parse.y" + { + yyerror(&(yylsp[0]), p, "formal argument cannot be a global variable"); + (yyval.nd) = 0; + } +#line 10802 "mrbgems/mruby-compiler/core/y.tab.c" + break; -#line 10797 "mrbgems/mruby-compiler/core/y.tab.c" + case 549: /* f_bad_arg: "class variable" */ +#line 4289 "mrbgems/mruby-compiler/core/parse.y" + { + yyerror(&(yylsp[0]), p, "formal argument cannot be a class variable"); + (yyval.nd) = 0; + } +#line 10811 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 550: /* f_bad_arg: "numbered parameter" */ +#line 4294 "mrbgems/mruby-compiler/core/parse.y" + { + yyerror(&(yylsp[0]), p, "formal argument cannot be a numbered parameter"); + (yyval.nd) = 0; + } +#line 10820 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 551: /* f_norm_arg: f_bad_arg */ +#line 4301 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.id) = 0; + } +#line 10828 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 552: /* f_norm_arg: "local variable or method" */ +#line 4305 "mrbgems/mruby-compiler/core/parse.y" + { + local_add_f(p, (yyvsp[0].id)); + (yyval.id) = (yyvsp[0].id); + } +#line 10837 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 553: /* f_arg_item: f_norm_arg */ +#line 4312 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = new_lvar(p, (yyvsp[0].id)); + } +#line 10845 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 554: /* @32: %empty */ +#line 4316 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = local_switch(p); + } +#line 10853 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 555: /* f_arg_item: tLPAREN @32 f_margs rparen */ +#line 4320 "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 10863 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 556: /* f_arg: f_arg_item */ +#line 4328 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list1((yyvsp[0].nd)); + } +#line 10871 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 557: /* f_arg: f_arg ',' f_arg_item */ +#line 4332 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 10879 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 558: /* f_opt_asgn: "local variable or method" '=' */ +#line 4338 "mrbgems/mruby-compiler/core/parse.y" + { + local_add_f(p, (yyvsp[-1].id)); + local_nest(p); + (yyval.id) = (yyvsp[-1].id); + } +#line 10889 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 559: /* f_opt: f_opt_asgn arg */ +#line 4346 "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 10899 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 560: /* f_block_opt: f_opt_asgn primary_value */ +#line 4354 "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 10909 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 561: /* f_block_optarg: f_block_opt */ +#line 4362 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list1((yyvsp[0].nd)); + } +#line 10917 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 562: /* f_block_optarg: f_block_optarg ',' f_block_opt */ +#line 4366 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 10925 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 563: /* f_optarg: f_opt */ +#line 4372 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list1((yyvsp[0].nd)); + } +#line 10933 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 564: /* f_optarg: f_optarg ',' f_opt */ +#line 4376 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 10941 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 567: /* f_rest_arg: restarg_mark "local variable or method" */ +#line 4386 "mrbgems/mruby-compiler/core/parse.y" + { + local_add_f(p, (yyvsp[0].id)); + (yyval.id) = (yyvsp[0].id); + } +#line 10950 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 568: /* f_rest_arg: restarg_mark */ +#line 4391 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.id) = intern_op(mul); + local_add_f(p, (yyval.id)); + } +#line 10959 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 571: /* f_block_arg: blkarg_mark "local variable or method" */ +#line 4402 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.id) = (yyvsp[0].id); + } +#line 10967 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 572: /* f_block_arg: blkarg_mark */ +#line 4406 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.id) = intern_op(and); + } +#line 10975 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 573: /* opt_f_block_arg: ',' f_block_arg */ +#line 4412 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.id) = (yyvsp[0].id); + } +#line 10983 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 574: /* opt_f_block_arg: none */ +#line 4416 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.id) = 0; + } +#line 10991 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 575: /* singleton: var_ref */ +#line 4422 "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 11001 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 576: /* $@33: %empty */ +#line 4427 "mrbgems/mruby-compiler/core/parse.y" + {p->lstate = EXPR_BEG;} +#line 11007 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 577: /* singleton: '(' $@33 expr rparen */ +#line 4428 "mrbgems/mruby-compiler/core/parse.y" + { + prohibit_literals(p, (yyvsp[-1].nd)); + (yyval.nd) = (yyvsp[-1].nd); + } +#line 11016 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 579: /* assoc_list: assocs trailer */ +#line 4436 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = (yyvsp[-1].nd); + } +#line 11024 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 580: /* assocs: assoc */ +#line 4442 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = list1((yyvsp[0].nd)); + } +#line 11032 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 581: /* assocs: assocs comma assoc */ +#line 4446 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = push((yyvsp[-2].nd), (yyvsp[0].nd)); + } +#line 11040 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 582: /* assoc: arg "=>" arg */ +#line 4452 "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 11050 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 583: /* assoc: "local variable or method" "label" arg */ +#line 4458 "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 11059 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 584: /* assoc: "local variable or method" "label" */ +#line 4463 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = cons(new_sym(p, (yyvsp[-1].id)), label_reference(p, (yyvsp[-1].id))); + } +#line 11067 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 585: /* assoc: "numbered parameter" "label" */ +#line 4467 "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 11076 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 586: /* assoc: "numbered parameter" "label" arg */ +#line 4472 "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 11085 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 587: /* assoc: string_fragment "label" arg */ +#line 4477 "mrbgems/mruby-compiler/core/parse.y" + { + void_expr_error(p, (yyvsp[0].nd)); + if ((yyvsp[-2].nd)->cdr) { + /* Multiple fragments - create dynamic symbol */ + (yyval.nd) = cons(new_dsym(p, (yyvsp[-2].nd)), (yyvsp[0].nd)); + } + else if (node_to_int((yyvsp[-2].nd)->car->car) < 0) { + /* Single fragment but it's an expression (-1 . node) - create dynamic symbol */ + (yyval.nd) = cons(new_dsym(p, (yyvsp[-2].nd)), (yyvsp[0].nd)); + } + else { + /* Single string fragment - create simple symbol */ + (yyval.nd) = cons(new_sym(p, new_strsym(p, (yyvsp[-2].nd)->car)), (yyvsp[0].nd)); + } + } +#line 11105 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 588: /* assoc: "**" arg */ +#line 4493 "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 11114 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 589: /* assoc: "**" */ +#line 4498 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = cons(new_kw_rest_args(p, 0), new_lvar(p, intern_op(pow))); + } +#line 11122 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 602: /* call_op: '.' */ +#line 4524 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.num) = '.'; + } +#line 11130 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 603: /* call_op: "&." */ +#line 4528 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.num) = 0; + } +#line 11138 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 605: /* call_op2: "::" */ +#line 4535 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.num) = tCOLON2; + } +#line 11146 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 614: /* term: ';' */ +#line 4556 "mrbgems/mruby-compiler/core/parse.y" + {yyerrok;} +#line 11152 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 616: /* nl: '\n' */ +#line 4561 "mrbgems/mruby-compiler/core/parse.y" + { + p->lineno += (yyvsp[0].num); + p->column = 0; + } +#line 11161 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + case 620: /* none: %empty */ +#line 4573 "mrbgems/mruby-compiler/core/parse.y" + { + (yyval.nd) = 0; + } +#line 11169 "mrbgems/mruby-compiler/core/y.tab.c" + break; + + +#line 11173 "mrbgems/mruby-compiler/core/y.tab.c" default: break; } @@ -11026,7 +11402,7 @@ yyreturnlab: return yyresult; } -#line 4225 "mrbgems/mruby-compiler/core/parse.y" +#line 4577 "mrbgems/mruby-compiler/core/parse.y" #define pylval (*((YYSTYPE*)(p->ylval))) @@ -11114,13 +11490,13 @@ backref_error(parser_state *p, node *n) { int c; - c = intn(n->car); + c = node_to_int(n->car); if (c == NODE_NTH_REF) { - yyerror_c(p, "can't set variable $", (char)intn(n->cdr)+'0'); + yyerror_c(p, "can't set variable $", (char)node_to_int(n->cdr)+'0'); } else if (c == NODE_BACK_REF) { - yyerror_c(p, "can't set variable $", (char)intn(n->cdr)); + yyerror_c(p, "can't set variable $", (char)node_to_int(n->cdr)); } else { yyerror(NULL, p, "Internal error in backref_error()"); @@ -11130,37 +11506,56 @@ backref_error(parser_state *p, node *n) static void void_expr_error(parser_state *p, node *n) { - int c; - if (n == NULL) return; - c = intn(n->car); - switch (c) { - case NODE_BREAK: - case NODE_RETURN: - case NODE_NEXT: - case NODE_REDO: - case NODE_RETRY: - yyerror(NULL, p, "void value expression"); - break; - case NODE_AND: - case NODE_OR: - if (n->cdr) { - void_expr_error(p, n->cdr->car); - void_expr_error(p, n->cdr->cdr); - } - break; - case NODE_STMTS: - case NODE_BEGIN: - if (n->cdr) { - while (n->cdr) { - n = n->cdr; + + /* Check if this is a variable-sized node first */ + struct mrb_ast_var_header *header = (struct mrb_ast_var_header*)n; + if (header) { + /* Handle variable-sized nodes */ + switch ((enum node_type)header->node_type) { + case NODE_BREAK: + case NODE_RETURN: + case NODE_NEXT: + case NODE_REDO: + case NODE_RETRY: + yyerror(NULL, p, "void value expression"); + return; + case NODE_AND: + case NODE_OR: + { + struct mrb_ast_and_node *and_n = (struct mrb_ast_and_node*)n; + void_expr_error(p, (node*)and_n->left); + void_expr_error(p, (node*)and_n->right); } - void_expr_error(p, n->car); + return; + case NODE_STMTS: + { + struct mrb_ast_stmts_node *stmts = (struct mrb_ast_stmts_node*)n; + node *last = stmts->stmts; + if (last) { + /* Find the last statement in the cons list */ + while (last->cdr) { + last = last->cdr; + } + void_expr_error(p, last->car); + } + } + return; + case NODE_BEGIN: + { + struct mrb_ast_begin_node *begin_n = (struct mrb_ast_begin_node*)n; + if (begin_n->body) { + void_expr_error(p, (node*)begin_n->body); + } + } + return; + default: + /* Other variable-sized nodes are OK */ + return; } - break; - default: - break; } + + /* Should not reach here - all nodes should be variable-sized now */ } static void pushback(parser_state *p, int c); @@ -11194,7 +11589,7 @@ nextc(parser_state *p) if (p->pb) { node *tmp; - c = intn(p->pb->car); + c = node_to_int(p->pb->car); tmp = p->pb; p->pb = p->pb->cdr; cons_free(tmp); @@ -11230,7 +11625,7 @@ pushback(parser_state *p, int c) if (c >= 0) { p->column--; } - p->pb = cons(nint(c), p->pb); + p->pb = cons(int_to_node(c), p->pb); } static void @@ -11255,7 +11650,7 @@ peekc_n(parser_state *p, int n) c0 = nextc(p); if (c0 == -1) return c0; /* do not skip partial EOF */ if (c0 >= 0) --p->column; - list = push(list, nint(c0)); + list = push(list, int_to_node(c0)); } while(n--); if (p->pb) { p->pb = append(list, p->pb); @@ -11322,14 +11717,13 @@ skips(parser_state *p, const char *s) } return TRUE; } - else{ + else { s--; } } return FALSE; } - static int newtok(parser_state *p) { @@ -11671,8 +12065,8 @@ heredoc_remove_indent(parser_state *p, parser_heredoc_info *hinfo) while (indented) { n = indented->car; pair = n->car; - str = (char*)pair->car; - len = (size_t)pair->cdr; + len = (size_t)pair->car; + str = (char*)pair->cdr; escaped = n->cdr->car; nspaces = n->cdr->cdr; if (escaped) { @@ -11695,14 +12089,14 @@ heredoc_remove_indent(parser_state *p, parser_heredoc_info *hinfo) } if (newlen < len) newstr[newlen] = '\0'; - pair->car = (node*)newstr; - pair->cdr = (node*)newlen; + pair->car = (node*)newlen; + pair->cdr = (node*)newstr; } else { spaces = (size_t)nspaces->car; heredoc_count_indent(hinfo, str, len, spaces, &offset); - pair->car = (node*)(str + offset); - pair->cdr = (node*)(len - offset); + pair->car = (node*)(len - offset); + pair->cdr = (node*)(str + offset); } indented = indented->cdr; } @@ -11782,11 +12176,10 @@ parse_string(parser_state *p) } return 0; } - node *nd = new_str(p, tok(p), toklen(p)); - pylval.nd = nd; + pylval.nd = new_str_tok(p); if (unindent && head) { - nspaces = push(nspaces, nint(spaces)); - heredoc_push_indented(p, hinfo, nd->cdr, escaped, nspaces, empty && line_head); + nspaces = push(nspaces, int_to_node(spaces)); + heredoc_push_indented(p, hinfo, pylval.nd, escaped, nspaces, empty && line_head); } return tHD_STRING_MID; } @@ -11820,8 +12213,8 @@ parse_string(parser_state *p) p->lineno++; p->column = 0; if (unindent) { - nspaces = push(nspaces, nint(spaces)); - escaped = push(escaped, nint(pos)); + nspaces = push(nspaces, int_to_node(spaces)); + escaped = push(escaped, int_to_node(pos)); pos--; empty = TRUE; spaces = 0; @@ -11875,12 +12268,11 @@ parse_string(parser_state *p) tokfix(p); p->lstate = EXPR_BEG; p->cmd_start = TRUE; - node *nd = new_str(p, tok(p), toklen(p)); - pylval.nd = nd; + pylval.nd = new_str_tok(p); if (hinfo) { if (unindent && head) { - nspaces = push(nspaces, nint(spaces)); - heredoc_push_indented(p, hinfo, nd->cdr, escaped, nspaces, FALSE); + nspaces = push(nspaces, int_to_node(spaces)); + heredoc_push_indented(p, hinfo, pylval.nd, escaped, nspaces, FALSE); } hinfo->line_head = FALSE; return tHD_STRING_PART; @@ -11910,7 +12302,7 @@ parse_string(parser_state *p) else { pushback(p, c); tokfix(p); - pylval.nd = new_str(p, tok(p), toklen(p)); + pylval.nd = new_str_tok(p); return tSTRING_MID; } } @@ -11926,14 +12318,15 @@ parse_string(parser_state *p) end_strterm(p); if (type & STR_FUNC_XQUOTE) { - pylval.nd = new_xstr(p, tok(p), toklen(p)); + pylval.nd = new_str_tok(p); return tXSTRING; } if (type & STR_FUNC_REGEXP) { int f = 0; int re_opt; - char *s = strndup(tok(p), toklen(p)); + int pattern_len = toklen(p); + char *s = strndup(tok(p), pattern_len); char flags[3]; char *flag = flags; char enc = '\0'; @@ -11984,11 +12377,11 @@ parse_string(parser_state *p) else { encp = NULL; } - pylval.nd = new_regx(p, s, dup, encp); + pylval.nd = cons(cons(int_to_node(pattern_len), (node*)s), cons((node*)dup, (node*)encp)); return tREGEXP; } - pylval.nd = new_str(p, tok(p), toklen(p)); + pylval.nd = new_str_tok(p); return tSTRING; } @@ -12002,7 +12395,7 @@ number_literal_suffix(parser_state *p) int mask = NUM_SUFFIX_R|NUM_SUFFIX_I; while ((c = nextc(p)) != -1) { - list = push(list, nint(c)); + list = push(list, int_to_node(c)); if ((mask & NUM_SUFFIX_I) && c == 'i') { result |= (mask & NUM_SUFFIX_I); @@ -12089,8 +12482,7 @@ heredoc_identifier(parser_state *p) pushback(p, c); } tokfix(p); - newnode = new_heredoc(p); - info = (parser_heredoc_info*)newnode->cdr; + newnode = new_heredoc(p, &info); info->term = strndup(tok(p), toklen(p)); info->term_len = toklen(p); if (! quote) @@ -12482,7 +12874,7 @@ parser_yylex(parser_state *p) tokadd(p, c); } tokfix(p); - pylval.nd = new_str(p, tok(p), toklen(p)); + pylval.nd = new_str_tok(p); p->lstate = EXPR_END; return tCHAR; @@ -13318,7 +13710,7 @@ parser_yylex(parser_state *p) int nvar; if (n > 0) { - nvar = intn(p->nvars->car); + nvar = node_to_int(p->nvars->car); if (nvar != -2) { /* numbered parameters never appear on toplevel */ pylval.num = n; p->lstate = EXPR_END; @@ -13472,8 +13864,11 @@ parser_update_cxt(parser_state *p, mrb_ccontext *cxt) if (!cxt) return; if (!p->tree) return; - if (intn(p->tree->car) != NODE_SCOPE) return; - n0 = n = p->tree->cdr->car; + if (!node_type_p(p->tree, NODE_SCOPE)) return; + + /* Extract locals from variable-sized NODE_SCOPE */ + struct mrb_ast_scope_node *scope = scope_node(p->tree); + n0 = n = scope->locals; while (n) { i++; n = n->cdr; @@ -13481,11 +13876,11 @@ parser_update_cxt(parser_state *p, mrb_ccontext *cxt) cxt->syms = (mrb_sym*)mrbc_realloc(cxt->syms, i*sizeof(mrb_sym)); cxt->slen = i; for (i=0, n=n0; n; i++,n=n->cdr) { - cxt->syms[i] = sym(n->car); + cxt->syms[i] = node_to_sym(n->car); } } -void mrb_parser_dump(mrb_state *mrb, node *tree, int offset); +static void dump_node(mrb_state *mrb, node *tree, int offset); MRB_API void mrb_parser_parse(parser_state *p, mrb_ccontext *c) @@ -13511,7 +13906,7 @@ mrb_parser_parse(parser_state *p, mrb_ccontext *c) } parser_update_cxt(p, c); if (c && c->dump_result) { - mrb_parser_dump(p->mrb, p->tree, 0); + dump_node(p->mrb, p->tree, 0); } } MRB_CATCH(p->mrb->jmp) { @@ -13819,15 +14214,10 @@ mrb_load_detect_file_cxt(mrb_state *mrb, FILE *fp, mrb_ccontext *c) return mrb_load_exec(mrb, mrb_parse_file_continue(mrb, fp, leading.b, bufsize, c), c); } else { - mrb_int binsize; - uint8_t *bin; - mrb_value bin_obj = mrb_nil_value(); /* temporary string object */ - mrb_value result; - - binsize = bin_to_uint32(leading.h.binary_size); - bin_obj = mrb_str_new(mrb, NULL, binsize); - bin = (uint8_t*)RSTRING_PTR(bin_obj); - if ((size_t)binsize > bufsize) { + mrb_int binsize = bin_to_uint32(leading.h.binary_size); + mrb_value bin_obj = mrb_str_new(mrb, NULL, binsize); + uint8_t *bin = (uint8_t*)RSTRING_PTR(bin_obj); + if ((size_t)binsize > bufsize) { memcpy(bin, leading.b, bufsize); if (fread(bin + bufsize, binsize - bufsize, 1, fp) == 0) { binsize = bufsize; @@ -13835,7 +14225,7 @@ mrb_load_detect_file_cxt(mrb_state *mrb, FILE *fp, mrb_ccontext *c) } } - result = mrb_load_irep_buf_cxt(mrb, bin, binsize, c); + mrb_value result = mrb_load_irep_buf_cxt(mrb, bin, binsize, c); if (mrb_string_p(bin_obj)) mrb_str_resize(mrb, bin_obj, 0); return result; } @@ -13869,9 +14259,9 @@ mrb_load_string(mrb_state *mrb, const char *s) #ifndef MRB_NO_STDIO static void -dump_prefix(node *tree, int offset) +dump_prefix(int offset, uint16_t lineno) { - printf("%05d ", tree->lineno); + printf("%05d ", lineno); while (offset--) { putc(' ', stdout); putc(' ', stdout); @@ -13882,56 +14272,60 @@ static void dump_recur(mrb_state *mrb, node *tree, int offset) { while (tree) { - mrb_parser_dump(mrb, tree->car, offset); + dump_node(mrb, tree->car, offset); tree = tree->cdr; } } static void -dump_args(mrb_state *mrb, node *n, int offset) +dump_locals(mrb_state *mrb, node *tree, int offset, uint16_t lineno) { - if (n->car) { - dump_prefix(n, offset+1); - printf("mandatory args:\n"); - dump_recur(mrb, n->car, offset+2); - } - n = n->cdr; - if (n->car) { - dump_prefix(n, offset+1); - printf("optional args:\n"); - { - node *n2 = n->car; + if (!tree || (!tree->car && !tree->cdr)) return; - while (n2) { - dump_prefix(n2, offset+2); - printf("%s=\n", mrb_sym_name(mrb, sym(n2->car->car))); - mrb_parser_dump(mrb, n2->car->cdr, offset+3); - n2 = n2->cdr; + dump_prefix(offset, lineno); + printf("locals:\n"); + dump_prefix(offset+1, lineno); + while (tree) { + if (tree->car) { + mrb_sym sym = node_to_sym(tree->car); + if (sym != 0) { + const char *name = mrb_sym_name(mrb, sym); + if (name && strlen(name) > 0 && name[0] != '!' && name[0] != '@' && name[0] != '$') { + printf(" %s", mrb_sym_dump(mrb, sym)); + } + else { + printf(" (invalid symbol: %s)", name ? name : "(null)"); + } + } + else { + printf(" (anonymous)"); } } + tree = tree->cdr; } - n = n->cdr; - if (n->car) { - mrb_sym rest = sym(n->car); + printf("\n"); +} - dump_prefix(n, offset+1); - if (rest == MRB_OPSYM(mul)) - printf("rest=*\n"); - else - printf("rest=*%s\n", mrb_sym_name(mrb, rest)); +static void +dump_cpath(mrb_state *mrb, node *tree, int offset, uint16_t lineno) +{ + dump_prefix(offset, lineno); + printf("cpath: "); + if (!tree) { + printf("(null)\n"); } - n = n->cdr; - if (n->car) { - dump_prefix(n, offset+1); - printf("post mandatory args:\n"); - dump_recur(mrb, n->car, offset+2); + else if (node_to_int(tree->car) == 0) { + printf("(null)\n"); } - - n = n->cdr; - if (n) { - mrb_assert(intn(n->car) == NODE_ARGS_TAIL); - mrb_parser_dump(mrb, n, offset); + else if (node_to_int(tree->car) == 1) { + printf("Object\n"); } + else { + printf("\n"); + dump_node(mrb, tree->car, offset+1); + } + dump_prefix(offset, lineno); + printf("name: %s\n", mrb_sym_dump(mrb, node_to_sym(tree->cdr))); } /* @@ -13944,610 +14338,242 @@ static const char* str_dump(mrb_state *mrb, const char *str, int len) { int ai = mrb_gc_arena_save(mrb); - mrb_value s; -# if INT_MAX > MRB_INT_MAX / 4 - /* check maximum length with "\xNN" character */ - if (len > MRB_INT_MAX / 4) { - len = MRB_INT_MAX / 4; - } -# endif - s = mrb_str_new(mrb, str, (mrb_int)len); + mrb_value s = mrb_str_new(mrb, str, (mrb_int)len); s = mrb_str_dump(mrb, s); mrb_gc_arena_restore(mrb, ai); return RSTRING_PTR(s); } + +static void +dump_str(mrb_state *mrb, node *n, int offset, uint16_t lineno) +{ + while (n) { + dump_prefix(offset, lineno); + int len = node_to_int(n->car->car); + if (len >= 0) { + printf("str: %s\n", str_dump(mrb, (char*)n->car->cdr, len)); + } + else { + printf("interpolation:\n"); + dump_node(mrb, n->car->cdr, offset+1); + } + n = n->cdr; + } +} + +static void +dump_args(mrb_state *mrb, struct mrb_ast_args *args, int offset, uint16_t lineno) +{ + if (args->mandatory_args) { + dump_prefix(offset, lineno); + printf("mandatory args:\n"); + dump_recur(mrb, args->mandatory_args, offset+1); + } + if (args->optional_args) { + dump_prefix(offset, lineno); + printf("optional args:\n"); + { + node *n = args->optional_args; + while (n) { + dump_prefix(offset+1, lineno); + printf("%s=\n", mrb_sym_name(mrb, node_to_sym(n->car->car))); + dump_node(mrb, n->car->cdr, offset+2); + n = n->cdr; + } + } + } + if (args->rest_arg) { + mrb_sym rest = args->rest_arg; + + dump_prefix(offset, lineno); + if (rest == MRB_OPSYM(mul)) + printf("rest=*\n"); + else + printf("rest=*%s\n", mrb_sym_name(mrb, rest)); + } + if (args->post_mandatory_args) { + dump_prefix(offset, lineno); + printf("post mandatory args:\n"); + dump_recur(mrb, args->post_mandatory_args, offset+1); + } + if (args->keyword_args) { + dump_prefix(offset, lineno); + printf("keyword args:\n"); + { + node *n = args->keyword_args; + while (n) { + dump_prefix(offset+1, lineno); + printf("%s:\n", mrb_sym_name(mrb, node_to_sym(n->car->car))); + dump_node(mrb, n->car->cdr, offset+2); + n = n->cdr; + } + } + } + if (args->kwrest_arg) { + mrb_sym rest = args->kwrest_arg; + + dump_prefix(offset, lineno); + if (rest == MRB_OPSYM(pow)) + printf("kwrest=**\n"); + else + printf("kwrest=**%s\n", mrb_sym_name(mrb, rest)); + } + if (args->block_arg) { + mrb_sym blk = args->block_arg; + + dump_prefix(offset, lineno); + if (blk == MRB_OPSYM(and)) + printf("blk=&\n"); + else + printf("blk=&%s\n", mrb_sym_name(mrb, blk)); + } +} + +static void +dump_callargs(mrb_state *mrb, node *n, int offset, uint16_t lineno) +{ + if (!n) return; + + struct mrb_ast_callargs *args = (struct mrb_ast_callargs*)n; + if (args->regular_args) { + dump_prefix(offset+1, lineno); + printf("args:\n"); + dump_recur(mrb, args->regular_args, offset+2); + } + if (args->keyword_args) { + dump_prefix(offset+1, lineno); + printf("kw_args:\n"); + node *kw = args->keyword_args; + while (kw) { + dump_prefix(offset+2, lineno); + printf("key:\n"); + if (node_to_sym(kw->car->car) == MRB_OPSYM(pow)) { + dump_prefix(offset+3, lineno); + printf("**:\n"); + } + else { + dump_node(mrb, kw->car->car, offset+3); + } + dump_prefix(offset+2, lineno); + printf("value:\n"); + dump_node(mrb, kw->car->cdr, offset+3); + kw = kw->cdr; + } + } + if (args->block_arg) { + dump_prefix(offset+1, lineno); + printf("block:\n"); + dump_node(mrb, args->block_arg, offset+2); + } +} + #endif void -mrb_parser_dump(mrb_state *mrb, node *tree, int offset) +dump_node(mrb_state *mrb, node *tree, int offset) { #ifndef MRB_NO_STDIO - int nodetype; + enum node_type nodetype; + uint16_t lineno = 0; if (!tree) return; - again: - dump_prefix(tree, offset); - nodetype = intn(tree->car); - tree = tree->cdr; + + /* Extract line number from variable-sized node header */ + if (get_node_type(tree) != NODE_LAST) { + lineno = ((struct mrb_ast_var_header*)tree)->lineno; + } + + dump_prefix(offset, lineno); + + /* All nodes are now variable-sized nodes with headers */ + nodetype = get_node_type(tree); + switch (nodetype) { - case NODE_STMTS: - printf("NODE_STMTS:\n"); - dump_recur(mrb, tree, offset+1); - break; - - case NODE_BEGIN: - printf("NODE_BEGIN:\n"); - mrb_parser_dump(mrb, tree, offset+1); - break; - - case NODE_RESCUE: - printf("NODE_RESCUE:\n"); - if (tree->car) { - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - } - tree = tree->cdr; - if (tree->car) { - node *n2 = tree->car; - - dump_prefix(n2, offset+1); - printf("rescue:\n"); - while (n2) { - node *n3 = n2->car; - if (n3->car) { - dump_prefix(n2, offset+2); - printf("handle classes:\n"); - dump_recur(mrb, n3->car, offset+3); - } - if (n3->cdr->car) { - dump_prefix(n3, offset+2); - printf("exc_var:\n"); - mrb_parser_dump(mrb, n3->cdr->car, offset+3); - } - if (n3->cdr->cdr->car) { - dump_prefix(n3, offset+2); - printf("rescue body:\n"); - mrb_parser_dump(mrb, n3->cdr->cdr->car, offset+3); - } - n2 = n2->cdr; - } - } - tree = tree->cdr; - if (tree->car) { - dump_prefix(tree, offset+1); - printf("else:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - } - break; - - case NODE_ENSURE: - printf("NODE_ENSURE:\n"); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("ensure:\n"); - mrb_parser_dump(mrb, tree->cdr->cdr, offset+2); - break; - - case NODE_LAMBDA: - printf("NODE_LAMBDA:\n"); - dump_prefix(tree, offset+1); - goto block; - - case NODE_BLOCK: - block: - printf("NODE_BLOCK:\n"); - tree = tree->cdr; - if (tree->car) { - dump_args(mrb, tree->car, offset+1); - } - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr->car, offset+2); - break; - - case NODE_IF: - printf("NODE_IF:\n"); - dump_prefix(tree, offset+1); - printf("cond:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("then:\n"); - mrb_parser_dump(mrb, tree->cdr->car, offset+2); - if (tree->cdr->cdr->car) { - dump_prefix(tree, offset+1); - printf("else:\n"); - mrb_parser_dump(mrb, tree->cdr->cdr->car, offset+2); - } - break; - - case NODE_AND: - printf("NODE_AND:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - mrb_parser_dump(mrb, tree->cdr, offset+1); - break; - - case NODE_OR: - printf("NODE_OR:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - mrb_parser_dump(mrb, tree->cdr, offset+1); - break; - - case NODE_CASE: - printf("NODE_CASE:\n"); - if (tree->car) { - mrb_parser_dump(mrb, tree->car, offset+1); - } - tree = tree->cdr; - while (tree) { - dump_prefix(tree, offset+1); - printf("case:\n"); - dump_recur(mrb, tree->car->car, offset+2); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->car->cdr, offset+2); - tree = tree->cdr; - } - break; - - case NODE_WHILE: - printf("NODE_WHILE:\n"); - dump_prefix(tree, offset+1); - printf("cond:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_UNTIL: - printf("NODE_UNTIL:\n"); - dump_prefix(tree, offset+1); - printf("cond:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_WHILE_MOD: - printf("NODE_WHILE_MOD:\n"); - dump_prefix(tree, offset+1); - printf("cond:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_UNTIL_MOD: - printf("NODE_UNTIL_MOD:\n"); - dump_prefix(tree, offset+1); - printf("cond:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_FOR: - printf("NODE_FOR:\n"); - dump_prefix(tree, offset+1); - printf("var:\n"); - { - node *n2 = tree->car; - - if (n2->car) { - dump_prefix(n2, offset+2); - printf("pre:\n"); - dump_recur(mrb, n2->car, offset+3); - } - n2 = n2->cdr; - if (n2) { - if (n2->car) { - dump_prefix(n2, offset+2); - printf("rest:\n"); - mrb_parser_dump(mrb, n2->car, offset+3); - } - n2 = n2->cdr; - if (n2) { - if (n2->car) { - dump_prefix(n2, offset+2); - printf("post:\n"); - dump_recur(mrb, n2->car, offset+3); - } - } - } - } - tree = tree->cdr; - dump_prefix(tree, offset+1); - printf("in:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - tree = tree->cdr; - dump_prefix(tree, offset+1); - printf("do:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - break; - + /* Variable-sized node cases */ case NODE_SCOPE: printf("NODE_SCOPE:\n"); - { - node *n2 = tree->car; - mrb_bool first_lval = TRUE; - - if (n2 && (n2->car || n2->cdr)) { - dump_prefix(n2, offset+1); - printf("local variables:\n"); - dump_prefix(n2, offset+2); - while (n2) { - if (n2->car) { - if (!first_lval) printf(", "); - printf("%s", mrb_sym_name(mrb, sym(n2->car))); - first_lval = FALSE; - } - n2 = n2->cdr; - } - printf("\n"); - } + if (scope_node(tree)->locals) { + dump_locals(mrb, scope_node(tree)->locals, offset+1, lineno); } - tree = tree->cdr; - offset++; - goto again; - - case NODE_FCALL: - case NODE_CALL: - case NODE_SCALL: - switch (nodetype) { - case NODE_FCALL: - printf("NODE_FCALL:\n"); break; - case NODE_CALL: - printf("NODE_CALL(.):\n"); break; - case NODE_SCALL: - printf("NODE_SCALL(&.):\n"); break; - default: - break; + if (scope_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, scope_node(tree)->body, offset+2); } - mrb_parser_dump(mrb, tree->car, offset+1); - dump_prefix(tree, offset+1); - printf("method='%s' (%d)\n", - mrb_sym_dump(mrb, sym(tree->cdr->car)), - intn(tree->cdr->car)); - tree = tree->cdr->cdr->car; - if (tree) { - dump_prefix(tree, offset+1); - printf("args:\n"); - dump_recur(mrb, tree->car, offset+2); - if (tree->cdr) { - if (tree->cdr->car) { - dump_prefix(tree, offset+1); - printf("kwargs:\n"); - mrb_parser_dump(mrb, tree->cdr->car, offset+2); - } - if (tree->cdr->cdr) { - dump_prefix(tree, offset+1); - printf("block:\n"); - mrb_parser_dump(mrb, tree->cdr->cdr, offset+2); - } - } - } - break; - - case NODE_DOT2: - printf("NODE_DOT2:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - mrb_parser_dump(mrb, tree->cdr, offset+1); - break; - - case NODE_DOT3: - printf("NODE_DOT3:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - mrb_parser_dump(mrb, tree->cdr, offset+1); - break; - - case NODE_COLON2: - printf("NODE_COLON2:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - dump_prefix(tree, offset+1); - printf("::%s\n", mrb_sym_name(mrb, sym(tree->cdr))); - break; - - case NODE_COLON3: - printf("NODE_COLON3: ::%s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_ARRAY: - printf("NODE_ARRAY:\n"); - dump_recur(mrb, tree, offset+1); - break; - - case NODE_HASH: - printf("NODE_HASH:\n"); - while (tree) { - dump_prefix(tree, offset+1); - printf("key:\n"); - mrb_parser_dump(mrb, tree->car->car, offset+2); - dump_prefix(tree, offset+1); - printf("value:\n"); - mrb_parser_dump(mrb, tree->car->cdr, offset+2); - tree = tree->cdr; - } - break; - - case NODE_KW_HASH: - printf("NODE_KW_HASH:\n"); - while (tree) { - dump_prefix(tree, offset+1); - printf("key:\n"); - mrb_parser_dump(mrb, tree->car->car, offset+2); - dump_prefix(tree, offset+1); - printf("value:\n"); - mrb_parser_dump(mrb, tree->car->cdr, offset+2); - tree = tree->cdr; - } - break; - - case NODE_SPLAT: - printf("NODE_SPLAT:\n"); - mrb_parser_dump(mrb, tree, offset+1); - break; - - case NODE_ASGN: - printf("NODE_ASGN:\n"); - dump_prefix(tree, offset+1); - printf("lhs:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - dump_prefix(tree, offset+1); - printf("rhs:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_MASGN: - printf("NODE_MASGN:\n"); - dump_prefix(tree, offset+1); - printf("mlhs:\n"); - { - node *n2 = tree->car; - - if (n2->car) { - dump_prefix(tree, offset+2); - printf("pre:\n"); - dump_recur(mrb, n2->car, offset+3); - } - n2 = n2->cdr; - if (n2) { - if (n2->car) { - dump_prefix(n2, offset+2); - printf("rest:\n"); - if (n2->car == nint(-1)) { - dump_prefix(n2, offset+2); - printf("(empty)\n"); - } - else { - mrb_parser_dump(mrb, n2->car, offset+3); - } - } - n2 = n2->cdr; - if (n2 && n2->car) { - dump_prefix(n2, offset+2); - printf("post:\n"); - dump_recur(mrb, n2->car, offset+3); - } - } - } - dump_prefix(tree, offset+1); - printf("rhs:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - break; - - case NODE_OP_ASGN: - printf("NODE_OP_ASGN:\n"); - dump_prefix(tree, offset+1); - printf("lhs:\n"); - mrb_parser_dump(mrb, tree->car, offset+2); - tree = tree->cdr; - dump_prefix(tree, offset+1); - printf("op='%s' (%d)\n", mrb_sym_name(mrb, sym(tree->car)), intn(tree->car)); - tree = tree->cdr; - mrb_parser_dump(mrb, tree->car, offset+1); - break; - - case NODE_SUPER: - printf("NODE_SUPER:\n"); - if (tree) { - dump_prefix(tree, offset+1); - printf("args:\n"); - dump_recur(mrb, tree->car, offset+2); - if (tree->cdr) { - dump_prefix(tree, offset+1); - printf("block:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - } - } - break; - - case NODE_ZSUPER: - printf("NODE_ZSUPER:\n"); - if (tree) { - dump_prefix(tree, offset+1); - printf("args:\n"); - dump_recur(mrb, tree->car, offset+2); - if (tree->cdr) { - dump_prefix(tree, offset+1); - printf("block:\n"); - mrb_parser_dump(mrb, tree->cdr, offset+2); - } - } - break; - - case NODE_RETURN: - printf("NODE_RETURN:\n"); - mrb_parser_dump(mrb, tree, offset+1); - break; - - case NODE_YIELD: - printf("NODE_YIELD:\n"); - dump_recur(mrb, tree, offset+1); - break; - - case NODE_BREAK: - printf("NODE_BREAK:\n"); - mrb_parser_dump(mrb, tree, offset+1); - break; - - case NODE_NEXT: - printf("NODE_NEXT:\n"); - mrb_parser_dump(mrb, tree, offset+1); - break; - - case NODE_REDO: - printf("NODE_REDO\n"); - break; - - case NODE_RETRY: - printf("NODE_RETRY\n"); - break; - - case NODE_LVAR: - printf("NODE_LVAR %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_GVAR: - printf("NODE_GVAR %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_IVAR: - printf("NODE_IVAR %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_CVAR: - printf("NODE_CVAR %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_NVAR: - printf("NODE_NVAR %d\n", intn(tree)); - break; - - case NODE_CONST: - printf("NODE_CONST %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_MATCH: - printf("NODE_MATCH:\n"); - dump_prefix(tree, offset + 1); - printf("lhs:\n"); - mrb_parser_dump(mrb, tree->car, offset + 2); - dump_prefix(tree, offset + 1); - printf("rhs:\n"); - mrb_parser_dump(mrb, tree->cdr, offset + 2); - break; - - case NODE_BACK_REF: - printf("NODE_BACK_REF: $%c\n", intn(tree)); - break; - - case NODE_NTH_REF: - printf("NODE_NTH_REF: $%d\n", intn(tree)); - break; - - case NODE_ARG: - printf("NODE_ARG %s\n", mrb_sym_name(mrb, sym(tree))); - break; - - case NODE_BLOCK_ARG: - printf("NODE_BLOCK_ARG:\n"); - mrb_parser_dump(mrb, tree, offset+1); break; case NODE_INT: - printf("NODE_INT %s base %d\n", (char*)tree->car, intn(tree->cdr->car)); + printf("NODE_INT: %d\n", int_node(tree)->value); + break; + + case NODE_BIGINT: + printf("NODE_BIGINT: %s (base %d)\n", bigint_node(tree)->string, bigint_node(tree)->base); break; case NODE_FLOAT: - printf("NODE_FLOAT %s\n", (char*)tree); - break; - - case NODE_NEGATE: - printf("NODE_NEGATE:\n"); - mrb_parser_dump(mrb, tree, offset+1); + printf("NODE_FLOAT: %s\n", float_node(tree)->value); break; case NODE_STR: - printf("NODE_STR %s len %d\n", str_dump(mrb, (char*)tree->car, intn(tree->cdr)), intn(tree->cdr)); - break; - - case NODE_DSTR: - printf("NODE_DSTR:\n"); - dump_recur(mrb, tree, offset+1); + printf("NODE_STR:\n"); + dump_str(mrb, str_node(tree)->list, offset+1, lineno); break; case NODE_XSTR: - printf("NODE_XSTR %s len %d\n", str_dump(mrb, (char*)tree->car, intn(tree->cdr)), intn(tree->cdr)); - break; - - case NODE_DXSTR: - printf("NODE_DXSTR:\n"); - dump_recur(mrb, tree, offset+1); - break; - - case NODE_REGX: - printf("NODE_REGX /%s/\n", (char*)tree->car); - if (tree->cdr->car) { - dump_prefix(tree, offset+1); - printf("opt: %s\n", (char*)tree->cdr->car); - } - if (tree->cdr->cdr) { - dump_prefix(tree, offset+1); - printf("enc: %s\n", (char*)tree->cdr->cdr); - } - break; - - case NODE_DREGX: - printf("NODE_DREGX:\n"); - dump_recur(mrb, tree->car, offset+1); - dump_prefix(tree, offset+1); - printf("tail: %s\n", (char*)tree->cdr->cdr->car); - if (tree->cdr->cdr->cdr->car) { - dump_prefix(tree, offset+1); - printf("opt: %s\n", (char*)tree->cdr->cdr->cdr->car); - } - if (tree->cdr->cdr->cdr->cdr) { - dump_prefix(tree, offset+1); - printf("enc: %s\n", (char*)tree->cdr->cdr->cdr->cdr); - } + printf("NODE_XSTR:\n"); + dump_str(mrb, xstr_node(tree)->list, offset+1, lineno); break; case NODE_SYM: - printf("NODE_SYM :%s (%d)\n", mrb_sym_dump(mrb, sym(tree)), - intn(tree)); + printf("NODE_SYM: %s\n", mrb_sym_dump(mrb, sym_node(tree)->symbol)); break; case NODE_DSYM: printf("NODE_DSYM:\n"); - mrb_parser_dump(mrb, tree, offset+1); + dump_str(mrb, str_node(tree)->list, offset+1, lineno); break; - case NODE_WORDS: - printf("NODE_WORDS:\n"); - dump_recur(mrb, tree, offset+1); + case NODE_LVAR: + printf("NODE_LVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; - case NODE_SYMBOLS: - printf("NODE_SYMBOLS:\n"); - dump_recur(mrb, tree, offset+1); + case NODE_GVAR: + printf("NODE_GVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; - case NODE_LITERAL_DELIM: - printf("NODE_LITERAL_DELIM\n"); + case NODE_IVAR: + printf("NODE_IVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; - case NODE_SELF: - printf("NODE_SELF\n"); + case NODE_CVAR: + printf("NODE_CVAR: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); break; - case NODE_NIL: - printf("NODE_NIL\n"); + case NODE_NVAR: + printf("NODE_NVAR: %d\n", nvar_node(tree)->num); + break; + + case NODE_CONST: + printf("NODE_CONST: %s\n", mrb_sym_dump(mrb, var_node(tree)->symbol)); + break; + + case NODE_CALL: + printf("NODE_CALL: %s\n", mrb_sym_dump(mrb, call_node(tree)->method_name)); + if (call_node(tree)->receiver) { + dump_prefix(offset+1, lineno); + printf("receiver:\n"); + dump_node(mrb, call_node(tree)->receiver, offset+2); + } + if (call_node(tree)->args) { + dump_callargs(mrb, call_node(tree)->args, offset, lineno); + } + break; + + case NODE_ARRAY: + printf("NODE_ARRAY:\n"); + if (array_node(tree)->elements) { + dump_recur(mrb, array_node(tree)->elements, offset+1); + } break; case NODE_TRUE: @@ -14558,10 +14584,501 @@ mrb_parser_dump(mrb_state *mrb, node *tree, int offset) printf("NODE_FALSE\n"); break; + case NODE_NIL: + printf("NODE_NIL\n"); + break; + + case NODE_SELF: + printf("NODE_SELF\n"); + break; + + case NODE_IF: + printf("NODE_IF:\n"); + if (if_node(tree)->condition) { + dump_prefix(offset+1, lineno); + printf("cond:\n"); + dump_node(mrb, if_node(tree)->condition, offset+2); + } + if (if_node(tree)->then_body) { + dump_prefix(offset+1, lineno); + printf("then:\n"); + dump_node(mrb, if_node(tree)->then_body, offset+2); + } + if (if_node(tree)->else_body) { + dump_prefix(offset+1, lineno); + printf("else:\n"); + dump_node(mrb, if_node(tree)->else_body, offset+2); + } + break; + + case NODE_DEF: + printf("NODE_DEF: %s\n", mrb_sym_dump(mrb, def_node(tree)->name)); + if (def_node(tree)->args) { + dump_args(mrb, sdef_node(tree)->args, offset+1, lineno); + } + if (def_node(tree)->locals) { + dump_locals(mrb, def_node(tree)->locals, offset+1, lineno); + } + if (def_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, def_node(tree)->body, offset+2); + } + break; + + case NODE_ASGN: + printf("NODE_ASGN:\n"); + if (asgn_node(tree)->lhs) { + dump_prefix(offset+1, lineno); + printf("lhs:\n"); + dump_node(mrb, asgn_node(tree)->lhs, offset+2); + } + if (asgn_node(tree)->rhs) { + dump_prefix(offset+1, lineno); + printf("rhs:\n"); + dump_node(mrb, asgn_node(tree)->rhs, offset+2); + } + break; + + case NODE_MASGN: + case NODE_MARG: + printf("%s:\n", get_node_type(tree) == NODE_MASGN ? "NODE_MASGN" : "NODE_MARG"); + /* Handle pre-splat variables */ + if (masgn_node(tree)->pre) { + dump_prefix(offset+1, lineno); + printf("pre:\n"); + dump_recur(mrb, masgn_node(tree)->pre, offset+2); + } + /* Handle splat variable (can be -1 sentinel for anonymous splat) */ + if (masgn_node(tree)->rest) { + if ((intptr_t)masgn_node(tree)->rest == -1) { + dump_prefix(offset+1, lineno); + printf("rest: *\n"); + } + else { + dump_prefix(offset+1, lineno); + printf("rest:\n"); + dump_node(mrb, masgn_node(tree)->rest, offset+2); + } + } + /* Handle post-splat variables */ + if (masgn_node(tree)->post) { + dump_prefix(offset+1, lineno); + printf("post:\n"); + dump_recur(mrb, masgn_node(tree)->post, offset+2); + } + if (masgn_node(tree)->rhs) { + dump_prefix(offset+1, lineno); + printf("rhs:\n"); + dump_node(mrb, masgn_node(tree)->rhs, offset+2); + } + break; + + case NODE_RETURN: + printf("NODE_RETURN:\n"); + if (return_node(tree)->args) { + dump_node(mrb, return_node(tree)->args, offset); + } + break; + + case NODE_BREAK: + printf("NODE_BREAK:\n"); + if (break_node(tree)->value) { + dump_prefix(offset+1, lineno); + printf("value:\n"); + dump_node(mrb, break_node(tree)->value, offset+2); + } + break; + + case NODE_NEXT: + printf("NODE_NEXT:\n"); + if (next_node(tree)->value) { + dump_prefix(offset+1, lineno); + printf("value:\n"); + dump_node(mrb, next_node(tree)->value, offset+2); + } + break; + + case NODE_NEGATE: + printf("NODE_NEGATE:\n"); + if (negate_node(tree)->operand) { + dump_prefix(offset+1, lineno); + printf("operand:\n"); + dump_node(mrb, negate_node(tree)->operand, offset+2); + } + break; + + case NODE_STMTS: + printf("NODE_STMTS:\n"); + if (stmts_node(tree)->stmts) { + dump_recur(mrb, stmts_node(tree)->stmts, offset+1); + } + break; + + case NODE_BEGIN: + printf("NODE_BEGIN:\n"); + if (begin_node(tree)->body) { + dump_node(mrb, begin_node(tree)->body, offset+1); + } + break; + + case NODE_RESCUE: + printf("NODE_RESCUE:\n"); + if (rescue_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, rescue_node(tree)->body, offset+2); + } + if (rescue_node(tree)->rescue_clauses) { + node *n2 = rescue_node(tree)->rescue_clauses; + dump_prefix(offset+1, lineno); + printf("rescue:\n"); + while (n2) { + node *n3 = n2->car; + if (n3->car) { + dump_prefix(offset+2, lineno); + printf("handle classes:\n"); + dump_recur(mrb, n3->car, offset+3); + } + if (n3->cdr->car) { + dump_prefix(offset+2, lineno); + printf("exc_var:\n"); + dump_node(mrb, n3->cdr->car, offset+3); + } + if (n3->cdr->cdr->car) { + dump_prefix(offset+2, lineno); + printf("rescue body:\n"); + dump_node(mrb, n3->cdr->cdr->car, offset+3); + } + n2 = n2->cdr; + } + } + if (rescue_node(tree)->else_clause) { + dump_prefix(offset+1, lineno); + printf("else:\n"); + dump_node(mrb, rescue_node(tree)->else_clause, offset+2); + } + break; + + case NODE_ENSURE: + printf("NODE_ENSURE:\n"); + if (ensure_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, ensure_node(tree)->body, offset+2); + } + if (ensure_node(tree)->ensure_clause) { + dump_prefix(offset+1, lineno); + printf("ensure:\n"); + dump_node(mrb, ensure_node(tree)->ensure_clause, offset+2); + } + break; + + case NODE_LAMBDA: + printf("NODE_LAMBDA:\n"); + goto block; + + case NODE_BLOCK: + printf("NODE_BLOCK:\n"); + block: + if (block_node(tree)->locals) { + dump_locals(mrb, block_node(tree)->locals, offset+1, lineno); + } + if (block_node(tree)->args) { + dump_args(mrb, block_node(tree)->args, offset+1, lineno); + } + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, block_node(tree)->body, offset+2); + break; + + case NODE_AND: + printf("NODE_AND:\n"); + dump_node(mrb, and_node(tree)->left, offset+1); + dump_node(mrb, and_node(tree)->right, offset+1); + break; + + case NODE_OR: + printf("NODE_OR:\n"); + dump_node(mrb, or_node(tree)->left, offset+1); + dump_node(mrb, or_node(tree)->right, offset+1); + break; + + case NODE_CASE: + printf("NODE_CASE:\n"); + if (case_node(tree)->value) { + dump_prefix(offset+1, lineno); + printf("value:\n"); + dump_node(mrb, case_node(tree)->value, offset+2); + } + if (case_node(tree)->body) { + node *when_node = case_node(tree)->body; + while (when_node) { + dump_prefix(offset+1, lineno); + printf("when:\n"); + node *when_clause = when_node->car; + if (when_clause && when_clause->car) { + dump_prefix(offset+2, lineno); + printf("cond:\n"); + dump_recur(mrb, when_clause->car, offset+3); + } + if (when_clause && when_clause->cdr) { + dump_prefix(offset+2, lineno); + printf("body:\n"); + dump_node(mrb, when_clause->cdr, offset+3); + } + when_node = when_node->cdr; + } + } + break; + + case NODE_WHILE: + printf("NODE_WHILE:\n"); + goto dump_loop_node; + case NODE_UNTIL: + printf("NODE_UNTIL:\n"); + goto dump_loop_node; + case NODE_WHILE_MOD: + printf("NODE_WHILE_MOD:\n"); + goto dump_loop_node; + case NODE_UNTIL_MOD: + printf("NODE_UNTIL_MOD:\n"); + + dump_loop_node: + dump_prefix(offset+1, lineno); + printf("cond:\n"); + dump_node(mrb, while_node(tree)->condition, offset+2); + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, while_node(tree)->body, offset+2); + break; + + case NODE_FOR: + printf("NODE_FOR:\n"); + if (for_node(tree)->var) { + dump_prefix(offset+1, lineno); + printf("var:\n"); + /* FOR_NODE_VAR structure: + * var_list->car: cons-list of pre-splat variables + * var_list->cdr->car: splat varnode (not a cons-list) + * var_list->cdr->cdr->car: cons-list of post-splat variables */ + node *var_list = for_node(tree)->var; + if (var_list) { + dump_recur(mrb, var_list->car, offset+2); + if (var_list && var_list->cdr) { + /* Second element is a varnode, not a cons-list */ + dump_prefix(offset+1, lineno); + printf("splat var:\n"); + dump_node(mrb, var_list->cdr->car, offset+2); + if (var_list->cdr->cdr) { + /* Third element is a cons-list of post-splat variables */ + dump_prefix(offset+1, lineno); + printf("post var:\n"); + dump_recur(mrb, var_list->cdr->cdr->car, offset+2); + } + } + } + } + if (for_node(tree)->iterable) { + dump_prefix(offset+1, lineno); + printf("iterable:\n"); + dump_node(mrb, for_node(tree)->iterable, offset+2); + } + if (for_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, for_node(tree)->body, offset+2); + } + break; + + case NODE_DOT2: + printf("NODE_DOT2:\n"); + { + if (dot2_node(tree)->left) { + dump_prefix(offset+1, lineno); + printf("left:\n"); + dump_node(mrb, dot2_node(tree)->left, offset+2); + } + if (dot2_node(tree)->right) { + dump_prefix(offset+1, lineno); + printf("right:\n"); + dump_node(mrb, dot2_node(tree)->right, offset+2); + } + } + break; + + case NODE_DOT3: + printf("NODE_DOT3:\n"); + { + if (dot3_node(tree)->left) { + dump_prefix(offset+1, lineno); + printf("left:\n"); + dump_node(mrb, dot3_node(tree)->left, offset+2); + } + if (dot3_node(tree)->right) { + dump_prefix(offset+1, lineno); + printf("right:\n"); + dump_node(mrb, dot3_node(tree)->right, offset+2); + } + } + break; + + case NODE_COLON2: + printf("NODE_COLON2:\n"); + if (colon2_node(tree)->base) { + dump_prefix(offset+1, lineno); + printf("base:\n"); + dump_node(mrb, colon2_node(tree)->base, offset+2); + } + dump_prefix(offset+1, lineno); + printf("name: %s\n", mrb_sym_name(mrb, colon2_node(tree)->name)); + break; + + case NODE_COLON3: + printf("NODE_COLON3: ::%s\n", mrb_sym_name(mrb, colon3_node(tree)->name)); + break; + + case NODE_HASH: + printf("NODE_HASH:\n"); + { + node *pairs = hash_node(tree)->pairs; + while (pairs) { + dump_prefix(offset+1, lineno); + printf("key:\n"); + if (node_to_sym(pairs->car->car) == MRB_OPSYM(pow)) { + dump_prefix(offset+2, lineno); + printf("**\n"); + } + else { + dump_node(mrb, pairs->car->car, offset+2); + } + dump_prefix(offset+1, lineno); + printf("value:\n"); + dump_node(mrb, pairs->car->cdr, offset+2); + pairs = pairs->cdr; + } + } + break; + + case NODE_SPLAT: + printf("NODE_SPLAT:\n"); + dump_node(mrb, splat_node(tree)->value, offset+1); + break; + + case NODE_OP_ASGN: + printf("NODE_OP_ASGN:\n"); + dump_prefix(offset+1, lineno); + printf("lhs:\n"); + dump_node(mrb, op_asgn_node(tree)->lhs, offset+2); + dump_prefix(offset+1, lineno); + printf("op='%s' (%d)\n", mrb_sym_name(mrb, op_asgn_node(tree)->op), (int)op_asgn_node(tree)->op); + dump_node(mrb, op_asgn_node(tree)->rhs, offset+1); + break; + + case NODE_SUPER: + printf("NODE_SUPER:\n"); + if (super_node(tree)->args) { + dump_callargs(mrb, super_node(tree)->args, offset, lineno); + } + break; + + case NODE_ZSUPER: + printf("NODE_ZSUPER:\n"); + if (super_node(tree)->args) { + dump_callargs(mrb, super_node(tree)->args, offset, lineno); + } + break; + + case NODE_YIELD: + printf("NODE_YIELD:\n"); + if (yield_node(tree)->args) { + dump_callargs(mrb, yield_node(tree)->args, offset, lineno); + } + break; + + case NODE_REDO: + printf("NODE_REDO\n"); + break; + + case NODE_RETRY: + printf("NODE_RETRY\n"); + break; + + case NODE_BACK_REF: + printf("NODE_BACK_REF: $%c\n", node_to_int(tree)); + break; + + case NODE_NTH_REF: + printf("NODE_NTH_REF: $%d\n", node_to_int(tree)); + break; + + case NODE_BLOCK_ARG: + printf("NODE_BLOCK_ARG:\n"); + dump_node(mrb, block_arg_node(tree)->value, offset+1); + break; + + case NODE_REGX: + printf("NODE_REGX:\n"); + if (regx_node(tree)->list) { + dump_str(mrb, regx_node(tree)->list, offset+1, lineno); + } + if (regx_node(tree)->flags) { + dump_prefix(offset+1, lineno); + printf("flags: %s\n", regx_node(tree)->flags); + } + if (regx_node(tree)->encoding) { + dump_prefix(offset+1, lineno); + printf("encoding: %s\n", regx_node(tree)->encoding); + } + break; + + case NODE_WORDS: + printf("NODE_WORDS:\n"); + if (words_node(tree)->args) { + node *list = words_node(tree)->args; + while (list && list->car) { + node *item = list->car; + if (item->car == 0 && item->cdr == 0) { + /* Skip separator (0 . 0) */ + } + else if (item->car && item->cdr) { + /* String item: (len . str) */ + dump_prefix(offset+1, lineno); + int len = node_to_int(item->car); + if (len >= 0 && len < 1000 && item->cdr) { + printf("word: \"%.*s\"\n", len, (char*)item->cdr); + } + } + list = list->cdr; + } + } + break; + + case NODE_SYMBOLS: + printf("NODE_SYMBOLS:\n"); + if (symbols_node(tree)->args) { + node *list = symbols_node(tree)->args; + while (list && list->car) { + node *item = list->car; + if (item->car == 0 && item->cdr == 0) { + /* Skip separator (0 . 0) */ + } else if (item->car && item->cdr) { + /* String item: (len . str) */ + dump_prefix(offset+1, lineno); + int len = node_to_int(item->car); + if (len >= 0 && len < 1000 && item->cdr) { + printf("symbol: \"%.*s\"\n", len, (char*)item->cdr); + } + } + list = list->cdr; + } + } + break; + case NODE_ALIAS: printf("NODE_ALIAS %s %s:\n", - mrb_sym_dump(mrb, sym(tree->car)), - mrb_sym_dump(mrb, sym(tree->cdr))); + mrb_sym_dump(mrb, node_to_sym(tree->car)), + mrb_sym_dump(mrb, node_to_sym(tree->cdr))); break; case NODE_UNDEF: @@ -14569,7 +15086,7 @@ mrb_parser_dump(mrb_state *mrb, node *tree, int offset) { node *t = tree; while (t) { - printf(" %s", mrb_sym_dump(mrb, sym(t->car))); + printf(" %s", mrb_sym_dump(mrb, node_to_sym(t->car))); t = t->cdr; } } @@ -14578,153 +15095,107 @@ mrb_parser_dump(mrb_state *mrb, node *tree, int offset) case NODE_CLASS: printf("NODE_CLASS:\n"); - if (tree->car->car == nint(0)) { - dump_prefix(tree, offset+1); - printf(":%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); + if (class_node(tree)->name) { + dump_cpath(mrb, module_node(tree)->name, offset+1, lineno); } - else if (tree->car->car == nint(1)) { - dump_prefix(tree, offset+1); - printf("::%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); - } - else { - mrb_parser_dump(mrb, tree->car->car, offset+1); - dump_prefix(tree, offset+1); - printf("::%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); - } - if (tree->cdr->car) { - dump_prefix(tree, offset+1); + if (class_node(tree)->superclass) { + dump_prefix(offset+1, lineno); printf("super:\n"); - mrb_parser_dump(mrb, tree->cdr->car, offset+2); + dump_node(mrb, class_node(tree)->superclass, offset+2); + } + if (class_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, class_node(tree)->body->cdr, offset+2); } - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr->cdr->car->cdr, offset+2); break; case NODE_MODULE: printf("NODE_MODULE:\n"); - if (tree->car->car == nint(0)) { - dump_prefix(tree, offset+1); - printf(":%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); + if (module_node(tree)->name) { + dump_cpath(mrb, module_node(tree)->name, offset+1, lineno); } - else if (tree->car->car == nint(1)) { - dump_prefix(tree, offset+1); - printf("::%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); + if (module_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, module_node(tree)->body->cdr, offset+2); } - else { - mrb_parser_dump(mrb, tree->car->car, offset+1); - dump_prefix(tree, offset+1); - printf("::%s\n", mrb_sym_name(mrb, sym(tree->car->cdr))); - } - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr->car->cdr, offset+2); break; case NODE_SCLASS: printf("NODE_SCLASS:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - dump_prefix(tree, offset+1); - printf("body:\n"); - mrb_parser_dump(mrb, tree->cdr->car->cdr, offset+2); - break; - - case NODE_DEF: - printf("NODE_DEF:\n"); - dump_prefix(tree, offset+1); - printf("%s\n", mrb_sym_dump(mrb, sym(tree->car))); - tree = tree->cdr; - { - node *n2 = tree->car; - mrb_bool first_lval = TRUE; - - if (n2 && (n2->car || n2->cdr)) { - dump_prefix(n2, offset+1); - printf("local variables:\n"); - dump_prefix(n2, offset+2); - while (n2) { - if (n2->car) { - if (!first_lval) printf(", "); - printf("%s", mrb_sym_name(mrb, sym(n2->car))); - first_lval = FALSE; - } - n2 = n2->cdr; - } - printf("\n"); - } + if (sclass_node(tree)->obj) { + dump_prefix(offset+1, lineno); + printf("obj:\n"); + dump_node(mrb, sclass_node(tree)->obj, offset+2); } - tree = tree->cdr; - if (tree->car) { - dump_args(mrb, tree->car, offset); + if (sclass_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, sclass_node(tree)->body->cdr, offset+2); } - mrb_parser_dump(mrb, tree->cdr->car, offset+1); break; case NODE_SDEF: - printf("NODE_SDEF:\n"); - mrb_parser_dump(mrb, tree->car, offset+1); - tree = tree->cdr; - dump_prefix(tree, offset+1); - printf(":%s\n", mrb_sym_dump(mrb, sym(tree->car))); - tree = tree->cdr->cdr; - if (tree->car) { - dump_args(mrb, tree->car, offset+1); + printf("NODE_SDEF: %s\n", mrb_sym_dump(mrb, def_node(tree)->name)); + if (sdef_node(tree)->obj) { + dump_prefix(offset+1, lineno); + printf("recv:\n"); + dump_node(mrb, sdef_node(tree)->obj, offset+2); + } + if (sdef_node(tree)->args) { + dump_args(mrb, sdef_node(tree)->args, offset+1, lineno); + } + if (sdef_node(tree)->locals) { + dump_locals(mrb, sdef_node(tree)->locals, offset+1, lineno); + } + if (sdef_node(tree)->body) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_node(mrb, sdef_node(tree)->body, offset+2); } - tree = tree->cdr; - mrb_parser_dump(mrb, tree->car, offset+1); break; case NODE_POSTEXE: printf("NODE_POSTEXE:\n"); - mrb_parser_dump(mrb, tree, offset+1); + dump_node(mrb, tree, offset+1); break; case NODE_HEREDOC: - printf("NODE_HEREDOC (<<%s):\n", ((parser_heredoc_info*)tree)->term); - dump_recur(mrb, ((parser_heredoc_info*)tree)->doc, offset+1); - break; - - case NODE_ARGS_TAIL: - printf("NODE_ARGS_TAIL:\n"); - { - node *kws = tree->car; - - while (kws) { - mrb_parser_dump(mrb, kws->car, offset+1); - kws = kws->cdr; - } + printf("NODE_HEREDOC:\n"); + if (heredoc_node(tree)->info.term) { + dump_prefix(offset+1, lineno); + printf("terminator: \"%s\"\n", heredoc_node(tree)->info.term); } - tree = tree->cdr; - if (tree->car) { - mrb_assert(intn(tree->car->car) == NODE_KW_REST_ARGS); - mrb_parser_dump(mrb, tree->car, offset+1); + if (heredoc_node(tree)->info.doc) { + dump_prefix(offset+1, lineno); + printf("body:\n"); + dump_str(mrb, heredoc_node(tree)->info.doc, offset+2, lineno); } - tree = tree->cdr; - if (tree->car) { - dump_prefix(tree, offset+1); - printf("block='%s'\n", mrb_sym_name(mrb, sym(tree->car))); + if (heredoc_node(tree)->info.allow_indent) { + dump_prefix(offset+1, lineno); + printf("allow_indent: true\n"); + } + if (heredoc_node(tree)->info.remove_indent) { + dump_prefix(offset+1, lineno); + printf("remove_indent: true\n"); } - break; - - case NODE_KW_ARG: - printf("NODE_KW_ARG %s:\n", mrb_sym_name(mrb, sym(tree->car))); - mrb_parser_dump(mrb, tree->cdr->car, offset + 1); - break; - - case NODE_KW_REST_ARGS: - if (tree) - printf("NODE_KW_REST_ARGS %s\n", mrb_sym_name(mrb, sym(tree))); - else - printf("NODE_KW_REST_ARGS\n"); break; default: - printf("node type: %d (0x%x)\n", nodetype, (unsigned)nodetype); + /* Fallback: unknown node type - skip like codegen.c does */ + printf("unknown node type %d (0x%x)\n", nodetype, (unsigned)nodetype); break; } #endif } +void +mrb_parser_dump(mrb_state *mrb, node *tree, int offset) +{ + dump_node(mrb, tree, offset); +} + typedef mrb_bool mrb_parser_foreach_top_variable_func(mrb_state *mrb, mrb_sym sym, void *user); void mrb_parser_foreach_top_variable(mrb_state *mrb, struct mrb_parser_state *p, mrb_parser_foreach_top_variable_func *func, void *user); @@ -14732,11 +15203,15 @@ void mrb_parser_foreach_top_variable(mrb_state *mrb, struct mrb_parser_state *p, mrb_parser_foreach_top_variable_func *func, void *user) { const mrb_ast_node *n = p->tree; - if ((intptr_t)n->car == NODE_SCOPE) { - n = n->cdr->car; + if (node_type_p((node*)n, NODE_SCOPE)) { + /* Extract locals from variable-sized NODE_SCOPE */ + struct mrb_ast_scope_node *scope = scope_node(n); + n = scope->locals; for (; n; n = n->cdr) { - mrb_sym sym = sym(n->car); - if (sym && !func(mrb, sym, user)) break; + mrb_sym sym = node_to_sym(n->car); + if (sym != 0) { + if (!func(mrb, sym, user)) break; + } } } } diff --git a/mrbgems/mruby-compiler/mrbgem.rake b/mrbgems/mruby-compiler/mrbgem.rake index c5e1bd539..bd83c8bc4 100644 --- a/mrbgems/mruby-compiler/mrbgem.rake +++ b/mrbgems/mruby-compiler/mrbgem.rake @@ -3,15 +3,17 @@ MRuby::Gem::Specification.new 'mruby-compiler' do |spec| spec.author = 'mruby developers' spec.summary = 'mruby compiler library' - objs = %w[codegen y.tab].map do |name| - src = "#{dir}/core/#{name}.c" - if build.cxx_exception_enabled? - build.compile_as_cxx(src) - else - objfile(src.pathmap("#{build_dir}/core/%n")) + spec.build_settings do + objs = %w[codegen y.tab].map do |name| + src = "#{dir}/core/#{name}.c" + if build.cxx_exception_enabled? + build.compile_as_cxx(src) + else + objfile(src.pathmap("#{build_dir}/core/%n")) + end end + build.libmruby_core_objs << objs end - build.libmruby_core_objs << objs end dir = __dir__ diff --git a/mrbgems/mruby-complex/mrblib/complex.rb b/mrbgems/mruby-complex/mrblib/complex.rb index 44b218024..8d58c334a 100644 --- a/mrbgems/mruby-complex/mrblib/complex.rb +++ b/mrbgems/mruby-complex/mrblib/complex.rb @@ -1,24 +1,84 @@ class Complex < Numeric + # + # call-seq: + # Complex.polar(abs [, arg]) -> complex + # + # Returns a complex number in terms of its polar coordinates. + # abs is the absolute value (magnitude) and arg is the argument (angle). + # + # Complex.polar(3, 0) #=> (3+0i) + # Complex.polar(3, Math::PI/2) #=> (1.836909530733566e-16+3.0i) + # Complex.polar(3, Math::PI) #=> (-3.0+3.673819061467132e-16i) + # def self.polar(abs, arg = 0) Complex(abs * Math.cos(arg), abs * Math.sin(arg)) end + # + # call-seq: + # cmp.inspect -> string + # + # Returns the value as a string for inspection. + # + # Complex(2).inspect #=> "(2+0i)" + # Complex(-8, 6).inspect #=> "(-8+6i)" + # Complex(1, 2).inspect #=> "(1+2i)" + # def inspect "(#{to_s})" end + # + # call-seq: + # cmp.to_s -> string + # + # Returns the value as a string. + # + # Complex(2).to_s #=> "2+0i" + # Complex(-8, 6).to_s #=> "-8+6i" + # Complex(1, -2).to_s #=> "1-2i" + # def to_s "#{real}#{'+' unless imaginary < 0}#{imaginary}#{'*' unless imaginary.finite?}i" end + # + # call-seq: + # +cmp -> cmp + # + # Returns self. + # + # +Complex(1, 2) #=> (1+2i) + # def +@ self end + # + # call-seq: + # -cmp -> complex + # + # Returns the negation of self. + # + # -Complex(1, 2) #=> (-1-2i) + # -Complex(-1, 2) #=> (1-2i) + # def -@ Complex(-real, -imaginary) end + # + # call-seq: + # cmp <=> numeric -> -1, 0, +1, or nil + # + # Returns -1, 0, or +1 depending on whether cmp is less than, equal to, + # or greater than numeric. This is the basis for the tests in the Comparable module. + # Returns nil if the two values are incomparable. + # + # Complex(2, 3) <=> Complex(2, 3) #=> 0 + # Complex(5) <=> 5 #=> 0 + # Complex(2, 3) <=> 1 #=> 1 + # def <=>(other) return nil unless other.kind_of?(Numeric) self.to_f <=> other.to_f @@ -26,47 +86,137 @@ class Complex < Numeric nil end + # + # call-seq: + # cmp.abs -> real + # cmp.magnitude -> real + # + # Returns the absolute part of its polar form. + # + # Complex(-1).abs #=> 1.0 + # Complex(3.0, -4.0).abs #=> 5.0 + # def abs Math.hypot imaginary, real end alias_method :magnitude, :abs + # + # call-seq: + # cmp.abs2 -> real + # + # Returns square of the absolute value. + # + # Complex(-1).abs2 #=> 1 + # Complex(3.0, -4.0).abs2 #=> 25.0 + # def abs2 real * real + imaginary * imaginary end + # + # call-seq: + # cmp.arg -> float + # cmp.angle -> float + # cmp.phase -> float + # + # Returns the angle part of its polar form. + # + # Complex.polar(3, Math::PI/2).arg #=> 1.5707963267948966 + # def arg Math.atan2 imaginary, real end alias_method :angle, :arg alias_method :phase, :arg + # + # call-seq: + # cmp.conjugate -> complex + # cmp.conj -> complex + # + # Returns the complex conjugate. + # + # Complex(1, 2).conjugate #=> (1-2i) + # def conjugate Complex(real, -imaginary) end alias_method :conj, :conjugate + # + # call-seq: + # cmp.fdiv(numeric) -> complex + # + # Performs division as each part is a float, even if the parts are not floats. + # + # Complex(11, 22).fdiv(3) #=> (3.6666666666666665+7.333333333333333i) + # def fdiv(numeric) Complex(real / numeric, imaginary / numeric) end + # + # call-seq: + # cmp.polar -> array + # + # Returns an array; [cmp.abs, cmp.arg]. + # + # Complex(1, 2).polar #=> [2.23606797749979, 1.1071487177940904] + # def polar [abs, arg] end + # + # call-seq: + # cmp.real? -> false + # + # Returns false. + # + # Complex(1).real? #=> false + # def real? false end + # + # call-seq: + # cmp.rectangular -> array + # cmp.rect -> array + # + # Returns an array; [cmp.real, cmp.imag]. + # + # Complex(1, 2).rectangular #=> [1, 2] + # def rectangular [real, imaginary] end alias_method :rect, :rectangular + # + # call-seq: + # cmp.to_c -> cmp + # + # Returns self. + # + # Complex(2).to_c #=> (2+0i) + # Complex(-8, 6).to_c #=> (-8+6i) + # def to_c self end + # + # call-seq: + # cmp.to_r -> rational + # + # Returns the value as a rational if possible (the imaginary part should be exactly zero). + # + # Complex(1, 0).to_r #=> (1/1) + # Complex(1, 0.0).to_r #=> (1/1) + # Complex(1, 2).to_r #=> RangeError + # def to_r raise RangeError.new "can't convert #{to_s} into Rational" unless imaginary.zero? Rational(real, 1) @@ -75,6 +225,15 @@ class Complex < Numeric alias_method :imag, :imaginary Numeric.class_eval do + # + # call-seq: + # num.i -> complex + # + # Returns the Complex object created from this number and i (0+num*i). + # + # -42.i #=> (0-42i) + # 2.0.i #=> (0+2.0i) + # def i Complex(0, self) end @@ -83,6 +242,17 @@ class Complex < Numeric end class Numeric + # + # call-seq: + # num.to_c -> complex + # + # Returns the value as a complex. + # + # 1.to_c #=> (1+0i) + # -1.to_c #=> (-1+0i) + # 1.0.to_c #=> (1.0+0i) + # 3.14159.to_c #=> (3.14159+0i) + # def to_c Complex(self, 0) end diff --git a/mrbgems/mruby-complex/src/complex.c b/mrbgems/mruby-complex/src/complex.c index 03e058f71..b1d3d1b50 100644 --- a/mrbgems/mruby-complex/src/complex.c +++ b/mrbgems/mruby-complex/src/complex.c @@ -3,6 +3,7 @@ #include #include #include +#include #ifdef MRB_NO_FLOAT # error Complex conflicts with 'MRB_NO_FLOAT' configuration @@ -94,6 +95,15 @@ mrb_complex_copy(mrb_state *mrb, mrb_value x, mrb_value y) p1->imaginary = p2->imaginary; } +/* + * call-seq: + * complex.real -> float + * + * Returns the real part of the complex number. + * + * Complex(3, 4).real #=> 3.0 + * Complex(-1).real #=> -1.0 + */ static mrb_value complex_real(mrb_state *mrb, mrb_value self) { @@ -101,6 +111,16 @@ complex_real(mrb_state *mrb, mrb_value self) return mrb_float_value(mrb, p->real); } +/* + * call-seq: + * complex.imaginary -> float + * complex.imag -> float + * + * Returns the imaginary part of the complex number. + * + * Complex(3, 4).imaginary #=> 4.0 + * Complex(5).imag #=> 0.0 + */ static mrb_value complex_imaginary(mrb_state *mrb, mrb_value self) { @@ -108,6 +128,19 @@ complex_imaginary(mrb_state *mrb, mrb_value self) return mrb_float_value(mrb, p->imaginary); } +/* + * call-seq: + * Complex.rectangular(real, imag = 0) -> complex + * Complex.rect(real, imag = 0) -> complex + * Complex(real, imag = 0) -> complex + * + * Returns a complex number with the given real and imaginary parts. + * The imaginary part defaults to 0 if not specified. + * + * Complex.rectangular(1, 2) #=> (1+2i) + * Complex.rect(3) #=> (3+0i) + * Complex(1, -1) #=> (1-1i) + */ static mrb_value complex_s_rect(mrb_state *mrb, mrb_value self) { @@ -117,6 +150,16 @@ complex_s_rect(mrb_state *mrb, mrb_value self) return complex_new(mrb, real, imaginary); } +/* + * call-seq: + * complex.to_f -> float + * + * Returns the real part of the complex number as a float. + * Raises RangeError if the imaginary part is not zero. + * + * Complex(3, 0).to_f #=> 3.0 + * Complex(3, 4).to_f #=> RangeError: can't convert (3+4i) into Float + */ mrb_value mrb_complex_to_f(mrb_state *mrb, mrb_value self) { @@ -129,6 +172,16 @@ mrb_complex_to_f(mrb_state *mrb, mrb_value self) return mrb_float_value(mrb, p->real); } +/* + * call-seq: + * complex.to_i -> integer + * + * Returns the real part of the complex number as an integer. + * Raises RangeError if the imaginary part is not zero. + * + * Complex(3, 0).to_i #=> 3 + * Complex(3, 4).to_i #=> RangeError: can't convert (3+4i) into Integer + */ mrb_value mrb_complex_to_i(mrb_state *mrb, mrb_value self) { @@ -176,6 +229,17 @@ mrb_complex_eq(mrb_state *mrb, mrb_value x, mrb_value y) } } +/* + * call-seq: + * complex == object -> true or false + * + * Returns true if complex equals object. Two complex numbers are equal + * if their real and imaginary parts are equal. + * + * Complex(1, 2) == Complex(1, 2) #=> true + * Complex(1, 2) == Complex(2, 1) #=> false + * Complex(1, 0) == 1 #=> true + */ static mrb_value complex_eq(mrb_state *mrb, mrb_value x) { @@ -183,26 +247,54 @@ complex_eq(mrb_state *mrb, mrb_value x) return mrb_bool_value(mrb_complex_eq(mrb, x, y)); } +static mrb_value +complex_op(mrb_state *mrb, mrb_value x, mrb_value y, char op) +{ + struct mrb_complex *p1 = complex_ptr(mrb, x); + mrb_float r, i; + + switch (mrb_type(y)) { + case MRB_TT_COMPLEX: { + struct mrb_complex *p2 = complex_ptr(mrb, y); + r = p2->real; + i = p2->imaginary; + break; + } + default: { + r = mrb_as_float(mrb, y); + i = 0; + break; + } + } + + switch (op) { + case '+': + return mrb_complex_new(mrb, p1->real + r, p1->imaginary + i); + case '-': + return mrb_complex_new(mrb, p1->real - r, p1->imaginary - i); + case '*': + return mrb_complex_new(mrb, p1->real * r - p1->imaginary * i, p1->real * i + p1->imaginary * r); + } + return mrb_nil_value(); /* should not happen */ +} + mrb_value mrb_complex_add(mrb_state *mrb, mrb_value x, mrb_value y) { - struct mrb_complex *p1 = complex_ptr(mrb, x); - - switch (mrb_type(y)) { - case MRB_TT_COMPLEX: - { - struct mrb_complex *p2 = complex_ptr(mrb, y); - return mrb_complex_new(mrb, p1->real+p2->real, p1->imaginary+p2->imaginary); - } - - default: - { - mrb_float z = mrb_as_float(mrb, y); - return mrb_complex_new(mrb, p1->real+z, p1->imaginary); - } - } + return complex_op(mrb, x, y, '+'); } +/* + * call-seq: + * complex + numeric -> complex + * + * Returns the sum of complex and numeric. If numeric is a complex number, + * adds both real and imaginary parts. If numeric is real, adds only to + * the real part. + * + * Complex(1, 2) + Complex(3, 4) #=> (4+6i) + * Complex(1, 2) + 3 #=> (4+2i) + */ static mrb_value complex_add(mrb_state *mrb, mrb_value x) { @@ -213,23 +305,20 @@ complex_add(mrb_state *mrb, mrb_value x) mrb_value mrb_complex_sub(mrb_state *mrb, mrb_value x, mrb_value y) { - struct mrb_complex *p1 = complex_ptr(mrb, x); - - switch (mrb_type(y)) { - case MRB_TT_COMPLEX: - { - struct mrb_complex *p2 = complex_ptr(mrb, y); - return mrb_complex_new(mrb, p1->real-p2->real, p1->imaginary-p2->imaginary); - } - - default: - { - mrb_float z = mrb_as_float(mrb, y); - return mrb_complex_new(mrb, p1->real-z, p1->imaginary); - } - } + return complex_op(mrb, x, y, '-'); } +/* + * call-seq: + * complex - numeric -> complex + * + * Returns the difference of complex and numeric. If numeric is a complex number, + * subtracts both real and imaginary parts. If numeric is real, subtracts only + * from the real part. + * + * Complex(5, 6) - Complex(1, 2) #=> (4+4i) + * Complex(5, 6) - 2 #=> (3+6i) + */ static mrb_value complex_sub(mrb_state *mrb, mrb_value x) { @@ -240,24 +329,19 @@ complex_sub(mrb_state *mrb, mrb_value x) mrb_value mrb_complex_mul(mrb_state *mrb, mrb_value x, mrb_value y) { - struct mrb_complex *p1 = complex_ptr(mrb, x); - - switch (mrb_type(y)) { - case MRB_TT_COMPLEX: - { - struct mrb_complex *p2 = complex_ptr(mrb, y); - return mrb_complex_new(mrb, p1->real*p2->real - p1->imaginary*p2->imaginary, - p1->real*p2->imaginary + p2->real*p1->imaginary); - } - - default: - { - mrb_float z = mrb_as_float(mrb, y); - return mrb_complex_new(mrb, p1->real*z, p1->imaginary*z); - } - } + return complex_op(mrb, x, y, '*'); } +/* + * call-seq: + * complex * numeric -> complex + * + * Returns the product of complex and numeric. Uses the standard complex + * multiplication formula: (a+bi) * (c+di) = (ac-bd) + (ad+bc)i + * + * Complex(1, 2) * Complex(3, 4) #=> (-5+10i) + * Complex(1, 2) * 3 #=> (3+6i) + */ static mrb_value complex_mul(mrb_state *mrb, mrb_value x) { @@ -312,6 +396,7 @@ mrb_value mrb_complex_div(mrb_state *mrb, mrb_value self, mrb_value rhs) { struct mrb_complex *a, *b; + mrb_float r, den; a = complex_ptr(mrb, self); if (mrb_type(rhs) != MRB_TT_COMPLEX) { @@ -319,49 +404,76 @@ mrb_complex_div(mrb_state *mrb, mrb_value self, mrb_value rhs) mrb_int_zerodiv(mrb); } mrb_float f = mrb_as_float(mrb, rhs); + if (f == 0.0) { + mrb_int_zerodiv(mrb); + } return complex_new(mrb, mrb_div_float(a->real, f), mrb_div_float(a->imaginary, f)); } - struct float_pair ar, ai, br, bi; - struct float_pair br2, bi2; - struct float_pair div; - struct float_pair ar_br, ai_bi; - struct float_pair ai_br, ar_bi; - struct float_pair zr, zi; - b = complex_ptr(mrb, rhs); + if (b->real == 0 && b->imaginary == 0) { + mrb_int_zerodiv(mrb); + } - /* Split floating-point components into significand and exponent */ - ar.s = F(frexp)(a->real, &ar.x); - ai.s = F(frexp)(a->imaginary, &ai.x); - br.s = F(frexp)(b->real, &br.x); - bi.s = F(frexp)(b->imaginary, &bi.x); + mrb_float br = b->real; + mrb_float bi = b->imaginary; - /* Perform arithmetic on (significand, exponent) pairs to produce - the result: */ + if (F(fabs)(br) < DBL_MIN * F(fabs)(bi) && F(fabs)(bi) < DBL_MIN * F(fabs)(br)) { + /* Fallback to frexp/ldexp for extreme values */ + struct float_pair ar_p, ai_p, br_p, bi_p; + struct float_pair br2_p, bi2_p; + struct float_pair div_p; + struct float_pair ar_br_p, ai_bi_p; + struct float_pair ai_br_p, ar_bi_p; + struct float_pair zr_p, zi_p; - /* the divisor */ - mul_pair(&br2, &br, &br); - mul_pair(&bi2, &bi, &bi); - add_pair(&div, &br2, &bi2); + ar_p.s = F(frexp)(a->real, &ar_p.x); + ai_p.s = F(frexp)(a->imaginary, &ai_p.x); + br_p.s = F(frexp)(br, &br_p.x); + bi_p.s = F(frexp)(bi, &bi_p.x); - /* real component */ - mul_pair(&ar_br, &ar, &br); - mul_pair(&ai_bi, &ai, &bi); - add_pair(&zr, &ar_br, &ai_bi); - div_pair(&zr, &zr, &div); + mul_pair(&br2_p, &br_p, &br_p); + mul_pair(&bi2_p, &bi_p, &bi_p); + add_pair(&div_p, &br2_p, &bi2_p); - /* imaginary component */ - mul_pair(&ai_br, &ai, &br); - mul_pair(&ar_bi, &ar, &bi); - ar_bi.s = -ar_bi.s; - add_pair(&zi, &ai_br, &ar_bi); - div_pair(&zi, &zi, &div); + mul_pair(&ar_br_p, &ar_p, &br_p); + mul_pair(&ai_bi_p, &ai_p, &bi_p); + add_pair(&zr_p, &ar_br_p, &ai_bi_p); + div_pair(&zr_p, &zr_p, &div_p); - /* assemble the result */ - return complex_new(mrb, F(ldexp)(zr.s, zr.x), F(ldexp)(zi.s, zi.x)); + mul_pair(&ai_br_p, &ai_p, &br_p); + mul_pair(&ar_bi_p, &ar_p, &bi_p); + ar_bi_p.s = -ar_bi_p.s; + add_pair(&zi_p, &ai_br_p, &ar_bi_p); + div_pair(&zi_p, &zi_p, &div_p); + + return complex_new(mrb, F(ldexp)(zr_p.s, zr_p.x), F(ldexp)(zi_p.s, zi_p.x)); + } + else { + if (F(fabs)(br) > F(fabs)(bi)) { + r = bi / br; + den = br + r * bi; + return complex_new(mrb, (a->real + a->imaginary * r) / den, (a->imaginary - a->real * r) / den); + } + else { + r = br / bi; + den = bi + r * br; + return complex_new(mrb, (a->real * r + a->imaginary) / den, (a->imaginary * r - a->real) / den); + } + } } +/* + * call-seq: + * complex / numeric -> complex + * complex.quo(numeric) -> complex + * + * Returns the quotient of complex divided by numeric. Uses the standard + * complex division formula by multiplying by the conjugate. + * + * Complex(10, 5) / Complex(2, 1) #=> (5+0i) + * Complex(6, 4) / 2 #=> (3+2i) + */ static mrb_value complex_div(mrb_state *mrb, mrb_value x) { @@ -369,6 +481,15 @@ complex_div(mrb_state *mrb, mrb_value x) return mrb_complex_div(mrb, x, y); } +/* + * call-seq: + * complex.hash -> integer + * + * Returns a hash value for the complex number. Two complex numbers with + * the same real and imaginary parts will have the same hash value. + * + * Complex(1, 2).hash == Complex(1, 2).hash #=> true + */ static mrb_value complex_hash(mrb_state *mrb, mrb_value cpx) { @@ -378,12 +499,64 @@ complex_hash(mrb_state *mrb, mrb_value cpx) return mrb_int_value(mrb, hash); } +/* + * call-seq: + * nil.to_c -> complex + * + * Returns Complex(0, 0). + * + * nil.to_c #=> (0+0i) + */ static mrb_value nil_to_c(mrb_state *mrb, mrb_value self) { return complex_new(mrb, 0, 0); } +/* + * call-seq: + * cmp ** numeric -> complex + * + * Returns the result of raising cmp to the power of numeric. + * + * Complex(1, 2) ** 2 #=> (-3+4i) + * Complex(1, 2) ** Complex(1, 0) #=> (1+2i) + */ +static mrb_value +complex_pow(mrb_state *mrb, mrb_value self) +{ + mrb_value other = mrb_get_arg1(mrb); + struct mrb_complex *c_self = complex_ptr(mrb, self); + mrb_float self_real = c_self->real; + mrb_float self_imaginary = c_self->imaginary; + + if (mrb_type(other) == MRB_TT_COMPLEX) { + struct mrb_complex *c_other = complex_ptr(mrb, other); + mrb_float x = c_other->real; + mrb_float y = c_other->imaginary; + + mrb_float log_abs_self = F(log)(F(hypot)(self_real, self_imaginary)); + mrb_float arg_self = F(atan2)(self_imaginary, self_real); + + mrb_float a = x * log_abs_self - y * arg_self; + mrb_float b = x * arg_self + y * log_abs_self; + + mrb_float exp_a = F(exp)(a); + return mrb_complex_new(mrb, exp_a * F(cos)(b), exp_a * F(sin)(b)); + } + else { + mrb_float other_float = mrb_as_float(mrb, other); + + mrb_float abs_self = F(hypot)(self_real, self_imaginary); + mrb_float arg_self = F(atan2)(self_imaginary, self_real); + + mrb_float pow_abs_self = F(pow)(abs_self, other_float); + mrb_float new_arg = arg_self * other_float; + + return mrb_complex_new(mrb, pow_abs_self * F(cos)(new_arg), pow_abs_self * F(sin)(new_arg)); + } +} + void mrb_mruby_complex_gem_init(mrb_state *mrb) { struct RClass *comp; @@ -408,6 +581,7 @@ void mrb_mruby_complex_gem_init(mrb_state *mrb) mrb_define_method_id(mrb, comp, MRB_SYM(quo), complex_div, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, comp, MRB_OPSYM(eq), complex_eq, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, comp, MRB_SYM(hash), complex_hash, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, comp, MRB_OPSYM(pow), complex_pow, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, mrb->nil_class, MRB_SYM(to_c), nil_to_c, MRB_ARGS_NONE()); } diff --git a/mrbgems/mruby-complex/test/complex.rb b/mrbgems/mruby-complex/test/complex.rb index 85ee6e998..747859354 100644 --- a/mrbgems/mruby-complex/test/complex.rb +++ b/mrbgems/mruby-complex/test/complex.rb @@ -76,6 +76,8 @@ assert 'Complex#/' do one = 1e200 end assert_complex Complex(ten, ten) / Complex(one, one), Complex(10.0, 0.0) + assert_raise(ZeroDivisionError) { Complex(1,1) / 0 } + assert_raise(ZeroDivisionError) { Complex(1,1) / Complex(0,0) } end assert 'Complex#==' do @@ -161,3 +163,12 @@ assert 'Complex#frozen?' do assert_predicate(Complex(2,3), :frozen?) assert_predicate(4+5i, :frozen?) end + +assert 'Complex#**' do + assert_complex Complex(2, 3) ** 2, Complex(-5, 12) + assert_complex Complex(2, 3) ** 0, Complex(1, 0) + assert_complex Complex(2, 3) ** 1, Complex(2, 3) + assert_complex Complex(2, 3) ** Complex(1, 0), Complex(2, 3) + assert_complex Complex(0, 1) ** 2, Complex(-1, 0) + assert_complex Complex(0, 1) ** Complex(0, 1), Complex(Math::E ** (-Math::PI / 2), 0) +end diff --git a/mrbgems/mruby-data/src/data.c b/mrbgems/mruby-data/src/data.c index f07046003..704c7d0c9 100644 --- a/mrbgems/mruby-data/src/data.c +++ b/mrbgems/mruby-data/src/data.c @@ -269,12 +269,12 @@ make_data_class(mrb_state *mrb, mrb_value members, struct RClass *klass) * call-seq: * DataClass.define(arg, ...) -> obj * - * Data::define returns a new Class object, + * `Data::define` returns a new `Class` object, * which can then be used to create specific instances of the new * data structure. The number of actual parameters must be * equal to the number of attributes defined for this class. * Passing too many or too less parameters will raise an - * ArgumentError. + * `ArgumentError`. * * The remaining methods listed in this section (class and instance) * are defined for this generated class. @@ -379,10 +379,10 @@ mrb_data_init_copy(mrb_state *mrb, mrb_value copy) * call-seq: * data == other_data -> true or false * - * Equality---Returns true if other_data is + * Equality---Returns `true` if *other_data* is * equal to this one: they must be of the same class as generated by - * Data::define, and all values of must be equal - * (according to Object#==). + * `Data::define`, and all values of must be equal + * (according to `Object#==`). * * Customer = Data.define(:name, :address, :zip) * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) @@ -425,7 +425,7 @@ mrb_data_equal(mrb_state *mrb, mrb_value s) * data.eql?(other) -> true or false * * Two structures are equal if they are the same object, or if all their - * fields are equal (using Object#eql?). + * fields are equal (using `Object#eql?`). */ static mrb_value mrb_data_eql(mrb_state *mrb, mrb_value s) @@ -510,18 +510,18 @@ mrb_data_to_s(mrb_state *mrb, mrb_value self) } /* - * A Data is a convenient way to bundle a number of + * A `Data` is a convenient way to bundle a number of * attributes together, using accessor methods, without having to write * an explicit class. * - * The Data class is a generator of specific classes, + * The `Data` class is a generator of specific classes, * each one of which is defined to hold a set of variables and their * accessors. In these examples, we'll call the generated class - * "CustomerClass," and we'll show an example instance of that - * class as "CustomerInst." + * "*Customer*Class," and we'll show an example instance of that + * class as "*Customer*Inst." * - * In the descriptions that follow, the parameter symbol refers - * to a symbol (such as :name). + * In the descriptions that follow, the parameter *symbol* refers + * to a symbol (such as `:name`). */ void mrb_mruby_data_gem_init(mrb_state* mrb) diff --git a/mrbgems/mruby-dir/include/dir_hal.h b/mrbgems/mruby-dir/include/dir_hal.h new file mode 100644 index 000000000..3f8414c9f --- /dev/null +++ b/mrbgems/mruby-dir/include/dir_hal.h @@ -0,0 +1,79 @@ +/* +** dir_hal.h - Directory HAL interface for mruby +** +** See Copyright Notice in mruby.h +** +** Hardware Abstraction Layer for directory operations. +** Provides platform-independent interface for filesystem directory operations. +*/ + +#ifndef MRUBY_DIR_HAL_H +#define MRUBY_DIR_HAL_H + +#include + +/* + * Platform-independent directory handle + * Each HAL implementation defines this structure internally + */ +typedef struct mrb_dir_handle mrb_dir_handle; + +/* + * Directory Operations + */ + +/* Open directory for reading. Returns handle or NULL on error (sets errno). */ +mrb_dir_handle* mrb_hal_dir_open(mrb_state *mrb, const char *path); + +/* Close directory handle. Returns 0 on success, -1 on error. */ +int mrb_hal_dir_close(mrb_state *mrb, mrb_dir_handle *dir); + +/* Read next entry from directory. Returns name or NULL at end/error. */ +const char* mrb_hal_dir_read(mrb_state *mrb, mrb_dir_handle *dir); + +/* Rewind directory to beginning */ +void mrb_hal_dir_rewind(mrb_state *mrb, mrb_dir_handle *dir); + +/* + * Optional Operations (may not be available on all platforms) + */ + +/* Seek to position in directory. Returns -1 if unsupported (sets errno to ENOSYS). */ +int mrb_hal_dir_seek(mrb_state *mrb, mrb_dir_handle *dir, long pos); + +/* Get current position in directory. Returns -1 if unsupported (sets errno to ENOSYS). */ +long mrb_hal_dir_tell(mrb_state *mrb, mrb_dir_handle *dir); + +/* + * Filesystem Operations + */ + +/* Create directory with mode (mode may be ignored on some platforms). Returns 0 on success, -1 on error. */ +int mrb_hal_dir_mkdir(mrb_state *mrb, const char *path, int mode); + +/* Remove empty directory. Returns 0 on success, -1 on error. */ +int mrb_hal_dir_rmdir(mrb_state *mrb, const char *path); + +/* Change current working directory. Returns 0 on success, -1 on error. */ +int mrb_hal_dir_chdir(mrb_state *mrb, const char *path); + +/* Get current working directory. Returns 0 on success, -1 on error. */ +int mrb_hal_dir_getcwd(mrb_state *mrb, char *buf, size_t size); + +/* Change root directory (privileged operation). Returns -1 if unsupported (sets errno to ENOSYS). */ +int mrb_hal_dir_chroot(mrb_state *mrb, const char *path); + +/* Check if path is a directory. Returns 1 if directory, 0 if not. */ +int mrb_hal_dir_is_directory(mrb_state *mrb, const char *path); + +/* + * HAL Initialization/Finalization + */ + +/* Initialize HAL (called once at gem initialization) */ +void mrb_hal_dir_init(mrb_state *mrb); + +/* Cleanup HAL (called once at gem finalization) */ +void mrb_hal_dir_final(mrb_state *mrb); + +#endif /* MRUBY_DIR_HAL_H */ diff --git a/mrbgems/mruby-dir/mrbgem.rake b/mrbgems/mruby-dir/mrbgem.rake index ed05e14d1..71ed21cbd 100644 --- a/mrbgems/mruby-dir/mrbgem.rake +++ b/mrbgems/mruby-dir/mrbgem.rake @@ -1,4 +1,35 @@ 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/mruby-dir/mrblib/dir.rb b/mrbgems/mruby-dir/mrblib/dir.rb index 04d3a2f1f..71dd929f1 100644 --- a/mrbgems/mruby-dir/mrblib/dir.rb +++ b/mrbgems/mruby-dir/mrblib/dir.rb @@ -1,6 +1,17 @@ class Dir include Enumerable + # + # call-seq: + # dir.each { |filename| block } -> dir + # dir.each -> enumerator + # + # Calls the block once for each entry in this directory, passing the filename + # of each entry as a parameter to the block. + # + # d = Dir.new("testdir") + # d.each { |x| puts "Got #{x}" } + # def each(&block) return to_enum(:each) unless block while s = self.read @@ -9,6 +20,17 @@ class Dir self end + # + # call-seq: + # dir.each_child { |filename| block } -> dir + # dir.each_child -> enumerator + # + # Calls the block once for each entry in this directory except for "." and "..", + # passing the filename of each entry as a parameter to the block. + # + # d = Dir.new("testdir") + # d.each_child { |x| puts "Got #{x}" } + # def each_child(&block) return to_enum(:each_child) unless block while s = self.read @@ -21,26 +43,18 @@ class Dir alias pos= seek class << self - def entries(path) - a = [] - self.open(path) do |d| - while s = d.read - a << s - end - end - a - end - def children(path) - a = [] - self.open(path) do |d| - while s = d.read - a << s unless s == "." || s == ".." - end - end - a - end + # + # call-seq: + # Dir.foreach(dirname) { |filename| block } -> nil + # Dir.foreach(dirname) -> enumerator + # + # Calls the block once for each entry in the named directory, passing + # the filename of each entry as a parameter to the block. + # + # Dir.foreach("testdir") { |x| puts "Got #{x}" } + # def foreach(path, &block) return to_enum(:foreach, path) unless block self.open(path) do |d| @@ -48,6 +62,17 @@ class Dir end end + # + # call-seq: + # Dir.open(string) -> aDir + # Dir.open(string) { |aDir| block } -> obj + # + # With no block, open is a synonym for Dir.new. If a block is present, it is + # passed aDir as a parameter. The directory is closed at the end of the block, + # and Dir.open returns the value of the block. + # + # Dir.open("testdir") { |d| d.each { |x| puts "Got #{x}" } } + # def open(path, &block) if block d = self.new(path) @@ -64,6 +89,19 @@ class Dir end end + # + # call-seq: + # Dir.chdir(string) -> 0 + # Dir.chdir(string) { |path| block } -> obj + # + # Changes the current working directory of the calling process to the given + # string. When called with a block, changes to the directory, executes the + # block, then changes back to the original directory. Returns the value of + # the block. + # + # Dir.chdir("/var/spool/mail") + # Dir.chdir("/tmp") { Dir.pwd } #=> "/tmp" + # def chdir(path, &block) if block wd = self.getwd diff --git a/mrbgems/mruby-dir/src/Win/dirent.c b/mrbgems/mruby-dir/src/Win/dirent.c deleted file mode 100644 index 086aaf972..000000000 --- a/mrbgems/mruby-dir/src/Win/dirent.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - - Implementation of POSIX directory browsing functions and types for Win32. - - Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) - History: Created March 1997. Updated June 2003 and July 2012. - Rights: See end of file. - -*/ - -#include -#include /* _findfirst and _findnext set errno iff they return -1 */ -#include -#include - -#ifdef __cplusplus -extern "C" -{ -#endif - -typedef ptrdiff_t handle_type; /* C99's intptr_t not sufficiently portable */ - -struct dirent -{ - char *d_name; -}; - -struct DIR -{ - handle_type handle; /* -1 for failed rewind */ - struct _finddata_t info; - struct dirent result; /* d_name null iff first time */ - char *name; /* null-terminated char string */ -}; - -typedef struct DIR DIR; - -DIR *opendir(const char *name) -{ - DIR *dir = 0; - - if (name && name[0]) { - size_t base_length = strlen(name); - const char *all = /* search pattern must end with suitable wildcard */ - strchr("/\\", name[base_length - 1]) ? "*" : "/*"; - - if ((dir = (DIR *) malloc(sizeof *dir)) != 0 && - (dir->name = (char *) malloc(base_length + strlen(all) + 1)) != 0) { - strcat(strcpy(dir->name, name), all); - - if ((dir->handle = (handle_type) _findfirst(dir->name, &dir->info)) != -1) { - dir->result.d_name = 0; - } - else { /* rollback */ - free(dir->name); - free(dir); - dir = 0; - } - } - else { /* rollback */ - free(dir); - dir = 0; - errno = ENOMEM; - } - } - else { - errno = EINVAL; - } - - return dir; -} - -int closedir(DIR *dir) -{ - int result = -1; - - if (dir) { - if (dir->handle != -1) - { - result = _findclose(dir->handle); - } - - free(dir->name); - free(dir); - } - - if (result == -1) { /* map all errors to EBADF */ - errno = EBADF; - } - - return result; -} - -struct dirent *readdir(DIR *dir) -{ - struct dirent *result = 0; - - if (dir && dir->handle != -1) { - if (!dir->result.d_name || _findnext(dir->handle, &dir->info) != -1) { - result = &dir->result; - result->d_name = dir->info.name; - } - } - else { - errno = EBADF; - } - - return result; -} - -void rewinddir(DIR *dir) -{ - if (dir && dir->handle != -1) { - _findclose(dir->handle); - dir->handle = (handle_type) _findfirst(dir->name, &dir->info); - dir->result.d_name = 0; - } - else { - errno = EBADF; - } -} - -#ifdef __cplusplus -} -#endif - -/* - - 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-dir/src/dir.c b/mrbgems/mruby-dir/src/dir.c index 2467b8289..215f74381 100644 --- a/mrbgems/mruby-dir/src/dir.c +++ b/mrbgems/mruby-dir/src/dir.c @@ -5,41 +5,21 @@ */ #include +#include #include #include #include #include #include -#include -#if defined(_WIN32) - #define MAXPATHLEN 1024 - #if !defined(PATH_MAX) - #define PATH_MAX MAX_PATH - #endif - #define S_ISDIR(B) ((B)&_S_IFDIR) - #include "Win/dirent.c" - #include - #define rmdir(path) _rmdir(path) - #define getcwd(path,len) _getcwd(path,len) - #define mkdir(path,mode) _mkdir(path) - #define chdir(path) _chdir(path) -#else - #include - #include - #include -#endif -#include -#include -#include -#include -#include +#include "dir_hal.h" + #include #include #define E_IO_ERROR mrb_exc_get_id(mrb, MRB_SYM(IOError)) struct mrb_dir { - DIR *dir; + mrb_dir_handle *handle; }; static void @@ -47,35 +27,53 @@ mrb_dir_free(mrb_state *mrb, void *ptr) { struct mrb_dir *mdir = (struct mrb_dir*)ptr; - if (mdir->dir) { - closedir(mdir->dir); - mdir->dir = NULL; + if (mdir->handle) { + mrb_hal_dir_close(mrb, mdir->handle); + mdir->handle = NULL; } mrb_free(mrb, mdir); } static struct mrb_data_type mrb_dir_type = { "DIR", mrb_dir_free }; +/* + * call-seq: + * dir.close -> nil + * + * Closes the directory stream. Any further attempts to access dir will + * raise an IOError. + * + * d = Dir.new("testdir") + * d.close #=> nil + */ static mrb_value mrb_dir_close(mrb_state *mrb, mrb_value self) { struct mrb_dir *mdir; mdir = (struct mrb_dir*)mrb_get_datatype(mrb, self, &mrb_dir_type); if (!mdir) return mrb_nil_value(); - if (!mdir->dir) { + if (!mdir->handle) { mrb_raise(mrb, E_IO_ERROR, "closed directory"); } - if (closedir(mdir->dir) == -1) { + if (mrb_hal_dir_close(mrb, mdir->handle) == -1) { mrb_sys_fail(mrb, "closedir"); } - mdir->dir = NULL; + mdir->handle = NULL; return mrb_nil_value(); } +/* + * call-seq: + * Dir.new(string) -> aDir + * + * Returns a new directory object for the named directory. + * + * d = Dir.new("testdir") + */ static mrb_value mrb_dir_init(mrb_state *mrb, mrb_value self) { - DIR *dir; + mrb_dir_handle *handle; struct mrb_dir *mdir; const char *path; @@ -87,37 +85,54 @@ mrb_dir_init(mrb_state *mrb, mrb_value self) DATA_PTR(self) = NULL; mdir = (struct mrb_dir*)mrb_malloc(mrb, sizeof(*mdir)); - mdir->dir = NULL; + mdir->handle = NULL; DATA_PTR(self) = mdir; mrb_get_args(mrb, "z", &path); - if ((dir = opendir(path)) == NULL) { + if ((handle = mrb_hal_dir_open(mrb, path)) == NULL) { mrb_sys_fail(mrb, path); } - mdir->dir = dir; + mdir->handle = handle; return self; } +/* + * call-seq: + * Dir.delete(string) -> 0 + * + * Deletes the named directory. Raises a subclass of SystemCallError if the + * directory isn't empty. + * + * Dir.delete("testdir") + */ static mrb_value mrb_dir_delete(mrb_state *mrb, mrb_value klass) { const char *path; mrb_get_args(mrb, "z", &path); - if (rmdir(path) == -1) { + if (mrb_hal_dir_rmdir(mrb, path) == -1) { mrb_sys_fail(mrb, path); } return mrb_fixnum_value(0); } +/* + * call-seq: + * Dir.exist?(file_name) -> true or false + * + * Returns true if the named file is a directory, false otherwise. + * + * Dir.exist?(".") #=> true + * Dir.exist?("foo") #=> false + */ static mrb_value mrb_dir_existp(mrb_state *mrb, mrb_value klass) { - struct stat sb; const char *path; mrb_get_args(mrb, "z", &path); - if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + if (mrb_hal_dir_is_directory(mrb, path)) { return mrb_true_value(); } else { @@ -125,14 +140,22 @@ mrb_dir_existp(mrb_state *mrb, mrb_value klass) } } +/* + * call-seq: + * Dir.getwd -> string + * Dir.pwd -> string + * + * Returns the path to the current working directory of this process as a string. + * + * Dir.getwd #=> "/usr/local" + */ static mrb_value mrb_dir_getwd(mrb_state *mrb, mrb_value klass) { - mrb_value path; mrb_int size = 64; - path = mrb_str_buf_new(mrb, size); - while (getcwd(RSTRING_PTR(path), size) == NULL) { + mrb_value path = mrb_str_buf_new(mrb, size); + while (mrb_hal_dir_getcwd(mrb, RSTRING_PTR(path), (size_t)size) == -1) { int e = errno; if (e != ERANGE) { mrb_sys_fail(mrb, "getcwd(2)"); @@ -144,6 +167,18 @@ mrb_dir_getwd(mrb_state *mrb, mrb_value klass) return path; } +/* + * call-seq: + * Dir.mkdir(string [, integer]) -> 0 + * + * Makes a new directory named by string, with permissions specified by the + * optional parameter integer. The permissions may be modified by the value + * of File.umask, and are ignored on NT. Raises a SystemCallError if the + * directory cannot be created. + * + * Dir.mkdir("testdir") #=> 0 + * Dir.mkdir("testdir", 0755) #=> 0 + */ static mrb_value mrb_dir_mkdir(mrb_state *mrb, mrb_value klass) { @@ -152,42 +187,50 @@ mrb_dir_mkdir(mrb_state *mrb, mrb_value klass) mode = 0777; mrb_get_args(mrb, "z|i", &path, &mode); - if (mkdir(path, mode) == -1) { + if (mrb_hal_dir_mkdir(mrb, path, (int)mode) == -1) { mrb_sys_fail(mrb, path); } return mrb_fixnum_value(0); } +/* Helper for Dir.chdir - internal method to change directory */ static mrb_value mrb_dir_chdir(mrb_state *mrb, mrb_value klass) { const char *path; mrb_get_args(mrb, "z", &path); - if (chdir(path) == -1) { + if (mrb_hal_dir_chdir(mrb, path) == -1) { mrb_sys_fail(mrb, path); } return mrb_fixnum_value(0); } +/* + * call-seq: + * Dir.chroot(string) -> 0 + * + * Changes this process's idea of the file system root. Only a privileged + * process may make this call. Not available on all platforms. + * + * Dir.chroot("/production/secure/root") + */ static mrb_value mrb_dir_chroot(mrb_state *mrb, mrb_value self) { -#if defined(_WIN32) || defined(__ANDROID__) || defined(__MSDOS__) - mrb_raise(mrb, E_NOTIMP_ERROR, "chroot() unreliable on your system"); - return mrb_fixnum_value(0); -#else const char *path; int res; mrb_get_args(mrb, "z", &path); - res = chroot(path); + res = mrb_hal_dir_chroot(mrb, path); if (res == -1) { + if (errno == ENOSYS) { + mrb_raise(mrb, E_NOTIMP_ERROR, "chroot() unreliable on your system"); + } mrb_sys_fail(mrb, path); } return mrb_fixnum_value(res); -#endif } static mrb_bool @@ -200,48 +243,80 @@ skip_name_p(const char *name) return FALSE; } +/* + * call-seq: + * Dir.empty?(path_name) -> true or false + * + * Returns true if the named directory is empty, false otherwise. + * + * Dir.empty?(".") #=> false + * Dir.empty?("/tmp") #=> false + */ static mrb_value mrb_dir_empty(mrb_state *mrb, mrb_value self) { - DIR *dir; - struct dirent *dp; + mrb_dir_handle *handle; + const char *name; const char *path; mrb_value result = mrb_true_value(); mrb_get_args(mrb, "z", &path); - if ((dir = opendir(path)) == NULL) { + if ((handle = mrb_hal_dir_open(mrb, path)) == NULL) { mrb_sys_fail(mrb, path); } - while ((dp = readdir(dir))) { - if (!skip_name_p(dp->d_name)) { + while ((name = mrb_hal_dir_read(mrb, handle)) != NULL) { + if (!skip_name_p(name)) { result = mrb_false_value(); break; } } - closedir(dir); + mrb_hal_dir_close(mrb, handle); return result; } +/* + * call-seq: + * dir.read -> string or nil + * + * Reads the next entry from dir and returns it as a string. Returns nil + * at the end of the stream. + * + * d = Dir.new("testdir") + * d.read #=> "." + * d.read #=> ".." + * d.read #=> "config.h" + */ static mrb_value mrb_dir_read(mrb_state *mrb, mrb_value self) { struct mrb_dir *mdir; - struct dirent *dp; + const char *name; mdir = (struct mrb_dir*)mrb_get_datatype(mrb, self, &mrb_dir_type); if (!mdir) return mrb_nil_value(); - if (!mdir->dir) { + if (!mdir->handle) { mrb_raise(mrb, E_IO_ERROR, "closed directory"); } - dp = readdir(mdir->dir); - if (dp != NULL) { - return mrb_str_new_cstr(mrb, dp->d_name); + name = mrb_hal_dir_read(mrb, mdir->handle); + if (name != NULL) { + return mrb_str_new_cstr(mrb, name); } else { return mrb_nil_value(); } } +/* + * call-seq: + * dir.rewind -> dir + * + * Repositions dir to the beginning of the stream. + * + * d = Dir.new("testdir") + * d.read #=> "." + * d.rewind #=> # + * d.read #=> "." + */ static mrb_value mrb_dir_rewind(mrb_state *mrb, mrb_value self) { @@ -249,52 +324,137 @@ mrb_dir_rewind(mrb_state *mrb, mrb_value self) mdir = (struct mrb_dir*)mrb_get_datatype(mrb, self, &mrb_dir_type); if (!mdir) return mrb_nil_value(); - if (!mdir->dir) { + if (!mdir->handle) { mrb_raise(mrb, E_IO_ERROR, "closed directory"); } - rewinddir(mdir->dir); + mrb_hal_dir_rewind(mrb, mdir->handle); return self; } +/* + * call-seq: + * dir.seek(integer) -> dir + * + * Seeks to a particular location in dir. integer must be a value returned + * by Dir#tell. + * + * d = Dir.new("testdir") + * pos = d.tell #=> 0 + * d.read #=> "." + * d.seek(pos) #=> # + * d.read #=> "." + */ static mrb_value mrb_dir_seek(mrb_state *mrb, mrb_value self) { - #if defined(_WIN32) || defined(__ANDROID__) - mrb_raise(mrb, E_NOTIMP_ERROR, "dirseek() unreliable on your system"); - return self; - #else struct mrb_dir *mdir; mrb_int pos; mdir = (struct mrb_dir*)mrb_get_datatype(mrb, self, &mrb_dir_type); if (!mdir) return mrb_nil_value(); - if (!mdir->dir) { + if (!mdir->handle) { mrb_raise(mrb, E_IO_ERROR, "closed directory"); } mrb_get_args(mrb, "i", &pos); - seekdir(mdir->dir, (long)pos); + if (mrb_hal_dir_seek(mrb, mdir->handle, (long)pos) == -1) { + if (errno == ENOSYS) { + mrb_raise(mrb, E_NOTIMP_ERROR, "dirseek() unreliable on your system"); + } + } return self; - #endif } +/* + * call-seq: + * dir.tell -> integer + * dir.pos -> integer + * + * Returns the current position in dir. + * + * d = Dir.new("testdir") + * d.tell #=> 0 + * d.read #=> "." + * d.tell #=> 1 + */ static mrb_value mrb_dir_tell(mrb_state *mrb, mrb_value self) { -#if defined(_WIN32) || defined(__ANDROID__) - mrb_raise(mrb, E_NOTIMP_ERROR, "dirtell() unreliable on your system"); - return mrb_fixnum_value(0); -#else struct mrb_dir *mdir; - mrb_int pos; + long pos; mdir = (struct mrb_dir*)mrb_get_datatype(mrb, self, &mrb_dir_type); if (!mdir) return mrb_nil_value(); - if (!mdir->dir) { + if (!mdir->handle) { mrb_raise(mrb, E_IO_ERROR, "closed directory"); } - pos = (mrb_int)telldir(mdir->dir); - return mrb_fixnum_value(pos); -#endif + pos = mrb_hal_dir_tell(mrb, mdir->handle); + if (pos == -1) { + if (errno == ENOSYS) { + mrb_raise(mrb, E_NOTIMP_ERROR, "dirtell() unreliable on your system"); + } + } + return mrb_fixnum_value((mrb_int)pos); +} + +/* + * call-seq: + * Dir.entries(dirname) -> array + * + * Returns an array containing all of the filenames in the given directory. + * Will raise a SystemCallError if the named directory doesn't exist. + */ +static mrb_value +mrb_dir_entries(mrb_state *mrb, mrb_value klass) +{ + const char *path; + + mrb_get_args(mrb, "z", &path); + + mrb_dir_handle *handle = mrb_hal_dir_open(mrb, path); + if (handle == NULL) { + mrb_sys_fail(mrb, path); + } + + mrb_value ary = mrb_ary_new(mrb); + const char *name; + while ((name = mrb_hal_dir_read(mrb, handle)) != NULL) { + mrb_ary_push(mrb, ary, mrb_str_new_cstr(mrb, name)); + } + + mrb_hal_dir_close(mrb, handle); + return ary; +} + +/* + * call-seq: + * Dir.children(dirname) -> array + * + * Returns an array containing all of the filenames except for "." and ".." + * in the given directory. Will raise a SystemCallError if the named + * directory doesn't exist. + */ +static mrb_value +mrb_dir_children(mrb_state *mrb, mrb_value klass) +{ + const char *path; + + mrb_get_args(mrb, "z", &path); + + mrb_dir_handle *handle = mrb_hal_dir_open(mrb, path); + if (handle == NULL) { + mrb_sys_fail(mrb, path); + } + + mrb_value ary = mrb_ary_new(mrb); + const char *name; + while ((name = mrb_hal_dir_read(mrb, handle)) != NULL) { + if (!skip_name_p(name)) { + mrb_ary_push(mrb, ary, mrb_str_new_cstr(mrb, name)); + } + } + + mrb_hal_dir_close(mrb, handle); + return ary; } void @@ -302,6 +462,8 @@ mrb_mruby_dir_gem_init(mrb_state *mrb) { struct RClass *d; + mrb_hal_dir_init(mrb); + d = mrb_define_class_id(mrb, MRB_SYM(Dir), mrb->object_class); MRB_SET_INSTANCE_TT(d, MRB_TT_DATA); mrb_define_class_method_id(mrb, d, MRB_SYM(delete), mrb_dir_delete, MRB_ARGS_REQ(1)); @@ -311,6 +473,8 @@ mrb_mruby_dir_gem_init(mrb_state *mrb) mrb_define_class_method_id(mrb, d, MRB_SYM(_chdir), mrb_dir_chdir, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, d, MRB_SYM(chroot), mrb_dir_chroot, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, d, MRB_SYM_Q(empty), mrb_dir_empty, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, d, MRB_SYM(entries), mrb_dir_entries, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, d, MRB_SYM(children), mrb_dir_children, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, d, MRB_SYM(close), mrb_dir_close, MRB_ARGS_NONE()); mrb_define_method_id(mrb, d, MRB_SYM(initialize), mrb_dir_init, MRB_ARGS_REQ(1)); @@ -325,4 +489,5 @@ mrb_mruby_dir_gem_init(mrb_state *mrb) void mrb_mruby_dir_gem_final(mrb_state *mrb) { + mrb_hal_dir_final(mrb); } diff --git a/mrbgems/mruby-dir/test/dirtest.c b/mrbgems/mruby-dir/test/dirtest.c index 1a7aca8df..e7e8564ff 100644 --- a/mrbgems/mruby-dir/test/dirtest.c +++ b/mrbgems/mruby-dir/test/dirtest.c @@ -5,42 +5,24 @@ #include #include -#if defined(_WIN32) -#ifdef __cplusplus -extern "C" { -#endif -typedef struct DIR DIR; -struct dirent -{ - char *d_name; -}; -DIR *opendir(const char *name); -struct dirent *readdir(DIR *dir); -int closedir(DIR *dir); -#ifdef __cplusplus -} -#endif -#include -#include -#define rmdir(path) _rmdir(path) -#define getcwd(path,len) _getcwd(path,len) -#define mkdir(path,mode) _mkdir(path) -#define chdir(path) _chdir(path) -#else -#include -#include -#endif - #include #include #include +#include "dir_hal.h" + +#if defined(_WIN32) +#include +#include +#else +#include +#endif static void make_dir(mrb_state *mrb, const char *name, const char *up) { - if (mkdir(name, 0) == -1) { - if (chdir("..") == 0) { - rmdir(up); + if (mrb_hal_dir_mkdir(mrb, name, 0) == -1) { + if (mrb_hal_dir_chdir(mrb, "..") == 0) { + mrb_hal_dir_rmdir(mrb, up); } mrb_raisef(mrb, E_RUNTIME_ERROR, "mkdir(%s) failed", mrb_str_new_cstr(mrb, name)); } @@ -49,20 +31,21 @@ make_dir(mrb_state *mrb, const char *name, const char *up) mrb_value mrb_dirtest_setup(mrb_state *mrb, mrb_value klass) { - char buf[1024]; + char buf[2048]; + char cwd[1024]; const char *aname = "a"; const char *bname = "b"; /* save current working directory */ - if (getcwd(buf, sizeof(buf)) == NULL) { + if (mrb_hal_dir_getcwd(mrb, cwd, sizeof(cwd)) != 0) { mrb_raise(mrb, E_RUNTIME_ERROR, "getcwd() failed"); } - mrb_cv_set(mrb, klass, mrb_intern_cstr(mrb, "pwd"), mrb_str_new_cstr(mrb, buf)); + mrb_cv_set(mrb, klass, mrb_intern_cstr(mrb, "pwd"), mrb_str_new_cstr(mrb, cwd)); /* create sandbox */ #if defined(_WIN32) - snprintf(buf, sizeof(buf), "%s\\mruby-dir-test.XXXXXX", _getcwd(NULL,0)); - if ((_mktemp(buf) == NULL) || mkdir(buf,0) != 0) { + snprintf(buf, sizeof(buf), "%s\\mruby-dir-test.XXXXXX", cwd); + if ((_mktemp(buf) == NULL) || mrb_hal_dir_mkdir(mrb, buf, 0) != 0) { mrb_raisef(mrb, E_RUNTIME_ERROR, "mkdtemp(%s) failed", buf); } #else @@ -74,8 +57,8 @@ mrb_dirtest_setup(mrb_state *mrb, mrb_value klass) mrb_cv_set(mrb, klass, mrb_intern_cstr(mrb, "sandbox"), mrb_str_new_cstr(mrb, buf)); /* go to sandbox */ - if (chdir(buf) == -1) { - rmdir(buf); + if (mrb_hal_dir_chdir(mrb, buf) == -1) { + mrb_hal_dir_rmdir(mrb, buf); mrb_raisef(mrb, E_RUNTIME_ERROR, "chdir(%s) failed", buf); } @@ -90,35 +73,35 @@ mrb_value mrb_dirtest_teardown(mrb_state *mrb, mrb_value klass) { mrb_value d, sandbox; - DIR *dirp; - struct dirent *dp; + mrb_dir_handle *dirp; + const char *name; const char *path; /* cleanup sandbox */ sandbox = mrb_cv_get(mrb, klass, mrb_intern_cstr(mrb, "sandbox")); path = mrb_str_to_cstr(mrb, sandbox); - dirp = opendir(path); - while ((dp = readdir(dirp)) != NULL) { - if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) + dirp = mrb_hal_dir_open(mrb, path); + while ((name = mrb_hal_dir_read(mrb, dirp)) != NULL) { + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; - if (rmdir(dp->d_name) == -1) { - mrb_raisef(mrb, E_RUNTIME_ERROR, "rmdir(%s) failed", dp->d_name); + if (mrb_hal_dir_rmdir(mrb, name) == -1) { + mrb_raisef(mrb, E_RUNTIME_ERROR, "rmdir(%s) failed", name); } } - closedir(dirp); + mrb_hal_dir_close(mrb, dirp); /* back to original pwd */ d = mrb_cv_get(mrb, klass, mrb_intern_cstr(mrb, "pwd")); path = mrb_str_to_cstr(mrb, d); - if (chdir(path) == -1) { + if (mrb_hal_dir_chdir(mrb, path) == -1) { mrb_raisef(mrb, E_RUNTIME_ERROR, "chdir(%s) failed", path); } /* remove sandbox directory */ sandbox = mrb_cv_get(mrb, klass, mrb_intern_cstr(mrb, "sandbox")); path = mrb_str_to_cstr(mrb, sandbox); - if (rmdir(path) == -1) { + if (mrb_hal_dir_rmdir(mrb, path) == -1) { mrb_raisef(mrb, E_RUNTIME_ERROR, "rmdir(%s) failed", path); } diff --git a/mrbgems/mruby-enum-chain/mrblib/chain.rb b/mrbgems/mruby-enum-chain/mrblib/chain.rb index 43d0926c8..fb0dac66b 100644 --- a/mrbgems/mruby-enum-chain/mrblib/chain.rb +++ b/mrbgems/mruby-enum-chain/mrblib/chain.rb @@ -3,12 +3,33 @@ # See Copyright Notice in mruby.h module Enumerable + # + # call-seq: + # enum.chain(*enums) -> enumerator_chain + # + # Returns an Enumerator::Chain object which can enumerate over this + # enumerable and the given enumerables in sequence. + # + # e = (1..3).chain([4, 5]) + # e.to_a #=> [1, 2, 3, 4, 5] + # def chain(*args) Enumerator::Chain.new(self, *args) end end class Enumerator + # + # call-seq: + # enum + other_enum -> enumerator_chain + # + # Returns an Enumerator::Chain object which can enumerate over this + # enumerator and the given enumerator in sequence. + # + # e1 = (1..3).each + # e2 = [4, 5].each + # (e1 + e2).to_a #=> [1, 2, 3, 4, 5] + # def +(other) Chain.new(self, other) end @@ -16,11 +37,34 @@ class Enumerator class Chain include Enumerable + # + # call-seq: + # Enumerator::Chain.new(*enums) -> enumerator_chain + # + # Generates a new enumerator which iterates over each one of the + # given enumerable objects in sequence. + # + # e = Enumerator::Chain.new(1..3, [4, 5]) + # e.to_a #=> [1, 2, 3, 4, 5] + # def initialize(*args) @enums = args.freeze @pos = -1 end + # + # call-seq: + # chain.each { |obj| block } -> chain + # chain.each -> enumerator + # + # Iterates over the elements of the first enumerable by calling the + # each method on it with the given block, then proceeds to the next + # enumerable in the chain and continues until the end. + # + # e = Enumerator::Chain.new(1..3, [4, 5]) + # e.each { |x| puts x } + # # prints: 1, 2, 3, 4, 5 + # def each(&block) return to_enum unless block @@ -34,6 +78,16 @@ class Enumerator self end + # + # call-seq: + # chain.size -> integer or nil + # + # Returns the total size of the enumerator chain if all of the + # chained enumerables define size. Otherwise it returns nil. + # + # Enumerator::Chain.new(1..3, [4, 5]).size #=> 5 + # Enumerator::Chain.new(1..3, loop).size #=> nil + # def size @enums.reduce(0) do |a, e| return nil unless e.respond_to?(:size) @@ -41,6 +95,19 @@ class Enumerator end end + # + # call-seq: + # chain.rewind -> chain + # + # Rewinds the enumerator chain by calling the rewind method on each + # enumerable that has been iterated, in reverse order. Each enumerable + # that defines a rewind method will be rewound. + # + # e = Enumerator::Chain.new((1..3), [4, 5]) + # e.next #=> 1 + # e.rewind + # e.next #=> 1 + # def rewind while 0 <= @pos && @pos < @enums.size e = @enums[@pos] @@ -51,10 +118,30 @@ class Enumerator self end + # + # call-seq: + # chain + other_enum -> enumerator_chain + # + # Returns a new Enumerator::Chain object which will enumerate over the + # elements of this chain, followed by the elements of other_enum. + # + # e1 = Enumerator::Chain.new(1..3, [4, 5]) + # e2 = e1 + [6, 7] + # e2.to_a #=> [1, 2, 3, 4, 5, 6, 7] + # def +(other) self.class.new(self, other) end + # + # call-seq: + # chain.inspect -> string + # + # Returns a printable version of the enumerator chain. + # + # Enumerator::Chain.new(1..3, [4, 5]).inspect + # #=> "#" + # def inspect "#<#{self.class}: #{@enums.inspect}>" end diff --git a/mrbgems/mruby-enum-ext/mrblib/enum.rb b/mrbgems/mruby-enum-ext/mrblib/enum.rb index 985199abe..424a7055d 100644 --- a/mrbgems/mruby-enum-ext/mrblib/enum.rb +++ b/mrbgems/mruby-enum-ext/mrblib/enum.rb @@ -6,7 +6,7 @@ module Enumerable # call-seq: # enum.drop(n) -> array # - # Drops first n elements from enum, and returns rest elements + # Drops first n elements from *enum*, and returns rest elements # in an array. # # a = [1, 2, 3, 4, 5, 0] @@ -27,7 +27,7 @@ module Enumerable # enum.drop_while -> an_enumerator # # Drops elements up to, but not including, the first element for - # which the block returns +nil+ or +false+ and returns an array + # which the block returns `nil` or `false` and returns an array # containing the remaining elements. # # If no block is given, an enumerator is returned instead. @@ -50,7 +50,7 @@ module Enumerable # call-seq: # enum.take(n) -> array # - # Returns first n elements from enum. + # Returns first n elements from *enum*. # # a = [1, 2, 3, 4, 5, 0] # a.take(3) #=> [1, 2, 3] @@ -74,7 +74,7 @@ module Enumerable # enum.take_while {|arr| block } -> array # enum.take_while -> an_enumerator # - # Passes elements to the block until the block returns +nil+ or +false+, + # Passes elements to the block until the block returns `nil` or `false`, # then stops iterating and returns an array of all prior elements. # # If no block is given, an enumerator is returned instead. @@ -163,7 +163,7 @@ module Enumerable # enum.group_by -> an_enumerator # # Returns a hash, which keys are evaluated result from the - # block, and values are arrays of elements in enum + # block, and values are arrays of elements in *enum* # corresponding to the key. # # (1..6).group_by {|i| i%3} #=> {0=>[3, 6], 1=>[1, 4], 2=>[2, 5]} @@ -185,8 +185,8 @@ module Enumerable # enum.sort_by { |obj| block } -> array # enum.sort_by -> an_enumerator # - # Sorts enum using a set of keys generated by mapping the - # values in enum through the given block. + # Sorts *enum* using a set of keys generated by mapping the + # values in *enum* through the given block. # # If no block is given, an enumerator is returned instead. def sort_by(&block) @@ -199,8 +199,8 @@ module Enumerable # enum.first -> obj or nil # enum.first(n) -> an_array # - # Returns the first element, or the first +n+ elements, of the enumerable. - # If the enumerable is empty, the first form returns nil, and the + # Returns the first element, or the first `n` elements, of the enumerable. + # If the enumerable is empty, the first form returns `nil`, and the # second form returns an empty array. def first(*args) case args.length @@ -231,9 +231,9 @@ module Enumerable # enum.count(item) -> int # enum.count { |obj| block } -> int # - # Returns the number of items in +enum+ through enumeration. - # If an argument is given, the number of items in +enum+ that - # are equal to +item+ are counted. If a block is given, it + # Returns the number of items in `enum` through enumeration. + # If an argument is given, the number of items in `enum` that + # are equal to `item` are counted. If a block is given, it # counts the number of elements yielding a true value. def count(v=NONE, &block) count = 0 @@ -261,7 +261,7 @@ module Enumerable # enum.collect_concat -> an_enumerator # # Returns a new array with the concatenated results of running - # block once for every element in enum. + # block once for every element in *enum*. # # If no block is given, an enumerator is returned instead. # @@ -288,7 +288,7 @@ module Enumerable # enum.max_by {|obj| block } -> obj # enum.max_by -> an_enumerator # - # Returns the object in enum that gives the maximum + # Returns the object in *enum* that gives the maximum # value from the given block. # # If no block is given, an enumerator is returned instead. @@ -322,7 +322,7 @@ module Enumerable # enum.min_by {|obj| block } -> obj # enum.min_by -> an_enumerator # - # Returns the object in enum that gives the minimum + # Returns the object in *enum* that gives the minimum # value from the given block. # # If no block is given, an enumerator is returned instead. @@ -358,7 +358,7 @@ module Enumerable # # Returns two elements array which contains the minimum and the # maximum value in the enumerable. The first form assumes all - # objects implement Comparable; the second uses the + # objects implement `Comparable`; the second uses the # block to return a <=> b. # # a = %w(albatross dog horse) @@ -396,7 +396,7 @@ module Enumerable # enum.minmax_by -> an_enumerator # # Returns a two element array containing the objects in - # enum that correspond to the minimum and maximum values respectively + # *enum* that correspond to the minimum and maximum values respectively # from the given block. # # If no block is given, an enumerator is returned instead. @@ -437,12 +437,12 @@ module Enumerable # enum.none?(pattern) -> true or false # # Passes each element of the collection to the given block. The method - # returns true if the block never returns true - # for all elements. If the block is not given, none? will return - # true only if none of the collection members is true. + # returns `true` if the block never returns `true` + # for all elements. If the block is not given, `none?` will return + # `true` only if none of the collection members is true. # # If a pattern is supplied instead, the method returns whether - # pattern === element for none of the collection members. + # `pattern === element` for none of the collection members. # # %w(ant bear cat).none? { |word| word.length == 5 } #=> true # %w(ant bear cat).none? { |word| word.length >= 4 } #=> false @@ -475,13 +475,13 @@ module Enumerable # enum.one?(pattern) -> true or false # # Passes each element of the collection to the given block. The method - # returns true if the block returns true - # exactly once. If the block is not given, one? will return - # true only if exactly one of the collection members is + # returns `true` if the block returns `true` + # exactly once. If the block is not given, `one?` will return + # `true` only if exactly one of the collection members is # true. # # If a pattern is supplied instead, the method returns whether - # pattern === element for exactly one collection member. + # `pattern === element` for exactly one collection member. # # %w(ant bear cat).one? { |word| word.length == 4 } #=> true # %w(ant bear cat).one? { |word| word.length > 4 } #=> false @@ -520,14 +520,14 @@ module Enumerable # enum.all?(pattern) -> true or false # # Passes each element of the collection to the given block. The method - # returns true if the block never returns - # false or nil. If the block is not given, - # Ruby adds an implicit block of { |obj| obj } which will - # cause #all? to return +true+ when none of the collection members are - # +false+ or +nil+. + # returns `true` if the block never returns + # `false` or `nil`. If the block is not given, + # Ruby adds an implicit block of `{ |obj| obj }` which will + # cause #all? to return `true` when none of the collection members are + # `false` or `nil`. # # If a pattern is supplied instead, the method returns whether - # pattern === element for every collection member. + # `pattern === element` for every collection member. # # %w[ant bear cat].all? { |word| word.length >= 3 } #=> true # %w[ant bear cat].all? { |word| word.length >= 4 } #=> false @@ -552,14 +552,14 @@ module Enumerable # enum.any?(pattern) -> true or false # # Passes each element of the collection to the given block. The method - # returns true if the block ever returns a value other - # than false or nil. If the block is not - # given, Ruby adds an implicit block of { |obj| obj } that - # will cause #any? to return +true+ if at least one of the collection - # members is not +false+ or +nil+. + # returns `true` if the block ever returns a value other + # than `false` or `nil`. If the block is not + # given, Ruby adds an implicit block of `{ |obj| obj }` that + # will cause #any? to return `true` if at least one of the collection + # members is not `false` or `nil`. # # If a pattern is supplied instead, the method returns whether - # pattern === element for any collection member. + # `pattern === element` for any collection member. # # %w[ant bear cat].any? { |word| word.length >= 3 } #=> true # %w[ant bear cat].any? { |word| word.length >= 4 } #=> true @@ -635,13 +635,13 @@ module Enumerable # enum.cycle(n=nil) { |obj| block } -> nil # enum.cycle(n=nil) -> an_enumerator # - # Calls block for each element of enum repeatedly _n_ - # times or forever if none or +nil+ is given. If a non-positive + # Calls *block* for each element of *enum* repeatedly _n_ + # times or forever if none or `nil` is given. If a non-positive # number is given or the collection is empty, does nothing. Returns - # +nil+ if the loop has finished without getting interrupted. + # `nil` if the loop has finished without getting interrupted. # # Enumerable#cycle saves elements in an internal array so changes - # to enum after the first pass have no effect. + # to *enum* after the first pass have no effect. # # If no block is given, an enumerator is returned instead. # @@ -684,10 +684,10 @@ module Enumerable # enum.find_index { |obj| block } -> int or nil # enum.find_index -> an_enumerator # - # Compares each entry in enum with value or passes + # Compares each entry in *enum* with value or passes # to block. Returns the index for the first for which the # evaluated value is non-false. If no object matches, returns - # nil + # `nil` # # If neither block nor argument is given, an enumerator is returned instead. # @@ -719,12 +719,12 @@ module Enumerable # enum.zip(arg, ...) -> an_array_of_array # enum.zip(arg, ...) { |arr| block } -> nil # - # Takes one element from enum and merges corresponding - # elements from each args. This generates a sequence of + # Takes one element from *enum* and merges corresponding + # elements from each *args*. This generates a sequence of # n-element arrays, where n is one more than the # count of arguments. The length of the resulting sequence will be - # enum#size. If the size of any argument is less than - # enum#size, nil values are supplied. If + # `enum#size`. If the size of any argument is less than + # `enum#size`, `nil` values are supplied. If # a block is given, it is invoked for each output array, otherwise # an array of arrays is returned. # @@ -773,8 +773,8 @@ module Enumerable # call-seq: # enum.to_h -> hash # - # Returns the result of interpreting enum as a list of - # [key, value] pairs. + # Returns the result of interpreting *enum* as a list of + # `[key, value]` pairs. # # %i[hello world].each_with_index.to_h # # => {:hello => 0, :world => 1} diff --git a/mrbgems/mruby-enum-lazy/mrblib/lazy.rb b/mrbgems/mruby-enum-lazy/mrblib/lazy.rb index 9ad2ccd2d..7a49b10af 100644 --- a/mrbgems/mruby-enum-lazy/mrblib/lazy.rb +++ b/mrbgems/mruby-enum-lazy/mrblib/lazy.rb @@ -1,21 +1,34 @@ module Enumerable - # = Enumerable#lazy implementation # - # Enumerable#lazy returns an instance of Enumerator::Lazy. - # You can use it just like as normal Enumerable object, - # except these methods act as 'lazy': + # call-seq: + # enum.lazy -> lazy_enumerator + # + # Returns an Enumerator::Lazy, which redefines most Enumerable + # methods to postpone enumeration and enumerate values only on an + # as-needed basis. + # + # === Example + # + # The following program finds pythagorean triples: + # + # def pythagorean_triples + # (1..Float::INFINITY).lazy.flat_map {|z| + # (1..z).flat_map {|x| + # (x..z).select {|y| + # x*x + y*y == z*z + # }.map {|y| + # [x, y, z] + # } + # } + # } + # end + # # show first ten pythagorean triples + # p pythagorean_triples.take(10).force # take is lazy, so force is needed + # p pythagorean_triples.first(10) # first is eager + # # show pythagorean triples less than 100 + # p pythagorean_triples.take_while { |*, z| z < 100 }.force # - # - map collect - # - select find_all - # - reject - # - grep - # - grep_v - # - drop - # - drop_while - # - take_while - # - flat_map collect_concat - # - zip def lazy Enumerator::Lazy.new(self) end @@ -28,6 +41,21 @@ class Enumerator # Inspired by https://github.com/antimon2/enumerable_lz # http://jp.rubyist.net/magazine/?0034-Enumerable_lz (ja) class Lazy < Enumerator + # + # call-seq: + # Lazy.new(obj, &block) + # + # Creates a new Lazy enumerator. When the enumerator is actually enumerated + # (e.g. by calling #force), obj will be enumerated and each value passed + # to the given block. The block can yield values back by calling yielder.yield. + # For example, to create a method that acts like Array#select: + # + # def select + # Lazy.new(self) do |yielder, value| + # yielder.yield(value) if yield(value) + # end + # end + # def initialize(obj, &block) super(){|yielder| begin @@ -43,6 +71,25 @@ class Enumerator } end + # + # call-seq: + # lazy.to_enum(method = :each, *args) -> lazy_enum + # lazy.to_enum(method = :each, *args) {|*args| ... } -> lazy_enum + # lazy.enum_for(method = :each, *args) -> lazy_enum + # lazy.enum_for(method = :each, *args) {|*args| ... } -> lazy_enum + # + # Similar to Object#to_enum, except it returns a lazy enumerator. + # This makes it easy to define Enumerable methods that will + # naturally remain lazy if called on a lazy enumerator. + # + # For example: + # + # module Enumerable + # def filter_map(&block) + # map(&block).compact + # end + # end + # def to_enum(meth=:each, *args, &block) unless self.respond_to?(meth) raise ArgumentError, "undefined method #{meth}" @@ -58,6 +105,18 @@ class Enumerator end alias enum_for to_enum + # + # call-seq: + # lazy.map {|obj| block } -> lazy_enumerator + # lazy.collect {|obj| block } -> lazy_enumerator + # + # Like Enumerable#map, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.map {|i| i**2 } + # #=> #:map> + # (1..Float::INFINITY).lazy.map {|i| i**2 }.first(3) + # #=> [1, 4, 9] + # def map(&block) Lazy.new(self){|yielder, val| yielder << block.call(val) @@ -65,6 +124,16 @@ class Enumerator end alias collect map + # + # call-seq: + # lazy.select {|obj| block } -> lazy_enumerator + # lazy.find_all {|obj| block } -> lazy_enumerator + # + # Like Enumerable#select, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.select {|i| i.even? }.first(3) + # #=> [2, 4, 6] + # def select(&block) Lazy.new(self){|yielder, val| if block.call(val) @@ -74,6 +143,15 @@ class Enumerator end alias find_all select + # + # call-seq: + # lazy.reject {|obj| block } -> lazy_enumerator + # + # Like Enumerable#reject, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.reject {|i| i.even? }.first(3) + # #=> [1, 3, 5] + # def reject(&block) Lazy.new(self){|yielder, val| unless block.call(val) @@ -82,6 +160,15 @@ class Enumerator } end + # + # call-seq: + # lazy.grep(pattern) -> lazy_enumerator + # + # Like Enumerable#grep, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.grep(1..10).force + # #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # def grep(pattern) Lazy.new(self){|yielder, val| if pattern === val @@ -90,6 +177,15 @@ class Enumerator } end + # + # call-seq: + # lazy.grep_v(pattern) -> lazy_enumerator + # + # Like Enumerable#grep_v, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.grep_v(2..4).first(3) + # #=> [1, 5, 6] + # def grep_v(pattern) Lazy.new(self){|yielder, val| unless pattern === val @@ -98,6 +194,15 @@ class Enumerator } end + # + # call-seq: + # lazy.drop(n) -> lazy_enumerator + # + # Like Enumerable#drop, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.drop(3).first(3) + # #=> [4, 5, 6] + # def drop(n) dropped = 0 Lazy.new(self){|yielder, val| @@ -109,6 +214,15 @@ class Enumerator } end + # + # call-seq: + # lazy.drop_while {|obj| block } -> lazy_enumerator + # + # Like Enumerable#drop_while, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.drop_while {|i| i < 4 }.first(3) + # #=> [4, 5, 6] + # def drop_while(&block) dropping = true Lazy.new(self){|yielder, val| @@ -123,6 +237,15 @@ class Enumerator } end + # + # call-seq: + # lazy.take(n) -> lazy_enumerator + # + # Like Enumerable#take, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.take(3).force + # #=> [1, 2, 3] + # def take(n) if n == 0 return Lazy.new(self){raise StopIteration} @@ -137,6 +260,15 @@ class Enumerator } end + # + # call-seq: + # lazy.take_while {|obj| block } -> lazy_enumerator + # + # Like Enumerable#take_while, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.take_while {|i| i < 4 }.force + # #=> [1, 2, 3] + # def take_while(&block) Lazy.new(self){|yielder, val| if block.call(val) @@ -147,6 +279,16 @@ class Enumerator } end + # + # call-seq: + # lazy.flat_map {|obj| block } -> lazy_enumerator + # lazy.collect_concat {|obj| block } -> lazy_enumerator + # + # Like Enumerable#flat_map, but chains operation to be lazy-evaluated. + # + # ["foo", "bar"].lazy.flat_map {|i| i.each_char.lazy}.force + # #=> ["f", "o", "o", "b", "a", "r"] + # def flat_map(&block) Lazy.new(self){|yielder, val| ary = block.call(val) @@ -158,6 +300,17 @@ class Enumerator end alias collect_concat flat_map + # + # call-seq: + # lazy.zip(arg, ...) -> lazy_enumerator + # lazy.zip(arg, ...) {|arr| block } -> lazy_enumerator + # + # Like Enumerable#zip, but chains operation to be lazy-evaluated. + # However, if a block is given to zip, values are enumerated immediately. + # + # (1..Float::INFINITY).lazy.zip(('a'..'z').cycle).first(3) + # #=> [[1, "a"], [2, "b"], [3, "c"]] + # def zip(*args, &block) enums = [self] + args Lazy.new(self){|yielder, val| @@ -170,6 +323,16 @@ class Enumerator } end + # + # call-seq: + # lazy.uniq -> lazy_enumerator + # lazy.uniq {|item| block } -> lazy_enumerator + # + # Like Enumerable#uniq, but chains operation to be lazy-evaluated. + # + # (1..Float::INFINITY).lazy.map {|i| i % 3}.uniq.first(3) + # #=> [1, 2, 0] + # def uniq(&block) hash = {} Lazy.new(self){|yielder, val| @@ -185,6 +348,16 @@ class Enumerator } end + # + # call-seq: + # lazy.force -> array + # + # Forces lazy evaluation and returns an array containing the values + # enumerated by the lazy enumerator. This is an alias for to_a. + # + # (1..Float::INFINITY).lazy.take(3).force + # #=> [1, 2, 3] + # alias force to_a end end diff --git a/mrbgems/mruby-enumerator/mrblib/enumerator.rb b/mrbgems/mruby-enumerator/mrblib/enumerator.rb index 470df36ec..81bed7644 100644 --- a/mrbgems/mruby-enumerator/mrblib/enumerator.rb +++ b/mrbgems/mruby-enumerator/mrblib/enumerator.rb @@ -96,7 +96,7 @@ class Enumerator # # In the first form, iteration is defined by the given block, in # which a "yielder" object, given as block parameter, can be used to - # yield a value by calling the +yield+ method (aliased as +<<+): + # yield a value by calling the `yield` method (aliased as +<<+): # # fib = Enumerator.new do |y| # a = b = 1 @@ -158,10 +158,10 @@ class Enumerator # e.with_index(offset = 0) # # Iterates the given block for each element with an index, which - # starts from +offset+. If no block is given, returns a new Enumerator - # that includes the index, starting from +offset+ + # starts from `offset`. If no block is given, returns a new Enumerator + # that includes the index, starting from `offset` # - # +offset+:: the starting index to use + # `offset`:: the starting index to use # def with_index(offset=0, &block) return to_enum :with_index, offset unless block @@ -199,8 +199,8 @@ class Enumerator # e.with_object(obj) {|(*args), obj| ... } # e.with_object(obj) # - # Iterates the given block for each element with an arbitrary object, +obj+, - # and returns +obj+ + # Iterates the given block for each element with an arbitrary object, `obj`, + # and returns `obj` # # If no block is given, returns a new Enumerator. # @@ -229,6 +229,15 @@ class Enumerator object end + ## + # call-seq: + # enum.inspect -> string + # + # Returns a string representation of the enumerator. + # + # [1, 2, 3].each.inspect #=> "#" + # [1, 2, 3].map.inspect #=> "#" + # def inspect if @args && @args.size > 0 args = @args.join(", ") @@ -238,6 +247,16 @@ class Enumerator end end + ## + # call-seq: + # enum.size -> int, float, or nil + # + # Returns the size of the enumerator, or nil if it cannot be calculated lazily. + # + # [1, 2, 3].each.size #=> 3 + # (1..100).each.size #=> 100 + # loop.size #=> nil + # def size if @size @size @@ -320,7 +339,7 @@ class Enumerator # p e.next #=> 3 # p e.next #raises StopIteration # - # Note that enumeration sequence by +next+ does not affect other non-external + # Note that enumeration sequence by `next` does not affect other non-external # enumeration methods, unless the underlying iteration methods itself has # side-effect # @@ -336,8 +355,8 @@ class Enumerator # internal position forward. When the position reached at the end, # StopIteration is raised. # - # This method can be used to distinguish yield and yield - # nil. + # This method can be used to distinguish `yield` and `yield + # nil`. # # === Example # @@ -369,7 +388,7 @@ class Enumerator # # yield nil [nil] nil # # yield [1, 2] [[1, 2]] [1, 2] # - # Note that +next_values+ does not affect other non-external enumeration + # Note that `next_values` does not affect other non-external enumeration # methods unless underlying iteration method itself has side-effect # def next_values @@ -491,7 +510,7 @@ class Enumerator # call-seq: # e.feed obj -> nil # - # Sets the value to be returned by the next yield inside +e+. + # Sets the value to be returned by the next yield inside `e`. # # If the value is not set, the yield returns nil. # @@ -577,9 +596,9 @@ class Enumerator # # Creates an infinite enumerator from any block, just called over and # over. Result of the previous iteration is passed to the next one. - # If +initial+ is provided, it is passed to the first iteration, and + # If `initial` is provided, it is passed to the first iteration, and # becomes the first element of the enumerator; if it is not provided, - # first iteration receives +nil+, and its result becomes first + # first iteration receives `nil`, and its result becomes first # element of the iterator. # # Raising StopIteration from the block stops an iteration. @@ -618,8 +637,8 @@ module Kernel # obj.to_enum(method = :each, *args) -> enum # obj.enum_for(method = :each, *args) -> enum # - # Creates a new Enumerator which will enumerate by calling +method+ on - # +obj+, passing +args+ if any. + # Creates a new Enumerator which will enumerate by calling `method` on + # `obj`, passing `args` if any. # # === Examples # @@ -729,14 +748,14 @@ module Enumerable # e.next # => [2, [6, 7, 8]] # e.next # => [3, [9, 10]] # - # You can use the special symbol :_alone to force an element + # You can use the special symbol `:_alone` to force an element # into its own separate chuck: # # a = [0, 0, 1, 1] # e = a.chunk{|i| i.even? ? :_alone : true } # e.to_a # => [[:_alone, [0]], [:_alone, [0]], [true, [1, 1]]] # - # You can use the special symbol :_separator or +nil+ + # You can use the special symbol `:_separator` or `nil` # to force an element to be ignored (not included in any chunk): # # a = [0, 0, -1, 1, 1] @@ -782,17 +801,17 @@ module Enumerable # _elt_before_ and _elt_after_, # in the receiver enumerator. # This method split chunks between _elt_before_ and _elt_after_ where - # the block returns false. + # the block returns `false`. # # The block is called the length of the receiver enumerator minus one. # # The result enumerator yields the chunked elements as an array. - # So +each+ method can be called as follows: + # So `each` method can be called as follows: # # enum.chunk_while { |elt_before, elt_after| bool }.each { |ary| ... } # # Other methods of the Enumerator class and Enumerable module, - # such as +to_a+, +map+, etc., are also usable. + # such as `to_a`, `map`, etc., are also usable. # # For example, one-by-one increasing subsequence can be chunked as follows: # @@ -818,7 +837,7 @@ module Enumerable # #=> [[7, 5, 9], [2, 0], [7, 9], [4, 2, 0]] # # Enumerable#slice_when does the same, except splitting when the block - # returns true instead of false. + # returns `true` instead of `false`. # def chunk_while(&block) enum = self diff --git a/mrbgems/mruby-errno/mrblib/errno.rb b/mrbgems/mruby-errno/mrblib/errno.rb index 52899396b..b85f705ee 100644 --- a/mrbgems/mruby-errno/mrblib/errno.rb +++ b/mrbgems/mruby-errno/mrblib/errno.rb @@ -1,14 +1,49 @@ module Errno + # + # call-seq: + # Errno.const_defined?(name) -> true or false + # + # Returns true if the given name is defined as an errno constant, + # false otherwise. This method checks both system-defined errno + # constants and those defined by the superclass. + # + # Errno.const_defined?(:ENOENT) #=> true + # Errno.const_defined?(:EINVAL) #=> true + # Errno.const_defined?(:UNKNOWN) #=> false + # def Errno.const_defined?(name) __errno_defined?(name) or super end + # + # call-seq: + # Errno.const_missing(name) -> errno_class + # + # Called when an undefined constant is referenced. This method + # attempts to define the errno constant if it exists in the system, + # otherwise delegates to the superclass. + # + # Errno::ENOENT # triggers const_missing if not yet defined + # #=> Errno::ENOENT + # def Errno.const_missing(name) __errno_define(name) or super end - # Module#constants is defined in mruby-metaprog - # So, it may be raised NoMethodError + # + # call-seq: + # Errno.constants -> array + # + # Returns an array of all errno constant names available on the system. + # This includes both already defined constants and those that can be + # dynamically defined. + # + # Errno.constants + # #=> [:EPERM, :ENOENT, :ESRCH, :EINTR, :EIO, ...] + # + # Note: Module#constants is defined in mruby-metaprog, so this method + # may raise NoMethodError if that gem is not available. + # def Errno.constants __errno_list(super) end diff --git a/mrbgems/mruby-errno/src/errno.c b/mrbgems/mruby-errno/src/errno.c index a9e9e26c5..4069ee2d6 100644 --- a/mrbgems/mruby-errno/src/errno.c +++ b/mrbgems/mruby-errno/src/errno.c @@ -335,13 +335,11 @@ mrb_sce_errno(mrb_state *mrb, mrb_value self) static mrb_value mrb_sce_sys_fail(mrb_state *mrb, mrb_value cls) { - struct RClass *sce; mrb_value msg, no; - mrb_int argc; mrb->c->ci->mid = 0; - sce = mrb_class_ptr(cls); - argc = mrb_get_args(mrb, "o|S", &no, &msg); + struct RClass *sce = mrb_class_ptr(cls); + mrb_int argc = mrb_get_args(mrb, "o|S", &no, &msg); struct RBasic* e = mrb_obj_alloc(mrb, MRB_TT_EXCEPTION, sce); mrb_value exc = mrb_obj_value(e); diff --git a/mrbgems/mruby-error/src/exception.c b/mrbgems/mruby-error/src/exception.c index b43a3ef05..b2b280356 100644 --- a/mrbgems/mruby-error/src/exception.c +++ b/mrbgems/mruby-error/src/exception.c @@ -1,11 +1,15 @@ #include #include +/* Helper structure to pass function and data to protection wrapper */ struct protect_data { - mrb_func_t body; - mrb_value data; + mrb_func_t body; /* Function to be executed under protection */ + mrb_value data; /* Data to be passed to the function */ }; +/* Helper function that wraps user function calls for exception protection. + * Extracts function and data from protect_data structure and calls the + * user function with proper parameters. Used internally by mrb_protect. */ static mrb_value protect_body(mrb_state *mrb, void *p) { @@ -13,6 +17,19 @@ protect_body(mrb_state *mrb, void *p) return dp->body(mrb, dp->data); } +/* + * Executes a function under exception protection. + * + * @param mrb mruby state + * @param body Function to execute under protection + * @param data Data to pass to the function + * @param state Pointer to store exception state (true if exception occurred) + * @return Return value from body function, or exception object if error occurred + * + * This function provides a C API equivalent to Ruby's begin/rescue blocks. + * If an exception occurs during execution of body, it will be caught and + * the exception object returned, with *state set to true. + */ MRB_API mrb_value mrb_protect(mrb_state *mrb, mrb_func_t body, mrb_value data, mrb_bool *state) { @@ -20,6 +37,21 @@ mrb_protect(mrb_state *mrb, mrb_func_t body, mrb_value data, mrb_bool *state) return mrb_protect_error(mrb, protect_body, &protect_data, state); } +/* + * Executes a function with guaranteed cleanup (ensure block). + * + * @param mrb mruby state + * @param body Main function to execute + * @param b_data Data to pass to body function + * @param ensure Cleanup function that always executes + * @param e_data Data to pass to ensure function + * @return Return value from body function + * + * This function provides a C API equivalent to Ruby's begin/ensure blocks. + * The ensure function is guaranteed to execute regardless of whether the + * body function completes normally or raises an exception. If an exception + * occurs in the body, it will be re-raised after the ensure block executes. + */ MRB_API mrb_value mrb_ensure(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t ensure, mrb_value e_data) { @@ -36,6 +68,20 @@ mrb_ensure(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t ensure, return result; } +/* + * Executes a function with exception handling for StandardError and its subclasses. + * + * @param mrb mruby state + * @param body Main function to execute + * @param b_data Data to pass to body function + * @param rescue Exception handler function + * @param r_data Data to pass to rescue function + * @return Return value from body function, or rescue function if StandardError occurred + * + * This function provides a C API equivalent to Ruby's begin/rescue blocks that + * catch StandardError. It's a convenience wrapper around mrb_rescue_exceptions + * that automatically handles StandardError and its subclasses. + */ MRB_API mrb_value mrb_rescue(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t rescue, mrb_value r_data) @@ -43,6 +89,22 @@ mrb_rescue(mrb_state *mrb, mrb_func_t body, mrb_value b_data, return mrb_rescue_exceptions(mrb, body, b_data, rescue, r_data, 1, &mrb->eStandardError_class); } +/* + * Executes a function with exception handling for specific exception classes. + * + * @param mrb mruby state + * @param body Main function to execute + * @param b_data Data to pass to body function + * @param rescue Exception handler function + * @param r_data Data to pass to rescue function + * @param len Number of exception classes to handle + * @param classes Array of exception classes to catch + * @return Return value from body function, or rescue function if matching exception occurred + * + * This function provides a C API equivalent to Ruby's begin/rescue blocks with + * specific exception class handling. Only exceptions that are instances of the + * specified classes will be caught; others will be re-raised. + */ MRB_API mrb_value mrb_rescue_exceptions(mrb_state *mrb, mrb_func_t body, mrb_value b_data, mrb_func_t rescue, mrb_value r_data, mrb_int len, struct RClass **classes) diff --git a/mrbgems/mruby-error/test/exception.c b/mrbgems/mruby-error/test/exception.c index 4de0e9607..e5292bba2 100644 --- a/mrbgems/mruby-error/test/exception.c +++ b/mrbgems/mruby-error/test/exception.c @@ -12,9 +12,10 @@ static mrb_value run_protect(mrb_state *mrb, mrb_value self) { mrb_value b; - mrb_value ret[2]; - mrb_bool state; mrb_get_args(mrb, "&", &b); + + mrb_bool state; + mrb_value ret[2]; ret[0] = mrb_protect(mrb, protect_cb, b, &state); ret[1] = mrb_bool_value(state); return mrb_ary_new_from_values(mrb, 2, ret); diff --git a/mrbgems/mruby-eval/src/eval.c b/mrbgems/mruby-eval/src/eval.c index 1189417f5..3ac080769 100644 --- a/mrbgems/mruby-eval/src/eval.c +++ b/mrbgems/mruby-eval/src/eval.c @@ -285,14 +285,13 @@ f_eval(mrb_state *mrb, mrb_value self) mrb_value binding = mrb_nil_value(); const char *file = NULL; mrb_int line = 1; - struct RProc *proc; mrb_get_args(mrb, "s|ozi", &s, &len, &binding, &file, &line); if (!mrb_nil_p(binding)) { binding_eval_prepare(mrb, binding, s, len, file); } - proc = create_proc_from_string(mrb, s, len, binding, file, line); + struct RProc *proc = create_proc_from_string(mrb, s, len, binding, file, line); if (!mrb_nil_p(binding)) { self = mrb_iv_get(mrb, binding, MRB_SYM(recv)); } @@ -332,12 +331,10 @@ f_instance_eval(mrb_state *mrb, mrb_value self) mrb_int len; const char *file = NULL; mrb_int line = 1; - struct RClass *c; - struct RProc *proc; mrb_get_args(mrb, "s|zi", &s, &len, &file, &line); - c = mrb_singleton_class_ptr(mrb, self); - proc = create_proc_from_string(mrb, s, len, mrb_nil_value(), 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); @@ -378,10 +375,9 @@ f_class_eval(mrb_state *mrb, mrb_value self) mrb_int len; const char *file = NULL; mrb_int line = 1; - struct RProc *proc; mrb_get_args(mrb, "s|zi", &s, &len, &file, &line); - proc = create_proc_from_string(mrb, s, len, mrb_nil_value(), 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)); diff --git a/mrbgems/mruby-exit/src/mruby_exit.c b/mrbgems/mruby-exit/src/mruby_exit.c index 3e3a56232..7ba153a54 100644 --- a/mrbgems/mruby-exit/src/mruby_exit.c +++ b/mrbgems/mruby-exit/src/mruby_exit.c @@ -32,7 +32,7 @@ get_status(mrb_state *mrb) * optional parameter is used to return a status code to the invoking * environment. * - * +true+ and +false+ of _status_ means success and failure + * `true` and `false` of _status_ means success and failure * respectively. The interpretation of other integer values are * system dependent. * diff --git a/mrbgems/mruby-fiber/src/fiber.c b/mrbgems/mruby-fiber/src/fiber.c index a31c4a524..4fcb5cb56 100644 --- a/mrbgems/mruby-fiber/src/fiber.c +++ b/mrbgems/mruby-fiber/src/fiber.c @@ -11,7 +11,7 @@ #define fiber_ptr(o) ((struct RFiber*)mrb_ptr(o)) #define FIBER_STACK_INIT_SIZE 64 -#define FIBER_CI_INIT_SIZE 8 +#define FIBER_CI_INIT_SIZE 4 /* copied from vm.c */ #define CINFO_RESUMED 3 @@ -79,10 +79,10 @@ init_fiber(mrb_state *mrb, struct RFiber *f, const struct RProc *p) * Fiber.new{...} -> obj * * Creates a fiber, whose execution is suspended until it is explicitly - * resumed using Fiber#resume method. + * resumed using `Fiber#resume` method. * The code running inside the fiber can give up control by calling - * Fiber.yield in which case it yields control back to caller - * (the caller of the Fiber#resume). + * `Fiber.yield` in which case it yields control back to caller + * (the caller of the `Fiber#resume`). * * Upon yielding or termination the Fiber returns the value of the last * executed expression @@ -104,10 +104,10 @@ init_fiber(mrb_state *mrb, struct RFiber *f, const struct RProc *p) * 2 * resuming dead fiber (FiberError) * - * The Fiber#resume method accepts an arbitrary number of - * parameters, if it is the first call to resume then they + * The `Fiber#resume` method accepts an arbitrary number of + * parameters, if it is the first call to `resume` then they * will be passed as block arguments. Otherwise they will be the return - * value of the call to Fiber.yield + * value of the call to `Fiber.yield` * * Example: * @@ -306,16 +306,16 @@ fiber_switch(mrb_state *mrb, mrb_value self, mrb_int len, const mrb_value *a, mr * call-seq: * fiber.resume(args, ...) -> obj * - * Resumes the fiber from the point at which the last Fiber.yield + * Resumes the fiber from the point at which the last `Fiber.yield` * was called, or starts running it if it is the first call to - * resume. Arguments passed to resume will be the value of - * the Fiber.yield expression or will be passed as block - * parameters to the fiber's block if this is the first resume. + * `resume`. Arguments passed to resume will be the value of + * the `Fiber.yield` expression or will be passed as block + * parameters to the fiber's block if this is the first `resume`. * * Alternatively, when resume is called it evaluates to the arguments passed - * to the next Fiber.yield statement inside the fiber's block + * to the next `Fiber.yield` statement inside the fiber's block * or to the block value if it runs to completion without any - * Fiber.yield + * `Fiber.yield` */ static mrb_value fiber_resume(mrb_state *mrb, mrb_value self) @@ -379,7 +379,13 @@ fiber_to_s(mrb_state *mrb, mrb_value self) fiber_check(mrb, self); const struct RFiber *f = fiber_ptr(self); - mrb_value s = mrb_str_new_lit(mrb, "#<"); + /* Cache status to avoid redundant lookups */ + enum mrb_fiber_state status = f->cxt->status; + + /* Pre-allocate buffer - 150 bytes handles typical fiber strings */ + mrb_value s = mrb_str_buf_new(mrb, 150); + + mrb_str_cat_lit(mrb, s, "#<"); mrb_value cname = mrb_class_path(mrb, mrb_class_real(mrb_class(mrb, self))); if (mrb_nil_p(cname)) { mrb_str_cat_lit(mrb, s, "Fiber:"); @@ -393,7 +399,7 @@ fiber_to_s(mrb_state *mrb, mrb_value self) const char *file; int32_t line; const struct RProc *p; - if (f->cxt->status != MRB_FIBER_TERMINATED && + if (status != MRB_FIBER_TERMINATED && !MRB_PROC_CFUNC_P(p = f->cxt->cibase->proc) && !MRB_PROC_ALIAS_P(p) && mrb_debug_get_position(mrb, p->body.irep, 0, &line, &file)) { mrb_str_cat_lit(mrb, s, " "); @@ -404,7 +410,7 @@ fiber_to_s(mrb_state *mrb, mrb_value self) } const char *st; - switch (fiber_ptr(self)->cxt->status) { + switch (status) { case MRB_FIBER_CREATED: st = "created"; break; case MRB_FIBER_RUNNING: st = "resumed"; break; case MRB_FIBER_RESUMED: st = "suspended by resuming"; break; @@ -425,12 +431,12 @@ fiber_to_s(mrb_state *mrb, mrb_value self) * fiber.transfer(args, ...) -> obj * * Transfers control to receiver fiber of the method call. - * Unlike resume the receiver wouldn't be pushed to call + * Unlike `resume` the receiver wouldn't be pushed to call * stack of fibers. Instead it will switch to the call stack of * transferring fiber. * When resuming a fiber that was transferred to another fiber it would * cause double resume error. Though when the fiber is re-transferred - * and Fiber.yield is called, the fiber would be resumable. + * and `Fiber.yield` is called, the fiber would be resumable. */ static mrb_value fiber_transfer(mrb_state *mrb, mrb_value self) @@ -493,14 +499,14 @@ mrb_fiber_yield(mrb_state *mrb, mrb_int len, const mrb_value *a) * * Yields control back to the context that resumed the fiber, passing * along any arguments that were passed to it. The fiber will resume - * processing at this point when resume is called next. - * Any arguments passed to the next resume will be the + * processing at this point when `resume` is called next. + * Any arguments passed to the next `resume` will be the * * mruby limitation: Fiber resume/yield cannot cross C function boundary. * thus you cannot yield from #initialize which is called by mrb_funcall(). * - * This method cannot be called from C using mrb_funcall(). - * Use mrb_fiber_yield() function instead. + * This method cannot be called from C using `mrb_funcall()`. + * Use `mrb_fiber_yield()` function instead. */ static mrb_value fiber_yield(mrb_state *mrb, mrb_value self) diff --git a/mrbgems/mruby-hash-ext/mrblib/hash.rb b/mrbgems/mruby-hash-ext/mrblib/hash.rb index 97e1031f9..dbebb82d6 100644 --- a/mrbgems/mruby-hash-ext/mrblib/hash.rb +++ b/mrbgems/mruby-hash-ext/mrblib/hash.rb @@ -79,7 +79,7 @@ class Hash # # Returns a value from the hash for the given key. If the key can't be # found, there are several options: With no other arguments, it will - # raise an KeyError exception; if default is + # raise an `KeyError` exception; if *default* is # given, then that will be returned; if the optional code block is # specified, then that will be run and its result returned. # @@ -119,8 +119,8 @@ class Hash # hsh.delete_if {| key, value | block } -> hsh # hsh.delete_if -> an_enumerator # - # Deletes every key-value pair from hsh for which block - # evaluates to true. + # Deletes every key-value pair from *hsh* for which *block* + # evaluates to `true`. # # If no block is given, an enumerator is returned instead. # @@ -146,7 +146,7 @@ class Hash # hash. That is, for every key or value that is an array, extract # its elements into the new array. Unlike Array#flatten, this # method does not flatten recursively by default. The optional - # level argument determines the level of recursion to flatten. + # *level* argument determines the level of recursion to flatten. # # a = {1=> "one", 2 => [2,"two"], 3 => "three"} # a.flatten # => [1, "one", 2, [2, "two"], 3, "three"] @@ -161,7 +161,7 @@ class Hash # call-seq: # hsh.invert -> new_hash # - # Returns a new hash created by using hsh's values as keys, and + # Returns a new hash created by using *hsh*'s values as keys, and # the keys as values. # # h = { "n" => 100, "m" => 100, "y" => 300, "d" => 200, "a" => 0 } @@ -179,7 +179,7 @@ class Hash # hsh.keep_if {| key, value | block } -> hsh # hsh.keep_if -> an_enumerator # - # Deletes every key-value pair from hsh for which block + # Deletes every key-value pair from *hsh* for which *block* # evaluates to false. # # If no block is given, an enumerator is returned instead. @@ -201,7 +201,7 @@ class Hash # call-seq: # hsh.to_h -> hsh or new_hash # - # Returns +self+. If called on a subclass of Hash, converts + # Returns `self`. If called on a subclass of Hash, converts # the receiver to a Hash object. # def to_h @@ -212,8 +212,8 @@ class Hash # call-seq: # hash < other -> true or false # - # Returns true if hash is subset of - # other. + # Returns `true` if *hash* is subset of + # *other*. # # h1 = {a:1, b:2} # h2 = {a:1, b:2, c:3} @@ -232,8 +232,8 @@ class Hash # call-seq: # hash <= other -> true or false # - # Returns true if hash is subset of - # other or equals to other. + # Returns `true` if *hash* is subset of + # *other* or equals to *other*. # # h1 = {a:1, b:2} # h2 = {a:1, b:2, c:3} @@ -252,8 +252,8 @@ class Hash # call-seq: # hash > other -> true or false # - # Returns true if other is subset of - # hash. + # Returns `true` if *other* is subset of + # *hash*. # # h1 = {a:1, b:2} # h2 = {a:1, b:2, c:3} @@ -272,8 +272,8 @@ class Hash # call-seq: # hash >= other -> true or false # - # Returns true if other is subset of - # hash or equals to hash. + # Returns `true` if *other* is subset of + # *hash* or equals to *hash*. # # h1 = {a:1, b:2} # h2 = {a:1, b:2, c:3} @@ -292,9 +292,9 @@ class Hash # call-seq: # hsh.dig(key,...) -> object # - # Extracts the nested value specified by the sequence of key - # objects by calling +dig+ at each step, returning +nil+ if any - # intermediate step is +nil+. + # Extracts the nested value specified by the sequence of *key* + # objects by calling `dig` at each step, returning `nil` if any + # intermediate step is `nil`. # def dig(idx,*args) n = self[idx] @@ -329,8 +329,8 @@ class Hash # hsh.transform_keys! {|key| block } -> hsh # hsh.transform_keys! -> an_enumerator # - # Invokes the given block once for each key in hsh, replacing it - # with the new key returned by the block, and then returns hsh. + # Invokes the given block once for each key in *hsh*, replacing it + # with the new key returned by the block, and then returns *hsh*. # # If no block is given, an enumerator is returned instead. # @@ -366,7 +366,7 @@ class Hash # hsh.transform_values! -> an_enumerator # # Invokes the given block once for each value in the hash, replacing - # with the new value returned by the block, and then returns hsh. + # with the new value returned by the block, and then returns *hsh*. # # If no block is given, an enumerator is returned instead. # @@ -388,8 +388,8 @@ class Hash # hsh.fetch_values(key, ...) { |key| block } -> array # # Returns an array containing the values associated with the given keys - # but also raises KeyError when one of keys can't be found. - # Also see Hash#values_at and Hash#fetch. + # but also raises `KeyError` when one of keys can't be found. + # Also see `Hash#values_at` and `Hash#fetch`. # # h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" } # diff --git a/mrbgems/mruby-hash-ext/src/hash_ext.c b/mrbgems/mruby-hash-ext/src/hash_ext.c index 4b63e4844..652662a9f 100644 --- a/mrbgems/mruby-hash-ext/src/hash_ext.c +++ b/mrbgems/mruby-hash-ext/src/hash_ext.c @@ -15,7 +15,7 @@ * hsh.values_at(key, ...) -> array * * Return an array containing the values associated with the given keys. - * Also see Hash.select. + * Also see `Hash.select`. * * h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" } * h.values_at("cow", "cat") #=> ["bovine", "feline"] @@ -25,14 +25,13 @@ static mrb_value hash_values_at(mrb_state *mrb, mrb_value hash) { const mrb_value *argv; - mrb_value result; mrb_int argc; - int ai; mrb_get_args(mrb, "*", &argv, &argc); - result = mrb_ary_new_capa(mrb, argc); + mrb_value result = mrb_ary_new_capa(mrb, argc); if (argc == 0) return result; - ai = mrb_gc_arena_save(mrb); + + int ai = mrb_gc_arena_save(mrb); for (mrb_int i = 0; i < argc; i++) { mrb_ary_push(mrb, result, mrb_hash_get(mrb, hash, argv[i])); mrb_gc_arena_restore(mrb, ai); @@ -54,11 +53,10 @@ static mrb_value hash_slice(mrb_state *mrb, mrb_value hash) { const mrb_value *argv; - mrb_value result; mrb_int argc; mrb_get_args(mrb, "*", &argv, &argc); - result = mrb_hash_new_capa(mrb, argc); + mrb_value result = mrb_hash_new_capa(mrb, argc); if (argc == 0) return result; /* empty hash */ for (mrb_int i = 0; i < argc; i++) { mrb_value key = argv[i]; @@ -105,7 +103,7 @@ slice_bang_i(mrb_state *mrb, mrb_value key, mrb_value val, void *data) * call-seq: * hsh.slice!(*keys) -> a_hash * - * Deletes keys from hsh that are not in +keys+. + * Deletes keys from hsh that are not in `keys`. * Returns a new hash containing the deleted key-value pairs. * * h = { a: 1, b: 2, c: 3, d: 4 } @@ -115,25 +113,23 @@ slice_bang_i(mrb_state *mrb, mrb_value key, mrb_value val, void *data) static mrb_value hash_slice_bang(mrb_state *mrb, mrb_value self) { + struct slice_bang_i_arg args; const mrb_value *argv; mrb_int argc; - mrb_value removed_hash; - struct slice_bang_i_arg args; - mrb_int i, len; mrb_get_args(mrb, "*", &argv, &argc); args.keep_keys = mrb_hash_new_capa(mrb, argc); - for (i = 0; i < argc; i++) { + for (mrb_int i = 0; i < argc; i++) { mrb_hash_set(mrb, args.keep_keys, argv[i], mrb_true_value()); } args.keys_to_remove = mrb_ary_new(mrb); mrb_hash_foreach(mrb, mrb_hash_ptr(self), slice_bang_i, &args); - len = RARRAY_LEN(args.keys_to_remove); - removed_hash = mrb_hash_new_capa(mrb, len); - for (i = 0; i < len; i++) { + mrb_int len = RARRAY_LEN(args.keys_to_remove); + mrb_value removed_hash = mrb_hash_new_capa(mrb, len); + for (mrb_int i = 0; i < len; i++) { mrb_value key = mrb_ary_ref(mrb, args.keys_to_remove, i); mrb_value val = mrb_hash_delete_key(mrb, self, key); mrb_hash_set(mrb, removed_hash, key, val); @@ -156,11 +152,10 @@ static mrb_value hash_except(mrb_state *mrb, mrb_value hash) { const mrb_value *argv; - mrb_value result; mrb_int argc; mrb_get_args(mrb, "*", &argv, &argc); - result = mrb_hash_dup(mrb, hash); + mrb_value result = mrb_hash_dup(mrb, hash); for (mrb_int i = 0; i < argc; i++) { mrb_hash_delete_key(mrb, result, argv[i]); } @@ -309,7 +304,7 @@ hash_key_i(mrb_state *mrb, mrb_value key, mrb_value val, void *data) * hsh.key(value) -> key * * Returns the key of an occurrence of a given value. If the value is - * not found, returns nil. + * not found, returns `nil`. * * h = { "a" => 100, "b" => 200, "c" => 300, "d" => 300 } * h.key(200) #=> "b" diff --git a/mrbgems/mruby-io/include/io_hal.h b/mrbgems/mruby-io/include/io_hal.h new file mode 100644 index 000000000..53bde4dea --- /dev/null +++ b/mrbgems/mruby-io/include/io_hal.h @@ -0,0 +1,451 @@ +/* +** io_hal.h - IO Hardware Abstraction Layer (HAL) +** +** See Copyright Notice in mruby.h +** +** This header defines the HAL interface for platform-specific I/O operations. +** Platform-specific implementations (hal-posix-io, hal-win-io, etc.) must +** provide all functions declared here. +*/ + +#ifndef MRUBY_IO_HAL_H +#define MRUBY_IO_HAL_H + +#include +#include + +/* + * Platform-independent type definitions + */ + +/* 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_io_stat; + +/* Timeval structure for select() */ +typedef struct mrb_io_timeval { + int64_t tv_sec; /* Seconds */ + int64_t tv_usec; /* Microseconds */ +} mrb_io_timeval; + +/* File descriptor set for select() */ +typedef struct mrb_io_fdset mrb_io_fdset; + +/* + * File mode constants (POSIX-style) + */ + +/* File type masks */ +#define MRB_IO_S_IFMT 0170000 /* Type of file mask */ +#define MRB_IO_S_IFSOCK 0140000 /* Socket */ +#define MRB_IO_S_IFLNK 0120000 /* Symbolic link */ +#define MRB_IO_S_IFREG 0100000 /* Regular file */ +#define MRB_IO_S_IFBLK 0060000 /* Block device */ +#define MRB_IO_S_IFDIR 0040000 /* Directory */ +#define MRB_IO_S_IFCHR 0020000 /* Character device */ +#define MRB_IO_S_IFIFO 0010000 /* FIFO */ + +/* File type test macros */ +#define MRB_IO_S_ISREG(m) (((m) & MRB_IO_S_IFMT) == MRB_IO_S_IFREG) +#define MRB_IO_S_ISDIR(m) (((m) & MRB_IO_S_IFMT) == MRB_IO_S_IFDIR) +#define MRB_IO_S_ISCHR(m) (((m) & MRB_IO_S_IFMT) == MRB_IO_S_IFCHR) +#define MRB_IO_S_ISBLK(m) (((m) & MRB_IO_S_IFMT) == MRB_IO_S_IFBLK) +#define MRB_IO_S_ISFIFO(m) (((m) & MRB_IO_S_IFMT) == MRB_IO_S_IFIFO) +#define MRB_IO_S_ISLNK(m) (((m) & MRB_IO_S_IFMT) == MRB_IO_S_IFLNK) +#define MRB_IO_S_ISSOCK(m) (((m) & MRB_IO_S_IFMT) == MRB_IO_S_IFSOCK) + +/* File lock constants */ +#define MRB_IO_LOCK_SH 1 /* Shared lock */ +#define MRB_IO_LOCK_EX 2 /* Exclusive lock */ +#define MRB_IO_LOCK_NB 4 /* Non-blocking */ +#define MRB_IO_LOCK_UN 8 /* Unlock */ + +/* Seek constants */ +#define MRB_IO_SEEK_SET 0 /* Seek from beginning */ +#define MRB_IO_SEEK_CUR 1 /* Seek from current position */ +#define MRB_IO_SEEK_END 2 /* Seek from end */ + +/* + * HAL Interface - File Operations + */ + +/** + * Get file status by path + * + * @param mrb mruby state + * @param path File path (UTF-8) + * @param st Output stat structure + * @return 0 on success, -1 on error (sets errno) + */ +int mrb_hal_io_stat(mrb_state *mrb, const char *path, mrb_io_stat *st); + +/** + * Get file status by descriptor + * + * @param mrb mruby state + * @param fd File descriptor + * @param st Output stat structure + * @return 0 on success, -1 on error (sets errno) + */ +int mrb_hal_io_fstat(mrb_state *mrb, int fd, mrb_io_stat *st); + +/** + * Get link status (don't follow symlinks) + * + * @param mrb mruby state + * @param path File path (UTF-8) + * @param st Output stat structure + * @return 0 on success, -1 on error (sets errno) + */ +int mrb_hal_io_lstat(mrb_state *mrb, const char *path, mrb_io_stat *st); + +/** + * Change file permissions + * + * @param mrb mruby state + * @param path File path (UTF-8) + * @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); + +/** + * Set/get file creation mask + * + * @param mrb mruby state + * @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); + +/** + * Truncate file to specified length + * + * @param mrb mruby state + * @param fd File descriptor + * @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); + +/** + * Apply or remove advisory lock on file + * + * @param mrb mruby state + * @param fd File descriptor + * @param operation Lock operation (MRB_IO_LOCK_*) + * @return 0 on success, -1 on error (sets errno) + */ +int mrb_hal_io_flock(mrb_state *mrb, int fd, int operation); + +/** + * Delete a file + * + * @param mrb mruby state + * @param path File path (UTF-8) + * @return 0 on success, -1 on error (sets errno) + */ +int mrb_hal_io_unlink(mrb_state *mrb, const char *path); + +/** + * Rename a file + * + * @param mrb mruby state + * @param oldpath Old file path (UTF-8) + * @param newpath New file path (UTF-8) + * @return 0 on success, -1 on error (sets errno) + */ +int mrb_hal_io_rename(mrb_state *mrb, const char *oldpath, const char *newpath); + +/** + * Create a symbolic link + * + * @param mrb mruby state + * @param target Target path (UTF-8) + * @param linkpath Link path (UTF-8) + * @return 0 on success, -1 on error (sets errno) + */ +int mrb_hal_io_symlink(mrb_state *mrb, const char *target, const char *linkpath); + +/** + * Read value of a symbolic link + * + * @param mrb mruby state + * @param path Symlink path (UTF-8) + * @param buf Buffer to store result (UTF-8) + * @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); + +/** + * Resolve pathname to absolute path + * + * @param mrb mruby state + * @param path Pathname (UTF-8) + * @param resolved Buffer for resolved path (must be at least PATH_MAX size) + * @return Pointer to resolved on success, NULL on error (sets errno) + */ +char* mrb_hal_io_realpath(mrb_state *mrb, const char *path, char *resolved); + +/** + * Get current working directory + * + * @param mrb mruby state + * @param buf Buffer to store result (UTF-8) + * @param size Buffer size + * @return Pointer to buf on success, NULL on error (sets errno) + */ +char* mrb_hal_io_getcwd(mrb_state *mrb, char *buf, size_t size); + +/** + * Get environment variable + * + * @param mrb mruby state + * @param name Variable name + * @return Value string (UTF-8) or NULL if not found + */ +const char* mrb_hal_io_getenv(mrb_state *mrb, const char *name); + +/** + * Get user's home directory + * + * @param mrb mruby state + * @param username User name (NULL for current user) + * @return Home directory path (UTF-8) or NULL on error (sets errno) + */ +const char* mrb_hal_io_gethome(mrb_state *mrb, const char *username); + +/* + * HAL Interface - Core I/O Operations + */ + +/** + * Open file + * + * @param mrb mruby state + * @param path File path (UTF-8) + * @param flags Open flags (O_RDONLY, O_WRONLY, O_RDWR, etc.) + * @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); + +/** + * Close file descriptor + * + * @param mrb mruby state + * @param fd File descriptor + * @return 0 on success, -1 on error (sets errno) + */ +int mrb_hal_io_close(mrb_state *mrb, int fd); + +/** + * Read from file descriptor + * + * @param mrb mruby state + * @param fd File descriptor + * @param buf Buffer to store data + * @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); + +/** + * Write to file descriptor + * + * @param mrb mruby state + * @param fd File descriptor + * @param buf Data to write + * @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); + +/** + * Reposition file offset + * + * @param mrb mruby state + * @param fd File descriptor + * @param offset Offset value + * @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); + +/** + * Duplicate file descriptor + * + * @param mrb mruby state + * @param fd File descriptor to duplicate + * @return New descriptor on success, -1 on error (sets errno) + */ +int mrb_hal_io_dup(mrb_state *mrb, int fd); + +/** + * Manipulate file descriptor + * + * @param mrb mruby state + * @param fd File descriptor + * @param cmd Command (F_GETFD, F_SETFD, etc.) + * @param arg Command argument + * @return Depends on command, -1 on error (sets errno) + */ +int mrb_hal_io_fcntl(mrb_state *mrb, int fd, int cmd, int arg); + +/** + * Check if descriptor refers to terminal + * + * @param mrb mruby state + * @param fd File descriptor + * @return 1 if TTY, 0 if not, -1 on error (sets errno) + */ +int mrb_hal_io_isatty(mrb_state *mrb, int fd); + +/** + * Create pipe + * + * @param mrb mruby state + * @param fds Array to store two file descriptors [read_end, write_end] + * @return 0 on success, -1 on error (sets errno) + */ +int mrb_hal_io_pipe(mrb_state *mrb, int fds[2]); + +/* + * HAL Interface - Process Operations + */ + +/** + * Spawn a new process + * + * Creates a new process and executes the command. File descriptors can be + * redirected for stdin/stdout/stderr (-1 means don't redirect). + * + * POSIX: Uses fork() + dup2() + execl() + * Windows: Uses CreateProcess() with STARTUPINFO + * + * @param mrb mruby state + * @param cmd Command to execute (shell command) + * @param stdin_fd File descriptor to use for stdin (-1 = don't redirect) + * @param stdout_fd File descriptor to use for stdout (-1 = don't redirect) + * @param stderr_fd File descriptor to use for stderr (-1 = don't redirect) + * @param pid Output parameter for process ID + * @return 0 on success, -1 on error (sets errno) + */ +int mrb_hal_io_spawn_process(mrb_state *mrb, const char *cmd, + int stdin_fd, int stdout_fd, int stderr_fd, + int *pid); + +/** + * Wait for process to change state + * + * @param mrb mruby state + * @param pid Process ID to wait for + * @param status Output parameter for exit status + * @param options Wait options (0 for blocking wait) + * @return Process ID on success, -1 on error (sets errno) + */ +int mrb_hal_io_waitpid(mrb_state *mrb, int pid, int *status, int options); + +/* + * HAL Interface - I/O Multiplexing + */ + +/** + * Allocate file descriptor set + * + * @param mrb mruby state + * @return Pointer to fdset or NULL on error + */ +mrb_io_fdset* mrb_hal_io_fdset_alloc(mrb_state *mrb); + +/** + * Free file descriptor set + * + * @param mrb mruby state + * @param fdset File descriptor set to free + */ +void mrb_hal_io_fdset_free(mrb_state *mrb, mrb_io_fdset *fdset); + +/** + * Clear file descriptor set + * + * @param mrb mruby state + * @param fdset File descriptor set + */ +void mrb_hal_io_fdset_zero(mrb_state *mrb, mrb_io_fdset *fdset); + +/** + * Add descriptor to set + * + * @param mrb mruby state + * @param fd File descriptor + * @param fdset File descriptor set + */ +void mrb_hal_io_fdset_set(mrb_state *mrb, int fd, mrb_io_fdset *fdset); + +/** + * Check if descriptor is in set + * + * @param mrb mruby state + * @param fd File descriptor + * @param fdset File descriptor set + * @return Non-zero if fd is in set, 0 otherwise + */ +int mrb_hal_io_fdset_isset(mrb_state *mrb, int fd, mrb_io_fdset *fdset); + +/** + * Monitor multiple file descriptors + * + * @param mrb mruby state + * @param nfds Highest file descriptor number + 1 + * @param readfds Set of descriptors to check for reading (NULL = ignore) + * @param writefds Set of descriptors to check for writing (NULL = ignore) + * @param errorfds Set of descriptors to check for errors (NULL = ignore) + * @param timeout Timeout (NULL = block indefinitely) + * @return Number of ready descriptors, 0 on timeout, -1 on error (sets errno) + */ +int mrb_hal_io_select(mrb_state *mrb, int nfds, + mrb_io_fdset *readfds, + mrb_io_fdset *writefds, + mrb_io_fdset *errorfds, + mrb_io_timeval *timeout); + +/* + * HAL Initialization/Finalization + */ + +/** + * Initialize I/O HAL + * + * Called during gem initialization. Platform-specific HAL should perform + * any necessary setup here. + * + * @param mrb mruby state + */ +void mrb_hal_io_init(mrb_state *mrb); + +/** + * Finalize I/O HAL + * + * Called during gem finalization. Platform-specific HAL should perform + * any necessary cleanup here. + * + * @param mrb mruby state + */ +void mrb_hal_io_final(mrb_state *mrb); + +#endif /* MRUBY_IO_HAL_H */ diff --git a/mrbgems/mruby-io/mrbgem.rake b/mrbgems/mruby-io/mrbgem.rake index 3449484e5..6663666df 100644 --- a/mrbgems/mruby-io/mrbgem.rake +++ b/mrbgems/mruby-io/mrbgem.rake @@ -3,9 +3,32 @@ MRuby::Gem::Specification.new('mruby-io') do |spec| spec.authors = ['Internet Initiative Japan Inc.', 'mruby developers'] spec.summary = 'IO and File class' - if spec.for_windows? - spec.linker.libraries << "ws2_32" - end 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 + end end diff --git a/mrbgems/mruby-io/mrblib/file.rb b/mrbgems/mruby-io/mrblib/file.rb index b60b3f820..a1c62d6d7 100644 --- a/mrbgems/mruby-io/mrblib/file.rb +++ b/mrbgems/mruby-io/mrblib/file.rb @@ -1,6 +1,21 @@ class File < IO + # The path to the file attr_accessor :path + # + # call-seq: + # File.new(filename, mode="r") -> file + # File.new(filename [, mode [, perm]]) -> file + # File.new(fd [, mode]) -> file + # + # Opens the file named by filename according to the given mode and returns + # a new File object. If a file descriptor is given instead of a filename, + # the new File object will be associated with that descriptor. + # + # f = File.new("testfile", "r") + # f = File.new("newfile", "w+") + # f = File.new("tmpfile", "a") + # def initialize(fd_or_path, mode = "r", perm = 0666) if fd_or_path.kind_of? Integer super(fd_or_path, mode) @@ -11,69 +26,74 @@ class File < IO end end + # + # call-seq: + # file.atime -> time + # + # Returns the last access time for file, or epoch if the platform + # doesn't have access time. + # + # File.new("testfile").atime #=> Wed Apr 09 08:51:48 CDT 2003 + # def atime t = self._atime t && Time.at(t) end + # + # call-seq: + # file.ctime -> time + # + # Returns the change time for file (that is, the time directory + # information about the file was changed, not the file itself). + # + # File.new("testfile").ctime #=> Wed Apr 09 08:53:13 CDT 2003 + # def ctime t = self._ctime t && Time.at(t) end + # + # call-seq: + # file.mtime -> time + # + # Returns the modification time for file. + # + # File.new("testfile").mtime #=> Wed Apr 09 08:53:14 CDT 2003 + # def mtime t = self._mtime t && Time.at(t) end + # + # call-seq: + # file.inspect -> string + # + # Return a string describing this File object. + # + # File.new("testfile").inspect #=> "#" + # def inspect "<#{self.class}:#{@path}>" end - def self.join(*names) - return "" if names.empty? - - names.map! do |name| - case name - when String - name - when Array - if names == name - raise ArgumentError, "recursive array" - end - join(*name) - else - raise TypeError, "no implicit conversion of #{name.class} into String" - end - end - - return names[0] if names.size == 1 - - if names[0][-1] == File::SEPARATOR - s = names[0][0..-2] - else - s = names[0].dup - end - - (1..names.size-2).each { |i| - t = names[i] - if t[0] == File::SEPARATOR and t[-1] == File::SEPARATOR - t = t[1..-2] - elsif t[0] == File::SEPARATOR - t = t[1..-1] - elsif t[-1] == File::SEPARATOR - t = t[0..-2] - end - s += File::SEPARATOR + t if t != "" - } - if names[-1][0] == File::SEPARATOR - s += File::SEPARATOR + names[-1][1..-1] - else - s += File::SEPARATOR + names[-1] - end - s - end + # + # call-seq: + # File.foreach(name) {|line| block } -> nil + # File.foreach(name) -> an_enumerator + # + # Executes the block for every line in the named I/O port, where lines + # are separated by sep. + # + # File.foreach("testfile") {|x| print "GOT ", x } + # GOT This is line one + # GOT This is line two + # GOT This is line three + # GOT And so on... + # def self.foreach(file) if block_given? self.open(file) do |f| @@ -84,58 +104,6 @@ class File < IO end end - def self.directory?(file) - FileTest.directory?(file) - end - def self.exist?(file) - FileTest.exist?(file) - end - def self.exists?(file) - FileTest.exists?(file) - end - - def self.file?(file) - FileTest.file?(file) - end - - def self.pipe?(file) - FileTest.pipe?(file) - end - - def self.size(file) - FileTest.size(file) - end - - def self.size?(file) - FileTest.size?(file) - end - - def self.socket?(file) - FileTest.socket?(file) - end - - def self.symlink?(file) - FileTest.symlink?(file) - end - - def self.zero?(file) - FileTest.zero?(file) - end - - def self.extname(filename) - fname = self.basename(filename) - epos = fname.rindex('.') - return '' if epos == 0 || epos.nil? - return fname[epos..-1] - end - - def self.path(filename) - if filename.kind_of?(String) - filename - else - raise TypeError, "no implicit conversion of #{filename.class} into String" - end - end end diff --git a/mrbgems/mruby-io/mrblib/file_constants.rb b/mrbgems/mruby-io/mrblib/file_constants.rb index bd77d53fe..40ca9fb0a 100644 --- a/mrbgems/mruby-io/mrblib/file_constants.rb +++ b/mrbgems/mruby-io/mrblib/file_constants.rb @@ -1,13 +1,24 @@ class File + # File name matching constants used with File.fnmatch and Dir.glob module Constants + # Makes File.fnmatch case sensitive on systems where it's case insensitive by default FNM_SYSCASE = 0 + + # Disables the special meaning of the backslash escape character FNM_NOESCAPE = 1 + + # Pathname wildcard doesn't match '/' (directory separator) FNM_PATHNAME = 2 + + # Allows patterns to match hidden files (those starting with '.') FNM_DOTMATCH = 4 + + # Makes the pattern case insensitive (overrides FNM_SYSCASE) FNM_CASEFOLD = 8 end end class File + # Include Constants module to make FNM_* constants available directly on File class include File::Constants end diff --git a/mrbgems/mruby-io/mrblib/io.rb b/mrbgems/mruby-io/mrblib/io.rb index b256acbe9..8cd442ac0 100644 --- a/mrbgems/mruby-io/mrblib/io.rb +++ b/mrbgems/mruby-io/mrblib/io.rb @@ -7,6 +7,21 @@ class IOError < StandardError; end class EOFError < IOError; end class IO + # + # call-seq: + # IO.open(fd, mode="r" [, opt]) -> io + # IO.open(fd, mode="r" [, opt]) {|io| block } -> obj + # + # With no associated block, IO.open is a synonym for IO.new. If the optional + # code block is given, it will be passed io as an argument, and the IO object + # will automatically be closed when the block terminates. In this instance, + # IO.open returns the value of the block. + # + # fd = IO.sysopen("/dev/tty", "w") + # a = IO.open(fd,"w") + # $stderr.puts "Hello" + # a.close + # def self.open(*args, &block) io = self.new(*args) @@ -22,6 +37,21 @@ class IO end end + # + # call-seq: + # IO.popen(cmd, mode="r" [, opt]) -> io + # IO.popen(cmd, mode="r" [, opt]) {|io| block } -> obj + # + # Runs the specified command as a subprocess; the subprocess's standard input + # and output will be connected to the returned IO object. + # + # p IO.popen("date").read #=> "Wed Apr 9 08:56:30 CDT 2003\n" + # IO.popen("dc", "r+") {|f| + # f.puts "5 2 *" + # f.close_write + # puts f.read + # } + # def self.popen(command, mode = 'r', **opts, &block) if !self.respond_to?(:_popen) raise NotImplementedError, "popen is not supported on this platform" @@ -40,6 +70,27 @@ class IO end end + # + # call-seq: + # IO.pipe -> [read_io, write_io] + # IO.pipe {|read_io, write_io| ... } -> obj + # + # Creates a pair of pipe endpoints (connected to each other) and returns + # them as a two-element array of IO objects: [read_io, write_io]. + # + # rd, wr = IO.pipe + # if fork + # wr.close + # puts rd.read + # rd.close + # Process.wait + # else + # rd.close + # wr.write "Hello, parent!" + # wr.close + # exit + # end + # def self.pipe(&block) if !self.respond_to?(:_pipe) raise NotImplementedError, "pipe is not supported on this platform" @@ -57,6 +108,19 @@ class IO end end + # + # call-seq: + # IO.read(name, [length [, offset]] ) -> string + # IO.read(name, [length [, offset]], mode: mode) -> string + # + # Opens the file, optionally seeks to the given offset, then returns length + # bytes (defaulting to the rest of the file). read ensures the file is + # closed before returning. + # + # IO.read("testfile") #=> "This is line one\nThis is line two\n" + # IO.read("testfile", 20) #=> "This is line one\nTh" + # IO.read("testfile", 20, 10) #=> "ne one\nThis is line " + # def self.read(path, length=nil, offset=0, mode: "r") str = "" fd = -1 @@ -76,39 +140,74 @@ class IO str end + + + # + # call-seq: + # ios.hash -> integer + # + # Compute a hash based on the IO object. Two IO objects with the same + # content will have the same hash code (and will compare using eql?). + # We must define IO#hash here because IO includes Enumerable and + # Enumerable#hash will call IO#read() otherwise. + # def hash # We must define IO#hash here because IO includes Enumerable and # Enumerable#hash will call IO#read() otherwise self.__id__ end - def <<(str) - write(str) - self - end + # Alias for eof? alias_method :eof, :eof? + # Alias for pos alias_method :tell, :pos + # + # call-seq: + # ios.pos = integer -> integer + # + # Seeks to the given position (in bytes) in ios. It is not guaranteed that + # seeking to the right position when ios is textmode. + # + # f = File.new("testfile") + # f.pos = 17 + # f.gets #=> "This is line two\n" + # def pos=(i) seek(i, SEEK_SET) end + # + # call-seq: + # ios.rewind -> 0 + # + # Positions ios to the beginning of input, resetting lineno to zero. + # + # f = File.new("testfile") + # f.readline #=> "This is line one\n" + # f.rewind #=> 0 + # f.lineno #=> 0 + # f.readline #=> "This is line one\n" + # def rewind seek(0, SEEK_SET) end - def ungetbyte(c) - if c.is_a? String - c = c.getbyte(0) - else - c &= 0xff - end - s = " " - s.setbyte(0,c) - ungetc s - end + # + # call-seq: + # ios.each(sep=$/) {|line| block } -> ios + # ios.each(limit) {|line| block } -> ios + # ios.each(sep,limit) {|line| block } -> ios + # ios.each(...) -> an_enumerator + # + # Executes the block for every line in ios, where lines are separated by sep. + # ios must be opened for reading. If no block is given, an enumerator is returned instead. + # + # f = File.new("testfile") + # f.each {|line| puts "#{f.lineno}: #{line}" } + # # 15.2.20.5.3 def each(&block) return to_enum unless block @@ -119,6 +218,19 @@ class IO self end + # + # call-seq: + # ios.each_byte {|byte| block } -> ios + # ios.each_byte -> an_enumerator + # + # Calls the given block once for each byte (0..255) in ios, passing the byte + # as an argument. The stream must be opened for reading or an IOError will be raised. + # + # f = File.new("testfile") + # checksum = 0 + # f.each_byte {|x| checksum ^= x } #=> # + # checksum #=> 12 + # # 15.2.20.5.4 def each_byte(&block) return to_enum(:each_byte) unless block @@ -129,9 +241,20 @@ class IO self end - # 15.2.20.5.5 + # Alias for each - 15.2.20.5.5 alias each_line each + # + # call-seq: + # ios.each_char {|c| block } -> ios + # ios.each_char -> an_enumerator + # + # Calls the given block once for each character in ios, passing the character + # as an argument. The stream must be opened for reading or an IOError will be raised. + # + # f = File.new("testfile") + # ios.each_char {|c| print c, ' ' } #=> # + # def each_char(&block) return to_enum(:each_char) unless block @@ -141,49 +264,40 @@ class IO self end - def puts(*args) - i = 0 - len = args.size - if len == 0 - write "\n" - return - end - while i < len - s = args[i] - if s.kind_of?(Array) - puts(*s) if s.size > 0 - else - s = s.to_s - write s - write "\n" if (s[-1] != "\n") - end - i += 1 - end - nil - end - def print(*args) - i = 0 - len = args.size - while i < len - write args[i].to_s - i += 1 - end - end + # + # call-seq: + # ios.printf(format_string [, obj, ...]) -> nil + # + # Formats and writes to ios, converting parameters under control of the format string. + # See sprintf for details of the format string. + # + # $stdout.printf "Number: %5.2f,\nString: %s\n", 1.23, "hello" + # Number: 1.23, + # String: hello + # def printf(*args) write sprintf(*args) nil end + # Alias for fileno - returns the integer file descriptor for ios alias_method :to_i, :fileno + # Alias for isatty - returns true if ios is associated with a terminal device alias_method :tty?, :isatty end +# Standard input stream - connected to file descriptor 0 STDIN = IO.open(0, "r") +# Standard output stream - connected to file descriptor 1 STDOUT = IO.open(1, "w") +# Standard error stream - connected to file descriptor 2 STDERR = IO.open(2, "w") +# Global variable for standard input $stdin = STDIN +# Global variable for standard output $stdout = STDOUT +# Global variable for standard error $stderr = STDERR diff --git a/mrbgems/mruby-io/mrblib/kernel.rb b/mrbgems/mruby-io/mrblib/kernel.rb index 9a09ada75..283d09631 100644 --- a/mrbgems/mruby-io/mrblib/kernel.rb +++ b/mrbgems/mruby-io/mrblib/kernel.rb @@ -1,8 +1,36 @@ module Kernel + # + # call-seq: + # `cmd` -> string + # + # Returns the standard output of running cmd in a subshell. + # The built-in syntax %x{...} uses this method. + # + # `date` #=> "Wed Apr 9 08:56:30 CDT 2003\n" + # `ls testdir`.split[1] #=> "main.rb" + # `echo oops && exit 99` #=> "oops\n" + # private def `(cmd) #` IO.popen(cmd) { |io| io.read } end + # + # call-seq: + # open(name [, mode [, perm]] [, opt]) -> io or nil + # open(name [, mode [, perm]] [, opt]) {|io| block } -> obj + # + # Creates an IO object connected to the given stream, file, or subprocess. + # If path starts with a pipe character ("|"), a subprocess is created, + # connected to the caller by a pair of pipes. The returned IO object may + # be used to write to the standard input and read from the standard output + # of this subprocess. + # + # open("testfile") #=> # + # open("| date") #=> # + # open("testfile") do |f| + # print f.gets + # end + # private def open(file, *rest, &block) raise ArgumentError unless file.is_a?(String) @@ -13,6 +41,19 @@ module Kernel end end + # + # call-seq: + # p(obj) -> obj + # p(obj1, obj2, ...) -> [obj1, obj2, ...] + # p() -> nil + # + # For each object, directly writes obj.inspect followed by a newline + # to the program's standard output. + # + # S = Struct.new(:name, :state) + # s = S['dave', 'TX'] + # p s #=> # + # private def p(*a) for e in a $stdout.write e.inspect @@ -24,26 +65,96 @@ module Kernel a end + # + # call-seq: + # print(obj, ...) -> nil + # + # Prints each object in turn to $stdout. If the output field separator + # ($,) is not nil, its contents will appear between each field. + # If the output record separator ($\) is not nil, it will be appended + # to the output. + # + # print "cat", [1,2,3], 99, "\n" + # $, = ", " + # $\ = "\n" + # print "cat", [1,2,3], 99 + # private def print(...) $stdout.print(...) end + # + # call-seq: + # puts(obj, ...) -> nil + # + # Equivalent to $stdout.puts(obj, ...). + # + # puts "this", "is", "a", "test" + # private def puts(...) $stdout.puts(...) end + # + # call-seq: + # printf(io, string [, obj ... ]) -> nil + # printf(string [, obj ... ]) -> nil + # + # Equivalent to io.write(sprintf(string, obj, ...)) or + # $stdout.write(sprintf(string, obj, ...)). + # + # printf "Number: %5.2f,\nString: %s\n", 1.23, "hello" + # private def printf(...) $stdout.printf(...) end + # + # call-seq: + # gets(sep=$/) -> string or nil + # gets(limit) -> string or nil + # gets(sep,limit) -> string or nil + # + # Returns (and assigns to $_) the next line from the list of files in ARGV + # (or $*), or from standard input if no files are present on the command line. + # Returns nil at end of file. + # + # print "Enter your name: " + # name = gets + # print "Hello #{name}" + # private def gets(...) $stdin.gets(...) end + # + # call-seq: + # readline(sep=$/) -> string + # readline(limit) -> string + # readline(sep,limit) -> string + # + # Equivalent to gets, except readline raises EOFError at end of file. + # + # print "Enter your name: " + # name = readline + # print "Hello #{name}" + # private def readline(...) $stdin.readline(...) end + # + # call-seq: + # readlines(sep=$/) -> array + # readlines(limit) -> array + # readlines(sep,limit) -> array + # + # Returns an array containing the lines returned by calling gets(sep) + # until the end of file. + # + # lines = readlines + # lines[0] #=> "This is line one\n" + # private def readlines(...) $stdin.readlines(...) end diff --git a/mrbgems/mruby-io/src/file.c b/mrbgems/mruby-io/src/file.c index 6210915ec..f8d56ade6 100644 --- a/mrbgems/mruby-io/src/file.c +++ b/mrbgems/mruby-io/src/file.c @@ -10,6 +10,7 @@ #include #include #include +#include "io_hal.h" #include #include @@ -19,6 +20,18 @@ #include #include #include + +/* Undefine system macros that conflict with mrb_io_stat field names */ +#ifdef st_atime +#undef st_atime +#endif +#ifdef st_mtime +#undef st_mtime +#endif +#ifdef st_ctime +#undef st_ctime +#endif + #if defined(_WIN32) #include #include @@ -62,17 +75,18 @@ #define DIRSEP_P(ch) ((ch) == '/') #endif +/* Use HAL lock constants */ #ifndef LOCK_SH -#define LOCK_SH 1 +#define LOCK_SH MRB_IO_LOCK_SH #endif #ifndef LOCK_EX -#define LOCK_EX 2 +#define LOCK_EX MRB_IO_LOCK_EX #endif #ifndef LOCK_NB -#define LOCK_NB 4 +#define LOCK_NB MRB_IO_LOCK_NB #endif #ifndef LOCK_UN -#define LOCK_UN 8 +#define LOCK_UN MRB_IO_LOCK_UN #endif #if !defined(_WIN32) || defined(MRB_MINGW32_LEGACY) @@ -103,26 +117,41 @@ flock(int fd, int operation) } #endif +/* + * call-seq: + * File.umask([mask]) -> integer + * + * Returns the current umask value for this process. If the optional + * `mask` argument is given, set the umask to that value and return + * the previous value. + * + * File.umask(0006) #=> 18 + * File.umask #=> 6 + */ static mrb_value mrb_file_s_umask(mrb_state *mrb, mrb_value klass) { -#if defined(_WIN32) - /* nothing to do on windows */ - return mrb_fixnum_value(0); + mrb_int mask; + uint32_t omask; -#else - mrb_int mask, omask; if (mrb_get_args(mrb, "|i", &mask) == 0) { - omask = umask(0); - umask(omask); + omask = mrb_hal_io_umask(mrb, -1); } else { - omask = umask(mask); + omask = mrb_hal_io_umask(mrb, (int32_t)mask); } return mrb_fixnum_value(omask); -#endif } +/* + * call-seq: + * File.delete(file_name, ...) -> integer + * File.unlink(file_name, ...) -> integer + * + * Deletes the named file(s). Returns the number of files deleted. + * + * File.delete("a.txt", "b.txt") #=> 2 + */ static mrb_value mrb_file_s_unlink(mrb_state *mrb, mrb_value obj) { @@ -135,7 +164,7 @@ mrb_file_s_unlink(mrb_state *mrb, mrb_value obj) mrb_ensure_string_type(mrb, pathv); const char *utf8_path = RSTRING_CSTR(mrb, pathv); char *path = mrb_locale_from_utf8(utf8_path, -1); - if (UNLINK(path) < 0) { + if (mrb_hal_io_unlink(mrb, path) < 0) { mrb_locale_free(path); mrb_sys_fail(mrb, utf8_path); } @@ -144,6 +173,14 @@ mrb_file_s_unlink(mrb_state *mrb, mrb_value obj) return mrb_fixnum_value(argc); } +/* + * call-seq: + * File.rename(old_name, new_name) -> 0 + * + * Renames the given file to the new name. + * + * File.rename("a.txt", "b.txt") #=> 0 + */ static mrb_value mrb_file_s_rename(mrb_state *mrb, mrb_value obj) { @@ -152,9 +189,12 @@ mrb_file_s_rename(mrb_state *mrb, mrb_value obj) mrb_get_args(mrb, "SS", &from, &to); char *src = mrb_locale_from_utf8(RSTRING_CSTR(mrb, from), -1); char *dst = mrb_locale_from_utf8(RSTRING_CSTR(mrb, to), -1); - if (rename(src, dst) < 0) { + if (mrb_hal_io_rename(mrb, src, dst) < 0) { #if defined(_WIN32) - if (CHMOD(dst, 0666) == 0 && UNLINK(dst) == 0 && rename(src, dst) == 0) { + /* Windows retry: try chmod+unlink+rename if initial rename fails */ + if (mrb_hal_io_chmod(mrb, dst, 0666) == 0 && + mrb_hal_io_unlink(mrb, dst) == 0 && + mrb_hal_io_rename(mrb, src, dst) == 0) { mrb_locale_free(src); mrb_locale_free(dst); return mrb_fixnum_value(0); @@ -189,6 +229,14 @@ scan_dirname(const char *path, mrb_int level) return p > path ? p : path; } +/* + * call-seq: + * File.dirname(file_name) -> string + * + * Returns the directory part of a file name. + * + * File.dirname("/usr/bin/ruby") #=> "/usr/bin" + */ static mrb_value mrb_file_dirname(mrb_state *mrb, mrb_value klass) { @@ -238,6 +286,15 @@ mrb_file_dirname(mrb_state *mrb, mrb_value klass) return (p == path) ? mrb_str_new_lit(mrb, ".") : mrb_str_new(mrb, path, p - path); } +/* + * call-seq: + * File.basename(file_name, [suffix]) -> string + * + * Returns the last component of the file name. + * + * File.basename("/usr/bin/ruby") #=> "ruby" + * File.basename("/usr/bin/ruby.exe", ".exe") #=> "ruby" + */ static mrb_value mrb_file_basename(mrb_state *mrb, mrb_value klass) { @@ -322,6 +379,15 @@ mrb_file_basename(mrb_state *mrb, mrb_value klass) #endif } +/* + * call-seq: + * File.realpath(pathname, [dir_string]) -> string + * + * Returns the real (absolute) path of `pathname` in the actual + * filesystem. + * + * File.realpath("../../bin/ruby") #=> "/usr/bin/ruby" + */ static mrb_value mrb_file_realpath(mrb_state *mrb, mrb_value klass) { @@ -335,7 +401,7 @@ mrb_file_realpath(mrb_state *mrb, mrb_value klass) } char *cpath = mrb_locale_from_utf8(RSTRING_CSTR(mrb, pathname), -1); mrb_value result = mrb_str_new_capa(mrb, PATH_MAX); - if (realpath(cpath, RSTRING_PTR(result)) == NULL) { + if (mrb_hal_io_realpath(mrb, cpath, RSTRING_PTR(result)) == NULL) { mrb_locale_free(cpath); mrb_sys_fail(mrb, RSTRING_CSTR(mrb, pathname)); return result; /* not reached */ @@ -350,7 +416,7 @@ path_getwd(mrb_state *mrb) { char buf[MAXPATHLEN]; - if (GETCWD(buf, MAXPATHLEN) == NULL) { + if (mrb_hal_io_getcwd(mrb, buf, MAXPATHLEN) == NULL) { mrb_sys_fail(mrb, "getcwd(2)"); } char *utf8 = mrb_utf8_from_locale(buf, -1); @@ -454,7 +520,7 @@ path_gethome(mrb_state *mrb, const char **pathp) { mrb_assert(pathp && *pathp && **pathp == '~'); - char *home; + const char *home; mrb_value path; const char *username = ++*pathp; @@ -462,12 +528,7 @@ path_gethome(mrb_state *mrb, const char **pathp) ptrdiff_t len = *pathp - username; if (len == 0) { - home = getenv("HOME"); -#ifdef _WIN32 - if (home == NULL) { - home = getenv("USERPROFILE"); - } -#endif + home = mrb_hal_io_gethome(mrb, NULL); if (home == NULL) { mrb_raise(mrb, E_ARGUMENT_ERROR, "couldn't find HOME environment -- expanding '~'"); } @@ -477,22 +538,17 @@ path_gethome(mrb_state *mrb, const char **pathp) } else { const char *uname = RSTRING_CSTR(mrb, mrb_str_new(mrb, username, (mrb_int)len)); -#if defined(_WIN32) || defined(MRB_IO_NO_PWNAM) - mrb_raisef(mrb, E_ARGUMENT_ERROR, "user %s doesn't exist", uname); -#else - const struct passwd *pwd = getpwnam(uname); - if (pwd == NULL) { + home = mrb_hal_io_gethome(mrb, uname); + if (home == NULL) { mrb_raisef(mrb, E_ARGUMENT_ERROR, "user %s doesn't exist", uname); } - home = pwd->pw_dir; if (!path_absolute_p(home)) { mrb_raisef(mrb, E_ARGUMENT_ERROR, "non-absolute home of ~%s", uname); } -#endif } - home = mrb_utf8_from_locale(home, -1); - path = mrb_str_new_cstr(mrb, home); - mrb_utf8_free(home); + char *home_utf8 = mrb_utf8_from_locale(home, -1); + path = mrb_str_new_cstr(mrb, home_utf8); + mrb_utf8_free(home_utf8); SKIP_DIRSEP(*pathp); return RSTRING_CSTR(mrb, path); @@ -585,6 +641,14 @@ mrb_file_expand_path(mrb_state *mrb, mrb_value self) return path_expand(mrb, path, default_dir, TRUE); } +/* + * call-seq: + * File.absolute_path(file_name, [dir_string]) -> string + * + * Converts a pathname to an absolute pathname. + * + * File.absolute_path("~oracle/bin/oracle") #=> "/home/oracle/bin/oracle" + */ static mrb_value mrb_file_absolute_path(mrb_state *mrb, mrb_value self) { @@ -594,6 +658,15 @@ mrb_file_absolute_path(mrb_state *mrb, mrb_value self) return path_expand(mrb, path, default_dir, FALSE); } +/* + * call-seq: + * File.absolute_path?(file_name) -> true or false + * + * Returns `true` if the file name is an absolute path, `false` otherwise. + * + * File.absolute_path?("/usr/bin/ruby") #=> true + * File.absolute_path?("bin/ruby") #=> false + */ static mrb_value mrb_file_absolute_path_p(mrb_state *mrb, mrb_value klass) { @@ -618,10 +691,10 @@ static mrb_value mrb_file_atime(mrb_state *mrb, mrb_value self) { int fd = mrb_io_fileno(mrb, self); - mrb_stat st; + mrb_io_stat st; mrb->c->ci->mid = 0; - if (mrb_fstat(fd, &st) == -1) + 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); @@ -633,10 +706,10 @@ static mrb_value mrb_file_ctime(mrb_state *mrb, mrb_value self) { int fd = mrb_io_fileno(mrb, self); - mrb_stat st; + mrb_io_stat st; mrb->c->ci->mid = 0; - if (mrb_fstat(fd, &st) == -1) + 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); @@ -648,10 +721,10 @@ static mrb_value mrb_file_mtime(mrb_state *mrb, mrb_value self) { int fd = mrb_io_fileno(mrb, self); - mrb_stat st; + mrb_io_stat st; mrb->c->ci->mid = 0; - if (mrb_fstat(fd, &st) == -1) + 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); @@ -659,6 +732,17 @@ mrb_file_mtime(mrb_state *mrb, mrb_value self) return mrb_int_value(mrb, (mrb_int)st.st_mtime); } +/* + * call-seq: + * file.flock(locking_constant) -> 0 or false + * + * Locks or unlocks a file according to `locking_constant`. + * See `File::LOCK_*` for locking constants. + * + * f = File.new("testfile") + * f.flock(File::LOCK_EX) #=> 0 + * f.flock(File::LOCK_UN) #=> 0 + */ static mrb_value mrb_file_flock(mrb_state *mrb, mrb_value self) { @@ -670,7 +754,7 @@ mrb_file_flock(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "i", &operation); int fd = mrb_io_fileno(mrb, self); - while (flock(fd, (int)operation) == -1) { + while (mrb_hal_io_flock(mrb, fd, (int)operation) == -1) { switch (errno) { case EINTR: /* retry */ @@ -679,7 +763,7 @@ mrb_file_flock(mrb_state *mrb, mrb_value self) #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: /* FreeBSD OpenBSD Linux */ #endif - if (operation & LOCK_NB) { + if (operation & MRB_IO_LOCK_NB) { return mrb_false_value(); } /* FALLTHRU - should not happen */ @@ -692,12 +776,20 @@ mrb_file_flock(mrb_state *mrb, mrb_value self) return mrb_fixnum_value(0); } +/* + * call-seq: + * file.size -> integer + * + * Returns the size of `file` in bytes. + * + * File.new("testfile").size #=> 66 + */ static mrb_value mrb_file_size(mrb_state *mrb, mrb_value self) { - mrb_stat st; + mrb_io_stat st; int fd = mrb_io_fileno(mrb, self); - if (mrb_fstat(fd, &st) == -1) { + if (mrb_hal_io_fstat(mrb, fd, &st) == -1) { mrb_sys_fail(mrb, "fstat"); } @@ -713,68 +805,70 @@ mrb_file_size(mrb_state *mrb, mrb_value self) } static int -mrb_ftruncate(int fd, mrb_int length) +mrb_ftruncate(mrb_state *mrb, int fd, mrb_int length) { -#ifndef _WIN32 - return ftruncate(fd, (off_t)length); -#else - __int64 cur; - HANDLE file = (HANDLE)_get_osfhandle(fd); - if (file == INVALID_HANDLE_VALUE) { - return -1; - } - - cur = _lseeki64(fd, 0, SEEK_CUR); - if (cur == -1) return -1; - - if (_lseeki64(fd, (__int64)length, SEEK_SET) == -1) return -1; - - if (!SetEndOfFile(file)) { - errno = EINVAL; /* TODO: GetLastError to errno */ - return -1; - } - - if (_lseeki64(fd, cur, SEEK_SET) == -1) return -1; - - return 0; -#endif /* _WIN32 */ + return mrb_hal_io_ftruncate(mrb, fd, (int64_t)length); } +/* + * call-seq: + * file.truncate(integer) -> 0 + * + * Truncates a file to a maximum of `integer` bytes. + * + * f = File.new("out", "w") + * f.write("1234567890") #=> 10 + * f.truncate(5) #=> 0 + * f.size #=> 5 + */ static mrb_value mrb_file_truncate(mrb_state *mrb, mrb_value self) { mrb_value lenv = mrb_get_arg1(mrb); int fd = mrb_io_fileno(mrb, self); mrb_int length = mrb_as_int(mrb, lenv); - if (mrb_ftruncate(fd, length) != 0) { + if (mrb_ftruncate(mrb, fd, length) != 0) { mrb_sys_fail(mrb, "ftruncate"); } return mrb_fixnum_value(0); } +/* + * call-seq: + * File.symlink(old_name, new_name) -> 0 + * + * Creates a symbolic link `new_name` for the file `old_name`. + * + * File.symlink("testfile", "link-to-test") #=> 0 + */ static mrb_value mrb_file_s_symlink(mrb_state *mrb, mrb_value klass) { -#if defined(_WIN32) - mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on this platform"); -#else mrb_value from, to; mrb_get_args(mrb, "SS", &from, &to); - const char *src = mrb_locale_from_utf8(RSTRING_CSTR(mrb, from), -1); - const char *dst = mrb_locale_from_utf8(RSTRING_CSTR(mrb, to), -1); - if (symlink(src, dst) == -1) { + char *src = mrb_locale_from_utf8(RSTRING_CSTR(mrb, from), -1); + char *dst = mrb_locale_from_utf8(RSTRING_CSTR(mrb, to), -1); + if (mrb_hal_io_symlink(mrb, src, dst) == -1) { mrb_locale_free(src); mrb_locale_free(dst); mrb_sys_fail(mrb, RSTRING_CSTR(mrb, mrb_format(mrb, "(%v, %v)", from, to))); } mrb_locale_free(src); mrb_locale_free(dst); -#endif return mrb_fixnum_value(0); } +/* + * call-seq: + * File.chmod(mode_int, file_name, ...) -> integer + * + * Changes permission bits on the named file(s) to the bit pattern + * represented by `mode_int`. + * + * File.chmod(0644, "testfile", "out") #=> 2 + */ static mrb_value mrb_file_s_chmod(mrb_state *mrb, mrb_value klass) { @@ -788,7 +882,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 (CHMOD(path, mode) == -1) { + if (mrb_hal_io_chmod(mrb, path, (uint32_t)mode) == -1) { mrb_locale_free(path); mrb_sys_fail(mrb, utf8_path); } @@ -799,13 +893,18 @@ mrb_file_s_chmod(mrb_state *mrb, mrb_value klass) return mrb_fixnum_value(argc); } +/* + * call-seq: + * File.readlink(link_name) -> string + * + * Returns the name of the file referenced by the given link. + * + * File.symlink("testfile", "link-to-test") #=> 0 + * File.readlink("link-to-test") #=> "testfile" + */ static mrb_value mrb_file_s_readlink(mrb_state *mrb, mrb_value klass) { -#if defined(_WIN32) - mrb_raise(mrb, E_NOTIMP_ERROR, "readlink is not supported on this platform"); - return mrb_nil_value(); // unreachable -#else const char *path; size_t bufsize = 100; @@ -814,8 +913,8 @@ mrb_file_s_readlink(mrb_state *mrb, mrb_value klass) char *tmp = mrb_locale_from_utf8(path, -1); char *buf = (char*)mrb_malloc(mrb, bufsize); - ssize_t rc; - while ((rc = readlink(tmp, buf, bufsize)) == (ssize_t)bufsize) { + int64_t rc; + while ((rc = mrb_hal_io_readlink(mrb, tmp, buf, bufsize)) == (int64_t)bufsize) { bufsize += 100; buf = (char*)mrb_realloc(mrb, buf, bufsize); } @@ -831,7 +930,213 @@ mrb_file_s_readlink(mrb_state *mrb, mrb_value klass) mrb_free(mrb, buf); return ret; -#endif +} + +/* + * call-seq: + * File.extname(path) -> string + * + * Returns the extension (the portion of file name in path starting from the + * last period). If path is a dotfile, or starts with a period, then the starting + * dot is not dealt with the start of the extension. + * + * File.extname("test.rb") #=> ".rb" + * File.extname("a/b/d/test.rb") #=> ".rb" + * File.extname("test") #=> "" + * File.extname(".profile") #=> "" + */ +static mrb_value +mrb_file_extname(mrb_state *mrb, mrb_value klass) +{ + char *path; + mrb_get_args(mrb, "z", &path); + + size_t len = strlen(path); + if (len == 0) { + return mrb_str_new_lit(mrb, ""); + } + + // Remove trailing slashes to find the actual filename + while (len > 1 && path[len - 1] == '/') { + len--; + } + + // Find the last path separator to get basename + ssize_t base_start = len - 1; + while (base_start >= 0 && path[base_start] != '/') { + base_start--; + } + base_start++; // move to first character after '/' + + // If the result is only slashes, no extension + if ((size_t)base_start == len) { + return mrb_str_new_lit(mrb, ""); + } + + // Look for the last '.' in the basename + ssize_t dot_pos = -1; + for (size_t i = base_start; i < len; i++) { + if (path[i] == '.') { + dot_pos = i; + } + } + + // No dot found, or dot is the first character (dotfile) + if (dot_pos == -1 || dot_pos == (ssize_t)base_start) { + return mrb_str_new_lit(mrb, ""); + } + + // Return extension from dot to end + return mrb_str_new(mrb, path + dot_pos, len - dot_pos); +} + +/* + * call-seq: + * File.path(path) -> string + * + * Returns the string representation of the path + * + * File.path("/dev/null") #=> "/dev/null" + * File.path(Pathname.new("/tmp")) #=> "/tmp" + */ +static mrb_value +mrb_file_path(mrb_state *mrb, mrb_value klass) +{ + mrb_value filename; + mrb_get_args(mrb, "S", &filename); + return filename; +} + +// Forward declaration for recursive join processing +static mrb_value mrb_file_join_process_args(mrb_state *mrb, const mrb_value *argv, mrb_int argc); + +static mrb_value +mrb_file_join_process_args(mrb_state *mrb, const mrb_value *argv, mrb_int argc) +{ + mrb_value result = mrb_ary_new_capa(mrb, argc); + + for (mrb_int i = 0; i < argc; i++) { + mrb_value arg = argv[i]; + + if (mrb_array_p(arg)) { + // Check for recursive arrays using mruby's built-in detection + if (MRB_RECURSIVE_UNARY_P(mrb, MRB_SYM(join), arg)) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "recursive array"); + } + + // Recursively process the array + mrb_value nested = mrb_file_join_process_args(mrb, RARRAY_PTR(arg), RARRAY_LEN(arg)); + + // Append nested results to our result + mrb_int nested_len = RARRAY_LEN(nested); + for (mrb_int k = 0; k < nested_len; k++) { + mrb_ary_push(mrb, result, RARRAY_PTR(nested)[k]); + } + } + else { + // Convert to string (raises TypeError if not convertible) + mrb_ensure_string_type(mrb, arg); + mrb_ary_push(mrb, result, arg); + } + } + + return result; +} + +/* + * call-seq: + * File.join(string, ...) -> string + * + * Returns a new string formed by joining the strings using the operating + * system's path separator (File::SEPARATOR). + * + * File.join("usr", "mail", "gumby") #=> "usr/mail/gumby" + * File.join("usr", "mail", "gumby") #=> "usr\\mail\\gumby" (on Windows) + */ +static mrb_value +mrb_file_join(mrb_state *mrb, mrb_value klass) +{ + const mrb_value *argv; + mrb_int argc; + + mrb_get_args(mrb, "*", &argv, &argc); + + // Handle empty case + if (argc == 0) { + return mrb_str_new_lit(mrb, ""); + } + + // Process arguments and flatten arrays + mrb_value names = mrb_file_join_process_args(mrb, argv, argc); + + mrb_int names_len = RARRAY_LEN(names); + if (names_len == 0) { + return mrb_str_new_lit(mrb, ""); + } + + // Handle single element case + if (names_len == 1) { + return RARRAY_PTR(names)[0]; + } + + // Start building the result + mrb_value first = RARRAY_PTR(names)[0]; + mrb_value result = mrb_str_dup(mrb, first); + + // Remove trailing separator from first component + const char *sep = FILE_SEPARATOR; + mrb_int sep_len = strlen(sep); + if (RSTRING_LEN(result) > 0 && + RSTRING_LEN(result) >= sep_len && + memcmp(RSTRING_PTR(result) + RSTRING_LEN(result) - sep_len, sep, sep_len) == 0) { + mrb_str_resize(mrb, result, RSTRING_LEN(result) - sep_len); + } + + // Process middle components + for (mrb_int i = 1; i < names_len - 1; i++) { + mrb_value component = RARRAY_PTR(names)[i]; + const char *comp_str = RSTRING_PTR(component); + mrb_int comp_len = RSTRING_LEN(component); + + // Skip empty components + if (comp_len == 0) continue; + + // Remove leading separator + if (comp_len >= sep_len && memcmp(comp_str, sep, sep_len) == 0) { + comp_str += sep_len; + comp_len -= sep_len; + } + + // Remove trailing separator + if (comp_len >= sep_len && memcmp(comp_str + comp_len - sep_len, sep, sep_len) == 0) { + comp_len -= sep_len; + } + + // Add separator and component if not empty + if (comp_len > 0) { + mrb_str_cat_cstr(mrb, result, sep); + mrb_str_cat(mrb, result, comp_str, comp_len); + } + } + + // Process last component + if (names_len > 1) { + mrb_value last = RARRAY_PTR(names)[names_len - 1]; + const char *last_str = RSTRING_PTR(last); + mrb_int last_len = RSTRING_LEN(last); + + // Remove leading separator from last component + if (last_len >= sep_len && memcmp(last_str, sep, sep_len) == 0) { + last_str += sep_len; + last_len -= sep_len; + } + + // Add separator and last component + mrb_str_cat_cstr(mrb, result, sep); + mrb_str_cat(mrb, result, last_str, last_len); + } + + return result; } void @@ -850,6 +1155,9 @@ mrb_init_file(mrb_state *mrb) mrb_define_class_method_id(mrb, file, MRB_SYM(dirname), mrb_file_dirname, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, file, MRB_SYM(basename), mrb_file_basename, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM(extname), mrb_file_extname, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM(join), mrb_file_join, MRB_ARGS_ANY()); + mrb_define_class_method_id(mrb, file, MRB_SYM(path), mrb_file_path, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, file, MRB_SYM(realpath), mrb_file_realpath, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); mrb_define_class_method_id(mrb, file, MRB_SYM(absolute_path), mrb_file_absolute_path, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); mrb_define_class_method_id(mrb, file, MRB_SYM_Q(absolute_path), mrb_file_absolute_path_p, MRB_ARGS_REQ(1)); diff --git a/mrbgems/mruby-io/src/file_test.c b/mrbgems/mruby-io/src/file_test.c index 72dcc254b..52c976905 100644 --- a/mrbgems/mruby-io/src/file_test.c +++ b/mrbgems/mruby-io/src/file_test.c @@ -9,42 +9,44 @@ #include #include #include +#include +#include "io_hal.h" #include #include -#if defined(_WIN32) - #define LSTAT stat - #include -#else - #define LSTAT lstat - #include - #include - #include -#ifndef __DJGPP__ - #include -#endif - #include - #include -#endif - -#include - #include #include #include 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, struct stat *st, int do_lstat) +mrb_stat0(mrb_state *mrb, mrb_value obj, mrb_io_stat *st, int do_lstat) { if (mrb_obj_is_kind_of(mrb, obj, mrb_class_get_id(mrb, MRB_SYM(IO)))) { struct mrb_io *fptr; fptr = (struct mrb_io*)mrb_data_get_ptr(mrb, obj, &mrb_io_type); if (fptr && fptr->fd >= 0) { - return fstat(fptr->fd, st); + return mrb_hal_io_fstat(mrb, fptr->fd, st); } mrb_raise(mrb, E_IO_ERROR, "closed stream"); @@ -54,10 +56,10 @@ mrb_stat0(mrb_state *mrb, mrb_value obj, struct stat *st, int do_lstat) char *path = mrb_locale_from_utf8(RSTRING_CSTR(mrb, obj), -1); int ret; if (do_lstat) { - ret = LSTAT(path, st); + ret = mrb_hal_io_lstat(mrb, path, st); } else { - ret = stat(path, st); + ret = mrb_hal_io_stat(mrb, path, st); } mrb_locale_free(path); return ret; @@ -65,30 +67,29 @@ mrb_stat0(mrb_state *mrb, mrb_value obj, struct stat *st, int do_lstat) } static int -mrb_stat(mrb_state *mrb, mrb_value obj, struct stat *st) +mrb_stat(mrb_state *mrb, mrb_value obj, mrb_io_stat *st) { return mrb_stat0(mrb, obj, st, 0); } -#ifdef S_ISLNK +#if defined(S_ISLNK) || defined(_S_ISLNK) || defined(S_IFLNK) || defined(_S_IFLNK) static int -mrb_lstat(mrb_state *mrb, mrb_value obj, struct stat *st) +mrb_lstat(mrb_state *mrb, mrb_value obj, mrb_io_stat *st) { return mrb_stat0(mrb, obj, st, 1); } #endif /* - * Document-method: directory? - * * call-seq: * File.directory?(file_name) -> true or false + * FileTest.directory?(file_name) -> true or false * - * Returns true if the named file is a directory, - * or a symlink that points at a directory, and false + * Returns `true` if the named file is a directory, or a symlink that points at a directory, and `false` * otherwise. * - * File.directory?(".") + * File.directory?(".") #=> true + * FileTest.directory?(".") #=> true */ static mrb_value @@ -98,7 +99,7 @@ mrb_filetest_s_directory_p(mrb_state *mrb, mrb_value klass) # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif - struct stat st; + mrb_io_stat st; mrb_value obj = mrb_get_arg1(mrb); if (mrb_stat(mrb, obj, &st) < 0) @@ -112,22 +113,27 @@ mrb_filetest_s_directory_p(mrb_state *mrb, mrb_value klass) /* * call-seq: * File.pipe?(file_name) -> true or false + * FileTest.pipe?(file_name) -> true or false * - * Returns true if the named file is a pipe. + * Returns `true` if the named file is a pipe. + * + * File.pipe?("/dev/stdin") #=> true + * FileTest.pipe?("/dev/stdin") #=> true */ static mrb_value mrb_filetest_s_pipe_p(mrb_state *mrb, mrb_value klass) { -#if defined(_WIN32) - mrb_raise(mrb, E_NOTIMP_ERROR, "pipe is not supported on this platform"); +#ifdef _WIN32 + /* Windows anonymous pipes are not Unix FIFOs */ + mrb_raise(mrb, E_NOTIMP_ERROR, "pipe? is not supported on Windows"); #else #ifdef S_IFIFO # ifndef S_ISFIFO # define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) # endif - struct stat st; + mrb_io_stat st; mrb_value obj = mrb_get_arg1(mrb); if (mrb_stat(mrb, obj, &st) < 0) @@ -143,15 +149,20 @@ mrb_filetest_s_pipe_p(mrb_state *mrb, mrb_value klass) /* * call-seq: * File.symlink?(file_name) -> true or false + * FileTest.symlink?(file_name) -> true or false * - * Returns true if the named file is a symbolic link. + * Returns `true` if the named file is a symbolic link. + * + * File.symlink?("link-to-test") #=> true + * FileTest.symlink?("link-to-test") #=> true */ static mrb_value mrb_filetest_s_symlink_p(mrb_state *mrb, mrb_value klass) { -#if defined(_WIN32) - mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on this platform"); +#ifdef _WIN32 + /* Symlinks not reliably supported on Windows */ + mrb_raise(mrb, E_NOTIMP_ERROR, "symlink? is not supported on Windows"); #else #ifndef S_ISLNK # ifdef _S_ISLNK @@ -168,31 +179,36 @@ mrb_filetest_s_symlink_p(mrb_state *mrb, mrb_value klass) #endif #ifdef S_ISLNK - struct stat st; + mrb_io_stat st; mrb_value obj = mrb_get_arg1(mrb); if (mrb_lstat(mrb, obj, &st) == -1) return mrb_false_value(); if (S_ISLNK(st.st_mode)) return mrb_true_value(); +#endif #endif return mrb_false_value(); -#endif } /* * call-seq: * File.socket?(file_name) -> true or false + * FileTest.socket?(file_name) -> true or false * - * Returns true if the named file is a socket. + * Returns `true` if the named file is a socket. + * + * File.socket?("/tmp/.X11-unix/X0") #=> true + * FileTest.socket?("/tmp/.X11-unix/X0") #=> true */ static mrb_value mrb_filetest_s_socket_p(mrb_state *mrb, mrb_value klass) { -#if defined(_WIN32) - mrb_raise(mrb, E_NOTIMP_ERROR, "socket is not supported on this platform"); +#ifdef _WIN32 + /* Unix domain sockets not supported on Windows */ + mrb_raise(mrb, E_NOTIMP_ERROR, "socket? is not supported on Windows"); #else #ifndef S_ISSOCK # ifdef _S_ISSOCK @@ -209,31 +225,38 @@ mrb_filetest_s_socket_p(mrb_state *mrb, mrb_value klass) #endif #ifdef S_ISSOCK - struct stat st; + mrb_io_stat st; mrb_value obj = mrb_get_arg1(mrb); if (mrb_stat(mrb, obj, &st) < 0) return mrb_false_value(); if (S_ISSOCK(st.st_mode)) return mrb_true_value(); +#endif #endif return mrb_false_value(); -#endif } /* * call-seq: * File.exist?(file_name) -> true or false * File.exists?(file_name) -> true or false + * FileTest.exist?(file_name) -> true or false + * FileTest.exists?(file_name) -> true or false * - * Return true if the named file exists. + * Returns `true` if the named file exists. + * + * File.exist?("config.h") #=> true + * File.exist?("no_such_file") #=> false + * FileTest.exist?("config.h") #=> true + * FileTest.exist?("no_such_file") #=> false */ static mrb_value mrb_filetest_s_exist_p(mrb_state *mrb, mrb_value klass) { - struct stat st; + mrb_io_stat st; mrb_value obj = mrb_get_arg1(mrb); if (mrb_stat(mrb, obj, &st) < 0) @@ -245,9 +268,12 @@ mrb_filetest_s_exist_p(mrb_state *mrb, mrb_value klass) /* * call-seq: * File.file?(file_name) -> true or false + * FileTest.file?(file_name) -> true or false * - * Returns true if the named file exists and is a - * regular file. + * Returns `true` if the named file exists and is a regular file. + * + * File.file?("testfile") #=> true + * FileTest.file?("testfile") #=> true */ static mrb_value @@ -257,7 +283,7 @@ mrb_filetest_s_file_p(mrb_state *mrb, mrb_value klass) # define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) #endif - struct stat st; + mrb_io_stat st; mrb_value obj = mrb_get_arg1(mrb); if (mrb_stat(mrb, obj, &st) < 0) @@ -271,15 +297,18 @@ mrb_filetest_s_file_p(mrb_state *mrb, mrb_value klass) /* * call-seq: * File.zero?(file_name) -> true or false + * FileTest.zero?(file_name) -> true or false * - * Returns true if the named file exists and has - * a zero size. + * Returns `true` if the named file exists and has a zero size. + * + * File.zero?("testfile") #=> false + * FileTest.zero?("testfile") #=> false */ static mrb_value mrb_filetest_s_zero_p(mrb_state *mrb, mrb_value klass) { - struct stat st; + mrb_io_stat st; mrb_value obj = mrb_get_arg1(mrb); if (mrb_stat(mrb, obj, &st) < 0) @@ -293,36 +322,44 @@ mrb_filetest_s_zero_p(mrb_state *mrb, mrb_value klass) /* * call-seq: * File.size(file_name) -> integer + * FileTest.size(file_name) -> integer * - * Returns the size of file_name. + * Returns the size of `file_name`. * - * _file_name_ can be an IO object. + * `file_name` can be an IO object. + * + * File.size("testfile") #=> 66 + * FileTest.size("testfile") #=> 66 */ static mrb_value mrb_filetest_s_size(mrb_state *mrb, mrb_value klass) { - struct stat st; + mrb_io_stat st; mrb_value obj = mrb_get_arg1(mrb); if (mrb_stat(mrb, obj, &st) < 0) mrb_sys_fail(mrb, "mrb_stat"); - return mrb_int_value(mrb, st.st_size); + return mrb_int64_value(mrb, st.st_size); } /* * call-seq: * File.size?(file_name) -> Integer or nil + * FileTest.size?(file_name) -> Integer or nil * - * Returns +nil+ if +file_name+ doesn't exist or has zero size, the size of the + * Returns `nil` if `file_name` doesn't exist or has zero size, the size of the * file otherwise. + * + * File.size?("testfile") #=> 66 + * FileTest.size?("testfile") #=> 66 */ static mrb_value mrb_filetest_s_size_p(mrb_state *mrb, mrb_value klass) { - struct stat st; + mrb_io_stat st; mrb_value obj = mrb_get_arg1(mrb); if (mrb_stat(mrb, obj, &st) < 0) @@ -330,7 +367,7 @@ mrb_filetest_s_size_p(mrb_state *mrb, mrb_value klass) if (st.st_size == 0) return mrb_nil_value(); - return mrb_int_value(mrb, st.st_size); + return mrb_int64_value(mrb, st.st_size); } void @@ -350,4 +387,17 @@ mrb_init_file_test(mrb_state *mrb) mrb_define_class_method_id(mrb, f, MRB_SYM_Q(socket), mrb_filetest_s_socket_p, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, f, MRB_SYM_Q(symlink), mrb_filetest_s_symlink_p, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, f, MRB_SYM_Q(zero), mrb_filetest_s_zero_p, MRB_ARGS_REQ(1)); + + // Also register the same methods on File class + struct RClass *file = mrb_class_get_id(mrb, MRB_SYM(File)); + mrb_define_class_method_id(mrb, file, MRB_SYM_Q(directory), mrb_filetest_s_directory_p, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM_Q(exist), mrb_filetest_s_exist_p, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM_Q(exists), mrb_filetest_s_exist_p, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM_Q(file), mrb_filetest_s_file_p, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM_Q(pipe), mrb_filetest_s_pipe_p, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM(size), mrb_filetest_s_size, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM_Q(size), mrb_filetest_s_size_p, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM_Q(socket), mrb_filetest_s_socket_p, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM_Q(symlink), mrb_filetest_s_symlink_p, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, file, MRB_SYM_Q(zero), mrb_filetest_s_zero_p, MRB_ARGS_REQ(1)); } diff --git a/mrbgems/mruby-io/src/io.c b/mrbgems/mruby-io/src/io.c index 856c0f0d1..16b16e0e5 100644 --- a/mrbgems/mruby-io/src/io.c +++ b/mrbgems/mruby-io/src/io.c @@ -13,6 +13,7 @@ #include #include #include +#include "io_hal.h" #include #include @@ -119,6 +120,12 @@ io_set_process_status(mrb_state *mrb, pid_t pid, int status) } #endif +static mrb_noreturn void +mode_error(mrb_state *mrb, const char *mode) +{ + mrb_raisef(mrb, E_ARGUMENT_ERROR, "illegal access mode %s", mode); +} + static int io_modestr_to_flags(mrb_state *mrb, const char *mode) { @@ -136,7 +143,7 @@ io_modestr_to_flags(mrb_state *mrb, const char *mode) flags = O_WRONLY | O_CREAT | O_APPEND; break; default: - goto modeerr; + mode_error(mrb, mode); } while (*m) { @@ -147,7 +154,7 @@ io_modestr_to_flags(mrb_state *mrb, const char *mode) #endif break; case 'x': - if (mode[0] != 'w') goto modeerr; + if (mode[0] != 'w') mode_error(mrb, mode); flags |= O_EXCL; break; case '+': @@ -156,15 +163,11 @@ io_modestr_to_flags(mrb_state *mrb, const char *mode) case ':': /* XXX: PASSTHROUGH*/ default: - goto modeerr; + mode_error(mrb, mode); } } return flags; - - modeerr: - mrb_raisef(mrb, E_ARGUMENT_ERROR, "illegal access mode %s", mode); - return 0; /* not reached */ } static int @@ -260,48 +263,6 @@ io_fd_cloexec(mrb_state *mrb, int fd) #endif } -#if !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) -static int -io_cloexec_pipe(mrb_state *mrb, int fildes[2]) -{ - int ret = pipe(fildes); - if (ret == -1) - return -1; - io_fd_cloexec(mrb, fildes[0]); - io_fd_cloexec(mrb, fildes[1]); - return ret; -} - -static int -io_pipe(mrb_state *mrb, int pipes[2]) -{ - int ret = io_cloexec_pipe(mrb, pipes); - if (ret == -1) { - if (errno == EMFILE || errno == ENFILE) { - mrb_garbage_collect(mrb); - ret = io_cloexec_pipe(mrb, pipes); - } - } - return ret; -} - -static int -io_process_exec(const char *pname) -{ - const char *s = pname; - - while (*s == ' ' || *s == '\t' || *s == '\n') - s++; - - if (!*s) { - errno = ENOENT; - return -1; - } - - execl("/bin/sh", "sh", "-c", pname, (char*)NULL); - return -1; -} -#endif static void io_free(mrb_state *mrb, void *ptr) @@ -348,6 +309,14 @@ io_alloc(mrb_state *mrb) #ifdef MRB_NO_IO_POPEN # define io_s_popen mrb_notimplement_m #else +struct popen_params { + mrb_value klass; + const char *cmd; + int flags; + int doexec; + int opt_in, opt_out, opt_err; +}; + static int option_to_fd(mrb_state *mrb, mrb_value v) { @@ -366,10 +335,8 @@ option_to_fd(mrb_state *mrb, mrb_value v) return -1; /* never reached */ } -static mrb_value -io_s_popen_args(mrb_state *mrb, mrb_value klass, - const char **cmd, int *flags, int *doexec, - int *opt_in, int *opt_out, int *opt_err) +static void +parse_popen_args(mrb_state *mrb, struct popen_params *p) { mrb_value mode = mrb_nil_value(); struct { mrb_value opt_in, opt_out, opt_err; } kv; @@ -381,231 +348,111 @@ io_s_popen_args(mrb_state *mrb, mrb_value klass, NULL, }; - mrb_get_args(mrb, "zo:", cmd, &mode, &kw); + mrb_get_args(mrb, "zo:", &p->cmd, &mode, &kw); - *flags = io_mode_to_flags(mrb, mode); - *doexec = (strcmp("-", *cmd) != 0); - *opt_in = option_to_fd(mrb, kv.opt_in); - *opt_out = option_to_fd(mrb, kv.opt_out); - *opt_err = option_to_fd(mrb, kv.opt_err); - - return mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type)); + p->flags = io_mode_to_flags(mrb, mode); + p->doexec = (strcmp("-", p->cmd) != 0); + p->opt_in = option_to_fd(mrb, kv.opt_in); + p->opt_out = option_to_fd(mrb, kv.opt_out); + p->opt_err = option_to_fd(mrb, kv.opt_err); } -#ifdef _WIN32 static mrb_value io_s_popen(mrb_state *mrb, mrb_value klass) { - int doexec; - int opt_in, opt_out, opt_err; - const char *cmd; - - struct mrb_io *fptr; - int pid = 0, flags; - STARTUPINFO si; - PROCESS_INFORMATION pi; - - HANDLE ifd[2]; - HANDLE ofd[2]; - - ifd[0] = INVALID_HANDLE_VALUE; - ifd[1] = INVALID_HANDLE_VALUE; - ofd[0] = INVALID_HANDLE_VALUE; - ofd[1] = INVALID_HANDLE_VALUE; + struct popen_params p; + p.klass = klass; + int pid = 0; + int pr[2] = { -1, -1 }; /* read pipe: parent reads, child writes */ + int pw[2] = { -1, -1 }; /* write pipe: parent writes, child reads */ + int readable, writable; + int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1; mrb->c->ci->mid = 0; - mrb_value io = io_s_popen_args(mrb, klass, &cmd, &flags, &doexec, - &opt_in, &opt_out, &opt_err); + parse_popen_args(mrb, &p); - SECURITY_ATTRIBUTES saAttr; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; + readable = OPEN_READABLE_P(p.flags); + writable = OPEN_WRITABLE_P(p.flags); - if (OPEN_READABLE_P(flags)) { - if (!CreatePipe(&ofd[0], &ofd[1], &saAttr, 0) - || !SetHandleInformation(ofd[0], HANDLE_FLAG_INHERIT, 0)) { + /* Create pipes for communication */ + if (readable) { + if (mrb_hal_io_pipe(mrb, pr) == -1) { mrb_sys_fail(mrb, "pipe"); } } - if (OPEN_WRITABLE_P(flags)) { - if (!CreatePipe(&ifd[0], &ifd[1], &saAttr, 0) - || !SetHandleInformation(ifd[1], HANDLE_FLAG_INHERIT, 0)) { + if (writable) { + if (mrb_hal_io_pipe(mrb, pw) == -1) { + if (pr[0] != -1) { + mrb_hal_io_close(mrb, pr[0]); + mrb_hal_io_close(mrb, pr[1]); + } mrb_sys_fail(mrb, "pipe"); } } - if (doexec) { - ZeroMemory(&pi, sizeof(pi)); - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - si.dwFlags |= STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; - si.dwFlags |= STARTF_USESTDHANDLES; - if (OPEN_READABLE_P(flags)) { - si.hStdOutput = ofd[1]; - si.hStdError = ofd[1]; + /* Set up child process file descriptors */ + if (p.doexec) { + /* Child stdin: either write pipe read end or opt_in */ + stdin_fd = (p.opt_in != -1) ? p.opt_in : (writable ? pw[0] : -1); + + /* Child stdout: either read pipe write end or opt_out */ + stdout_fd = (p.opt_out != -1) ? p.opt_out : (readable ? pr[1] : -1); + + /* Child stderr: opt_err or stdout */ + stderr_fd = (p.opt_err != -1) ? p.opt_err : stdout_fd; + + /* Spawn child process using HAL */ + if (mrb_hal_io_spawn_process(mrb, p.cmd, stdin_fd, stdout_fd, stderr_fd, &pid) == -1) { + int saved_errno = errno; + if (readable) { + mrb_hal_io_close(mrb, pr[0]); + mrb_hal_io_close(mrb, pr[1]); + } + if (writable) { + mrb_hal_io_close(mrb, pw[0]); + mrb_hal_io_close(mrb, pw[1]); + } + errno = saved_errno; + mrb_raisef(mrb, E_IO_ERROR, "command not found: %s", p.cmd); } - if (OPEN_WRITABLE_P(flags)) { - si.hStdInput = ifd[0]; + + /* Close child ends of pipes in parent */ + if (readable) { + mrb_hal_io_close(mrb, pr[1]); /* close write end */ } - if (!CreateProcess( - NULL, (char*)cmd, NULL, NULL, - TRUE, CREATE_NEW_PROCESS_GROUP, NULL, NULL, &si, &pi)) { - CloseHandle(ifd[0]); - CloseHandle(ifd[1]); - CloseHandle(ofd[0]); - CloseHandle(ofd[1]); - mrb_raisef(mrb, E_IO_ERROR, "command not found: %s", cmd); + if (writable) { + mrb_hal_io_close(mrb, pw[0]); /* close read end */ } - CloseHandle(pi.hThread); - CloseHandle(ifd[0]); - CloseHandle(ofd[1]); - pid = pi.dwProcessId; } - fptr = io_alloc(mrb); - fptr->fd = _open_osfhandle((intptr_t)ofd[0], 0); - fptr->fd2 = _open_osfhandle((intptr_t)ifd[1], 0); + /* Set up parent IO object */ + mrb_value io = mrb_obj_value(mrb_data_object_alloc(mrb, mrb_class_ptr(klass), NULL, &mrb_io_type)); + struct mrb_io *fptr = io_alloc(mrb); + + if (readable && writable) { + fptr->fd = pr[0]; /* parent reads from here */ + fptr->fd2 = pw[1]; /* parent writes to here */ + } + else if (readable) { + fptr->fd = pr[0]; /* parent reads from here */ + fptr->fd2 = -1; + } + else { + fptr->fd = pw[1]; /* parent writes to here */ + fptr->fd2 = -1; + } + fptr->pid = pid; - fptr->readable = OPEN_READABLE_P(flags); - fptr->writable = OPEN_WRITABLE_P(flags); + fptr->readable = readable; + fptr->writable = writable; io_init_buf(mrb, fptr); DATA_TYPE(io) = &mrb_io_type; DATA_PTR(io) = fptr; return io; } -#else -static mrb_value -io_s_popen(mrb_state *mrb, mrb_value klass) -{ - int doexec; - int opt_in, opt_out, opt_err; - const char *cmd; - - int pid, flags, write_fd = -1; - int pr[2] = { -1, -1 }; - int pw[2] = { -1, -1 }; - - mrb->c->ci->mid = 0; - mrb_value io = io_s_popen_args(mrb, klass, &cmd, &flags, &doexec, - &opt_in, &opt_out, &opt_err); - - if (OPEN_READABLE_P(flags)) { - if (pipe(pr) == -1) { - mrb_sys_fail(mrb, "pipe"); - } - io_fd_cloexec(mrb, pr[0]); - io_fd_cloexec(mrb, pr[1]); - } - - if (OPEN_WRITABLE_P(flags)) { - if (pipe(pw) == -1) { - if (pr[0] != -1) close(pr[0]); - if (pr[1] != -1) close(pr[1]); - mrb_sys_fail(mrb, "pipe"); - } - io_fd_cloexec(mrb, pw[0]); - io_fd_cloexec(mrb, pw[1]); - } - - if (!doexec) { - fflush(stdout); - fflush(stderr); - } - - mrb_value result = mrb_nil_value(); - switch (pid = fork()) { - case 0: /* child */ - if (opt_in != -1) { - dup2(opt_in, 0); - } - if (opt_out != -1) { - dup2(opt_out, 1); - } - if (opt_err != -1) { - dup2(opt_err, 2); - } - if (OPEN_READABLE_P(flags)) { - close(pr[0]); - if (pr[1] != 1) { - dup2(pr[1], 1); - close(pr[1]); - } - } - if (OPEN_WRITABLE_P(flags)) { - close(pw[1]); - if (pw[0] != 0) { - dup2(pw[0], 0); - close(pw[0]); - } - } - if (doexec) { - for (int fd = 3; fd < NOFILE; fd++) { - close(fd); - } - io_process_exec(cmd); - mrb_raisef(mrb, E_IO_ERROR, "command not found: %s", cmd); - _exit(127); - } - result = mrb_nil_value(); - break; - - default: /* parent */ - { - int fd; - - if (OPEN_RDWR_P(flags)) { - close(pr[1]); - fd = pr[0]; - close(pw[0]); - write_fd = pw[1]; - } - else if (OPEN_RDONLY_P(flags)) { - close(pr[1]); - fd = pr[0]; - } - else { - close(pw[0]); - fd = pw[1]; - } - - struct mrb_io *fptr = io_alloc(mrb); - fptr->fd = fd; - fptr->fd2 = write_fd; - fptr->pid = pid; - fptr->readable = OPEN_READABLE_P(flags); - fptr->writable = OPEN_WRITABLE_P(flags); - io_init_buf(mrb, fptr); - - DATA_TYPE(io) = &mrb_io_type; - DATA_PTR(io) = fptr; - result = io; - } - break; - - case -1: /* error */ - { - int saved_errno = errno; - if (OPEN_READABLE_P(flags)) { - close(pr[0]); - close(pr[1]); - } - if (OPEN_WRITABLE_P(flags)) { - close(pw[0]); - close(pw[1]); - } - errno = saved_errno; - mrb_sys_fail(mrb, "pipe_open failed"); - } - break; - } - return result; -} -#endif /* _WIN32 */ -#endif /* TARGET_OS_IPHONE */ +#endif /* MRB_NO_IO_POPEN */ static int symdup(mrb_state *mrb, int fd, mrb_bool *failed) @@ -666,6 +513,12 @@ io_init_copy(mrb_state *mrb, mrb_value copy) return copy; } +static mrb_noreturn void +badfd_error(mrb_state *mrb) +{ + mrb_sys_fail(mrb, "bad file descriptor"); +} + static void check_file_descriptor(mrb_state *mrb, mrb_int fd) { @@ -675,7 +528,7 @@ check_file_descriptor(mrb_state *mrb, mrb_int fd) #if MRB_INT_MIN < INT_MIN || MRB_INT_MAX > INT_MAX if (fdi != fd) { errno = EBADF; - goto badfd; + badfd_error(mrb); } #endif @@ -691,27 +544,20 @@ check_file_descriptor(mrb_state *mrb, mrb_int fd) if (fdi < 0 || fdi > _getmaxstdio()) { errno = EBADF; - goto badfd; + badfd_error(mrb); } #endif /* _WIN32 */ if (fstat(fdi, &sb) == 0) return; - if (errno == EBADF) goto badfd; - return; - -badfd: - mrb_sys_fail(mrb, "bad file descriptor"); + if (errno == EBADF) badfd_error(mrb); } static mrb_value io_init(mrb_state *mrb, mrb_value io) { - struct mrb_io *fptr; mrb_int fd; - mrb_value mode, opt; /* opt (Hash) will be ignored */ - int flags; - - mode = opt = mrb_nil_value(); + mrb_value mode = mrb_nil_value(); + mrb_value opt = mrb_nil_value(); if (mrb_block_given_p(mrb)) { mrb_warn(mrb, "File.new() does not take block; use File.open() instead"); @@ -726,9 +572,9 @@ io_init(mrb_state *mrb, mrb_value io) check_file_descriptor(mrb, fd); break; } - flags = io_mode_to_flags(mrb, mode); + int flags = io_mode_to_flags(mrb, mode); - fptr = (struct mrb_io*)DATA_PTR(io); + struct mrb_io *fptr = (struct mrb_io*)DATA_PTR(io); if (fptr != NULL) { fptr_finalize(mrb, fptr, TRUE); mrb_free(mrb, fptr); @@ -1074,22 +920,25 @@ fd_write(mrb_state *mrb, int fd, mrb_value str) fssize_t len = (fssize_t)RSTRING_LEN(str); if (len == 0) return 0; - for (fssize_t sum=0; sumbuf && fptr->buf->len > 0) { + int fd = io_get_write_fd(fptr); off_t n; /* get current position */ @@ -1100,6 +949,15 @@ io_write(mrb_state *mrb, mrb_value io) if (n == -1) mrb_sys_fail(mrb, "lseek(2)"); fptr->buf->start = fptr->buf->len = 0; } +} + +static mrb_value +io_write(mrb_state *mrb, mrb_value io) +{ + struct mrb_io *fptr = io_get_write_fptr(mrb, io); + int fd = io_get_write_fd(fptr); + + io_prepare_write(mrb, fptr); mrb_int len = 0; if (mrb_get_argc(mrb) == 1) { @@ -1117,6 +975,135 @@ io_write(mrb_state *mrb, mrb_value io) return mrb_int_value(mrb, len); } +/* Helper function to write a string followed by newline if needed */ +static void +io_puts_str(mrb_state *mrb, int fd, mrb_value str) +{ + str = mrb_obj_as_string(mrb, str); + const char *ptr = RSTRING_PTR(str); + mrb_int len = RSTRING_LEN(str); + + /* Write the original string */ + fd_write(mrb, fd, 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); + } +} + +/* Recursive helper for puts with arrays */ +static void +io_puts_ary(mrb_state *mrb, int fd, mrb_value ary) +{ + 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); + 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 */ + } + else { + io_puts_str(mrb, fd, elem); + } + } +} + +static mrb_value +io_puts(mrb_state *mrb, mrb_value io) +{ + struct mrb_io *fptr = io_get_write_fptr(mrb, io); + int fd = io_get_write_fd(fptr); + + /* Prepare IO for writing (handle read buffer adjustment) */ + io_prepare_write(mrb, fptr); + + mrb_value *argv; + mrb_int argc; + mrb_get_args(mrb, "*", &argv, &argc); + + if (argc == 0) { + /* No arguments - just write a newline */ + mrb_value newline = mrb_str_new_lit(mrb, "\n"); + fd_write(mrb, fd, newline); + return mrb_nil_value(); + } + + /* Process each argument */ + for (mrb_int i = 0; i < argc; i++) { + mrb_value arg = argv[i]; + if (mrb_array_p(arg)) { + io_puts_ary(mrb, fd, arg); + } + else { + io_puts_str(mrb, fd, arg); + } + } + + return mrb_nil_value(); +} + +/* + * call-seq: + * ios.print() -> nil + * ios.print(obj, ...) -> nil + * + * Writes the given object(s) to ios. Objects that aren't strings will be + * converted by calling their to_s method. + */ +static mrb_value +io_print(mrb_state *mrb, mrb_value io) +{ + struct mrb_io *fptr = io_get_write_fptr(mrb, io); + int fd = io_get_write_fd(fptr); + + /* Prepare IO for writing (handle read buffer adjustment) */ + io_prepare_write(mrb, fptr); + + mrb_value *argv; + mrb_int argc; + mrb_get_args(mrb, "*", &argv, &argc); + + /* Convert each argument to string and write it */ + for (mrb_int i = 0; i < argc; i++) { + mrb_value str = mrb_obj_as_string(mrb, argv[i]); + fd_write(mrb, fd, str); + } + + return mrb_nil_value(); +} + +/* + * call-seq: + * ios << obj -> ios + * + * String Output - Writes obj to ios. obj will be converted to a string using + * to_s. + */ +static mrb_value +io_lshift(mrb_state *mrb, mrb_value io) +{ + struct mrb_io *fptr = io_get_write_fptr(mrb, io); + int fd = io_get_write_fd(fptr); + + /* Prepare IO for writing (handle read buffer adjustment) */ + io_prepare_write(mrb, fptr); + + mrb_value str = mrb_get_arg1(mrb); + str = mrb_obj_as_string(mrb, str); + fd_write(mrb, fd, str); + + return io; +} + static mrb_value io_close(mrb_state *mrb, mrb_value io) { @@ -1126,6 +1113,17 @@ io_close(mrb_state *mrb, mrb_value io) return mrb_nil_value(); } +/* + * call-seq: + * ios.close_write -> nil + * + * Closes the write end of a duplex I/O stream (i.e., a pipe). + * It will raise an `IOError` if the stream is not duplex. + * + * r, w = IO.pipe + * w.close_write + * r.read #=> "" + */ static mrb_value io_close_write(mrb_state *mrb, mrb_value io) { @@ -1136,6 +1134,16 @@ io_close_write(mrb_state *mrb, mrb_value io) return mrb_nil_value(); } +/* + * call-seq: + * ios.closed? -> true or false + * + * Returns `true` if the stream is closed, `false` otherwise. + * + * f = File.new("testfile") + * f.close #=> nil + * f.closed? #=> true + */ static mrb_value io_closed(mrb_state *mrb, mrb_value io) { @@ -1162,6 +1170,24 @@ io_pos(mrb_state *mrb, mrb_value io) } } +/* + * call-seq: + * ios.pid -> integer or nil + * + * Returns the process ID of a child process on a pipe, or `nil` if the + * stream is not a pipe. + * + * r, w = IO.pipe + * fork do + * r.close + * w.write "hello" + * w.close + * end + * w.close + * p r.pid #=> 2056 + * r.read #=> "hello" + * r.close + */ static mrb_value io_pid(mrb_state *mrb, mrb_value io) { @@ -1174,21 +1200,21 @@ io_pid(mrb_state *mrb, mrb_value io) return mrb_nil_value(); } -static struct timeval +static mrb_io_timeval time2timeval(mrb_state *mrb, mrb_value time) { - struct timeval t = { 0, 0 }; + mrb_io_timeval t = { 0, 0 }; switch (mrb_type(time)) { case MRB_TT_INTEGER: - t.tv_sec = (ftime_t)mrb_integer(time); + t.tv_sec = (int64_t)mrb_integer(time); t.tv_usec = 0; break; #ifndef MRB_NO_FLOAT case MRB_TT_FLOAT: - t.tv_sec = (ftime_t)mrb_float(time); - t.tv_usec = (fsuseconds_t)((mrb_float(time) - t.tv_sec) * 1000000.0); + t.tv_sec = (int64_t)mrb_float(time); + t.tv_usec = (int64_t)((mrb_float(time) - t.tv_sec) * 1000000.0); break; #endif @@ -1199,13 +1225,24 @@ time2timeval(mrb_state *mrb, mrb_value time) return t; } +/* + * call-seq: + * IO.new(fd, mode="r") -> io + * + * Returns a new `IO` object for the given integer file descriptor `fd` and + * `mode` string. + * + * f = IO.new(1, "w") # STDOUT + * f.puts "hello" + */ + #if !defined(_WIN32) && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) static mrb_value io_s_pipe(mrb_state *mrb, mrb_value klass) { int pipes[2]; - if (io_pipe(mrb, pipes) == -1) { + if (mrb_hal_io_pipe(mrb, pipes) == -1) { mrb_sys_fail(mrb, "pipe"); } @@ -1236,6 +1273,24 @@ mrb_io_read_data_pending(mrb_state *mrb, struct mrb_io *fptr) return 0; } +/* + * call-seq: + * IO.select(read_array, write_array=nil, error_array=nil, timeout=nil) -> array or nil + * + * Performs a `select(2)` system call on the given arrays of `IO` objects. + * + * For each array, it can contain `IO` objects or `nil`. + * + * The `timeout` argument is a number of seconds. + * + * It returns a three-element array containing the `IO` objects that are + * ready for reading, writing, or have an error, respectively. + * + * If the `timeout` is reached, it returns `nil`. + * + * r, w = IO.pipe + * IO.select([r], [w]) #=> [[#], [#], []] + */ static mrb_value io_s_select(mrb_state *mrb, mrb_value klass) { @@ -1265,7 +1320,7 @@ io_s_select(mrb_state *mrb, mrb_value klass) write = argv[1]; mrb_value read = argv[0]; - struct timeval *tp, timerec; + mrb_io_timeval *tp, timerec; if (mrb_nil_p(timeout)) { tp = NULL; } @@ -1274,20 +1329,24 @@ io_s_select(mrb_state *mrb, mrb_value klass) tp = &timerec; } - fd_set pset, rset, *rp; - FD_ZERO(&pset); + mrb_io_fdset *pset = mrb_hal_io_fdset_alloc(mrb); + mrb_io_fdset *rset = NULL; + mrb_io_fdset *rp = NULL; + mrb_hal_io_fdset_zero(mrb, pset); if (!mrb_nil_p(read)) { mrb_check_type(mrb, read, MRB_TT_ARRAY); - rp = &rset; - FD_ZERO(rp); + rset = mrb_hal_io_fdset_alloc(mrb); + rp = rset; + mrb_hal_io_fdset_zero(mrb, rp); + /* Hoist pointer retrieval outside loop */ + mrb_value *read_ptr = RARRAY_PTR(read); for (int i = 0; i < RARRAY_LEN(read); i++) { - read_io = RARRAY_PTR(read)[i]; + read_io = read_ptr[i]; fptr = io_get_open_fptr(mrb, read_io); - if (fptr->fd >= FD_SETSIZE) continue; - FD_SET(fptr->fd, rp); + mrb_hal_io_fdset_set(mrb, fptr->fd, rp); if (mrb_io_read_data_pending(mrb, fptr)) { pending++; - FD_SET(fptr->fd, &pset); + mrb_hal_io_fdset_set(mrb, fptr->fd, pset); } if (max < fptr->fd) max = fptr->fd; @@ -1297,75 +1356,76 @@ io_s_select(mrb_state *mrb, mrb_value klass) tp = &timerec; } } - else { - rp = NULL; - } - fd_set wset, *wp; + mrb_io_fdset *wset = NULL; + mrb_io_fdset *wp = NULL; if (!mrb_nil_p(write)) { mrb_check_type(mrb, write, MRB_TT_ARRAY); - wp = &wset; - FD_ZERO(wp); + wset = mrb_hal_io_fdset_alloc(mrb); + wp = wset; + mrb_hal_io_fdset_zero(mrb, wp); + /* Hoist pointer retrieval outside loop */ + mrb_value *write_ptr = RARRAY_PTR(write); for (int i = 0; i < RARRAY_LEN(write); i++) { - fptr = io_get_open_fptr(mrb, RARRAY_PTR(write)[i]); - if (fptr->fd >= FD_SETSIZE) continue; - FD_SET(fptr->fd, wp); + fptr = io_get_open_fptr(mrb, write_ptr[i]); + mrb_hal_io_fdset_set(mrb, fptr->fd, wp); if (max < fptr->fd) max = fptr->fd; if (fptr->fd2 >= 0) { - FD_SET(fptr->fd2, wp); + mrb_hal_io_fdset_set(mrb, fptr->fd2, wp); if (max < fptr->fd2) max = fptr->fd2; } } } - else { - wp = NULL; - } - fd_set eset, *ep; + mrb_io_fdset *eset = NULL; + mrb_io_fdset *ep = NULL; if (!mrb_nil_p(except)) { mrb_check_type(mrb, except, MRB_TT_ARRAY); - ep = &eset; - FD_ZERO(ep); + eset = mrb_hal_io_fdset_alloc(mrb); + ep = eset; + mrb_hal_io_fdset_zero(mrb, ep); + /* Hoist pointer retrieval outside loop */ + mrb_value *except_ptr = RARRAY_PTR(except); for (int i = 0; i < RARRAY_LEN(except); i++) { - fptr = io_get_open_fptr(mrb, RARRAY_PTR(except)[i]); - if (fptr->fd >= FD_SETSIZE) continue; - FD_SET(fptr->fd, ep); + fptr = io_get_open_fptr(mrb, except_ptr[i]); + mrb_hal_io_fdset_set(mrb, fptr->fd, ep); if (max < fptr->fd) max = fptr->fd; if (fptr->fd2 >= 0) { - FD_SET(fptr->fd2, ep); + mrb_hal_io_fdset_set(mrb, fptr->fd2, ep); if (max < fptr->fd2) max = fptr->fd2; } } } - else { - ep = NULL; - } max++; int n; retry: - n = select(max, rp, wp, ep, tp); + n = mrb_hal_io_select(mrb, max, rp, wp, ep, tp); if (n < 0) { -#ifdef _WIN32 - errno = WSAGetLastError(); - if (errno != WSAEINTR) + if (errno != EINTR) { + mrb_hal_io_fdset_free(mrb, pset); + mrb_hal_io_fdset_free(mrb, rset); + mrb_hal_io_fdset_free(mrb, wset); + mrb_hal_io_fdset_free(mrb, eset); mrb_sys_fail(mrb, "select failed"); -#else - if (errno != EINTR) - mrb_sys_fail(mrb, "select failed"); -#endif + } if (tp == NULL) goto retry; interrupt_flag = 1; } - if (!pending && n == 0) + if (!pending && n == 0) { + mrb_hal_io_fdset_free(mrb, pset); + mrb_hal_io_fdset_free(mrb, rset); + mrb_hal_io_fdset_free(mrb, wset); + mrb_hal_io_fdset_free(mrb, eset); return mrb_nil_value(); + } result = mrb_ary_new_capa(mrb, 3); mrb_ary_push(mrb, result, rp ? mrb_ary_new(mrb) : mrb_ary_new_capa(mrb, 0)); @@ -1375,42 +1435,56 @@ retry: if (interrupt_flag == 0) { if (rp) { list = RARRAY_PTR(result)[0]; + /* Hoist pointer retrieval outside loop */ + mrb_value *read_ptr = RARRAY_PTR(read); for (int i = 0; i < RARRAY_LEN(read); i++) { - fptr = io_get_open_fptr(mrb, RARRAY_PTR(read)[i]); - if (FD_ISSET(fptr->fd, rp) || - FD_ISSET(fptr->fd, &pset)) { - mrb_ary_push(mrb, list, RARRAY_PTR(read)[i]); + mrb_value io = read_ptr[i]; + fptr = io_get_open_fptr(mrb, io); + if (mrb_hal_io_fdset_isset(mrb, fptr->fd, rp) || + mrb_hal_io_fdset_isset(mrb, fptr->fd, pset)) { + mrb_ary_push(mrb, list, io); } } } if (wp) { list = RARRAY_PTR(result)[1]; + /* Hoist pointer retrieval outside loop */ + mrb_value *write_ptr = RARRAY_PTR(write); for (int i = 0; i < RARRAY_LEN(write); i++) { - fptr = io_get_open_fptr(mrb, RARRAY_PTR(write)[i]); - if (FD_ISSET(fptr->fd, wp)) { - mrb_ary_push(mrb, list, RARRAY_PTR(write)[i]); + mrb_value io = write_ptr[i]; + fptr = io_get_open_fptr(mrb, io); + if (mrb_hal_io_fdset_isset(mrb, fptr->fd, wp)) { + mrb_ary_push(mrb, list, io); } - else if (fptr->fd2 >= 0 && FD_ISSET(fptr->fd2, wp)) { - mrb_ary_push(mrb, list, RARRAY_PTR(write)[i]); + else if (fptr->fd2 >= 0 && mrb_hal_io_fdset_isset(mrb, fptr->fd2, wp)) { + mrb_ary_push(mrb, list, io); } } } if (ep) { list = RARRAY_PTR(result)[2]; + /* Hoist pointer retrieval outside loop */ + mrb_value *except_ptr = RARRAY_PTR(except); for (int i = 0; i < RARRAY_LEN(except); i++) { - fptr = io_get_open_fptr(mrb, RARRAY_PTR(except)[i]); - if (FD_ISSET(fptr->fd, ep)) { - mrb_ary_push(mrb, list, RARRAY_PTR(except)[i]); + mrb_value io = except_ptr[i]; + fptr = io_get_open_fptr(mrb, io); + if (mrb_hal_io_fdset_isset(mrb, fptr->fd, ep)) { + mrb_ary_push(mrb, list, io); } - else if (fptr->fd2 >= 0 && FD_ISSET(fptr->fd2, ep)) { - mrb_ary_push(mrb, list, RARRAY_PTR(except)[i]); + else if (fptr->fd2 >= 0 && mrb_hal_io_fdset_isset(mrb, fptr->fd2, ep)) { + mrb_ary_push(mrb, list, io); } } } } + mrb_hal_io_fdset_free(mrb, pset); + mrb_hal_io_fdset_free(mrb, rset); + mrb_hal_io_fdset_free(mrb, wset); + mrb_hal_io_fdset_free(mrb, eset); + return result; } @@ -1421,6 +1495,15 @@ mrb_io_fileno(mrb_state *mrb, mrb_value io) return fptr->fd; } +/* + * call-seq: + * ios.fileno -> integer + * + * Returns the integer file descriptor number for the `IO` object. + * + * $stdin.fileno #=> 0 + * $stdout.fileno #=> 1 + */ static mrb_value io_fileno(mrb_state *mrb, mrb_value io) { @@ -1429,6 +1512,16 @@ io_fileno(mrb_state *mrb, mrb_value io) } #if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) +/* + * call-seq: + * ios.close_on_exec? -> true or false + * + * Returns `true` if the `FD_CLOEXEC` flag is set for the `IO` object, `false` + * otherwise. + * + * f = IO.new(1, "w") + * f.close_on_exec? #=> true + */ static mrb_value io_close_on_exec_p(mrb_state *mrb, mrb_value io) { @@ -1449,6 +1542,16 @@ io_close_on_exec_p(mrb_state *mrb, mrb_value io) #endif #if defined(F_GETFD) && defined(F_SETFD) && defined(FD_CLOEXEC) +/* + * call-seq: + * ios.close_on_exec = bool -> bool + * + * Sets the `FD_CLOEXEC` flag on the `IO` object. + * + * f = IO.new(1, "w") + * f.close_on_exec = false + * f.close_on_exec? #=> false + */ static mrb_value io_set_close_on_exec(mrb_state *mrb, mrb_value io) { @@ -1484,6 +1587,18 @@ io_set_close_on_exec(mrb_state *mrb, mrb_value io) # define io_set_close_on_exec mrb_notimplement_m #endif +/* + * call-seq: + * ios.sync = bool -> bool + * + * Sets the sync mode for the `IO` object. + * + * If `true`, all output is immediately flushed to the underlying operating + * system and is not buffered internally. + * + * f = File.new("testfile", "w") + * f.sync = true + */ static mrb_value io_set_sync(mrb_state *mrb, mrb_value io) { @@ -1495,6 +1610,15 @@ io_set_sync(mrb_state *mrb, mrb_value io) return mrb_bool_value(b); } +/* + * call-seq: + * ios.sync -> true or false + * + * Returns the sync mode for the `IO` object. + * + * f = File.new("testfile", "w") + * f.sync #=> false + */ static mrb_value io_sync(mrb_state *mrb, mrb_value io) { @@ -1543,26 +1667,83 @@ io_pwrite(mrb_state *mrb, mrb_value io) } #endif /* MRB_USE_IO_PREAD_PWRITE */ -static mrb_value -io_ungetc(mrb_state *mrb, mrb_value io) +/* + * call-seq: + * ios.ungetc(string) -> nil + * + * Pushes back characters (passed as a parameter) onto ios, such that a + * subsequent buffered character read will return it. Has no effect with + * unbuffered reads (such as IO#sysread). + * + * f = File.new("testfile") #=> # + * c = f.getc #=> "H" + * f.ungetc(c) #=> nil + * f.getc #=> "H" + */ +/* Helper function for ungetc operations with raw data */ +static void +io_unget_data(mrb_state *mrb, struct mrb_io *fptr, const char *ptr, mrb_int len) { - struct mrb_io *fptr = io_get_read_fptr(mrb, io); struct mrb_io_buf *buf = fptr->buf; - mrb_value str; - mrb_get_args(mrb, "S", &str); - mrb_int len = RSTRING_LEN(str); if (len > SHRT_MAX) { mrb_raise(mrb, E_ARGUMENT_ERROR, "string too long to ungetc"); } - if (len > MRB_IO_BUF_SIZE - buf->len) { + if (buf->len + len > SHRT_MAX) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "total ungetc buffer exceeds maximum size"); + } + if (buf->len + len > MRB_IO_BUF_SIZE) { fptr->buf = (struct mrb_io_buf*)mrb_realloc(mrb, buf, sizeof(struct mrb_io_buf)+buf->len+len-MRB_IO_BUF_SIZE); buf = fptr->buf; } memmove(buf->mem+len, buf->mem+buf->start, buf->len); - memcpy(buf->mem, RSTRING_PTR(str), len); + memcpy(buf->mem, ptr, len); buf->start = 0; buf->len += (short)len; +} + +static mrb_value +io_ungetc(mrb_state *mrb, mrb_value io) +{ + struct mrb_io *fptr = io_get_read_fptr(mrb, io); + mrb_value str; + + mrb_get_args(mrb, "S", &str); + io_unget_data(mrb, fptr, RSTRING_PTR(str), RSTRING_LEN(str)); + return mrb_nil_value(); +} + +/* + * call-seq: + * ios.ungetbyte(string) -> nil + * ios.ungetbyte(integer) -> nil + * + * Pushes back bytes (passed as a parameter) onto ios, such that a subsequent + * buffered character read will return it. Only one byte may be pushed back + * before a subsequent read operation (that is, you will be able to read only + * the last of several bytes that have been pushed back). Has no effect with + * unbuffered reads (such as IO#sysread). + */ +static mrb_value +io_ungetbyte(mrb_state *mrb, mrb_value io) +{ + struct mrb_io *fptr = io_get_read_fptr(mrb, io); + mrb_value c = mrb_get_arg1(mrb); + unsigned char byte_val; + + if (mrb_string_p(c)) { + if (RSTRING_LEN(c) == 0) { + return mrb_nil_value(); /* Empty string, do nothing */ + } + byte_val = (unsigned char)RSTRING_PTR(c)[0]; + } + else { + mrb_int val = mrb_integer(c); + byte_val = (unsigned char)(val & 0xff); + } + + /* Use helper function with single byte */ + io_unget_data(mrb, fptr, (const char*)&byte_val, 1); return mrb_nil_value(); } @@ -1662,6 +1843,18 @@ io_reset_outbuf(mrb_state *mrb, mrb_value outbuf, mrb_int len) return outbuf; } +/* + * call-seq: + * ios.read(length = nil, outbuf = "") -> string, outbuf, or nil + * + * Reads `length` bytes from the I/O stream. + * + * If `length` is `nil`, it reads until end of file. + * If `outbuf` is given, it will be used as the buffer. + * + * f = File.new("testfile") + * f.read(16) #=> "This is line one" + */ static mrb_value io_read(mrb_state *mrb, mrb_value io) { @@ -1734,7 +1927,7 @@ io_gets(mrb_state *mrb, mrb_value io) { mrb_value rs = mrb_nil_value(); mrb_bool rs_given = FALSE; /* newline break */ - mrb_int limit; + mrb_int limit = 0; mrb_bool limit_given = FALSE; /* no limit */ struct mrb_io *fptr = io_get_read_fptr(mrb, io); struct mrb_io_buf *buf = fptr->buf; @@ -1782,6 +1975,9 @@ io_gets(mrb_state *mrb, mrb_value io) mrb_value outbuf; if (limit_given) { + if (limit < 0) { + mrb_raisef(mrb, E_ARGUMENT_ERROR, "negative length %i given", limit); + } if (limit == 0) return mrb_str_new(mrb, NULL, 0); outbuf = mrb_str_new_capa(mrb, limit); } @@ -1875,6 +2071,17 @@ io_readchar(mrb_state *mrb, mrb_value io) return result; } +/* + * call-seq: + * ios.getbyte -> integer or nil + * + * Reads a byte from the `IO` stream. + * + * Returns the byte as an integer, or `nil` at end of file. + * + * f = File.new("testfile") + * f.getbyte #=> 72 + */ static mrb_value io_getbyte(mrb_state *mrb, mrb_value io) { @@ -1889,6 +2096,17 @@ io_getbyte(mrb_state *mrb, mrb_value io) return mrb_int_value(mrb, (mrb_int)c); } +/* + * call-seq: + * ios.readbyte -> integer + * + * Reads a byte from the `IO` stream. + * + * Returns the byte as an integer. Raises `EOFError` at end of file. + * + * f = File.new("testfile") + * f.readbyte #=> 72 + */ static mrb_value io_readbyte(mrb_state *mrb, mrb_value io) { @@ -1899,6 +2117,16 @@ io_readbyte(mrb_state *mrb, mrb_value io) return result; } +/* + * call-seq: + * ios.flush -> ios + * + * Flushes any buffered data within the `IO` object to the underlying + * operating system. + * + * $stdout.print "no newline" + * $stdout.flush + */ static mrb_value io_flush(mrb_state *mrb, mrb_value io) { @@ -1945,10 +2173,14 @@ mrb_init_io(mrb_state *mrb) mrb_define_method_id(mrb, io, MRB_SYM_Q(closed), io_closed, MRB_ARGS_NONE()); /* 15.2.20.5.2 */ mrb_define_method_id(mrb, io, MRB_SYM(flush), io_flush, MRB_ARGS_NONE()); /* 15.2.20.5.7 */ mrb_define_method_id(mrb, io, MRB_SYM(ungetc), io_ungetc, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, io, MRB_SYM(ungetbyte), io_ungetbyte, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, io, MRB_SYM(pos), io_pos, MRB_ARGS_NONE()); mrb_define_method_id(mrb, io, MRB_SYM(pid), io_pid, MRB_ARGS_NONE()); mrb_define_method_id(mrb, io, MRB_SYM(fileno), io_fileno, MRB_ARGS_NONE()); mrb_define_method_id(mrb, io, MRB_SYM(write), io_write, MRB_ARGS_ANY()); /* 15.2.20.5.20 */ + mrb_define_method_id(mrb, io, MRB_SYM(puts), io_puts, MRB_ARGS_ANY()); + mrb_define_method_id(mrb, io, MRB_SYM(print), io_print, MRB_ARGS_ANY()); + mrb_define_method_id(mrb, io, MRB_OPSYM(lshift), io_lshift, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, io, MRB_SYM(pread), io_pread, MRB_ARGS_ANY()); /* Ruby 2.5 feature */ mrb_define_method_id(mrb, io, MRB_SYM(pwrite), io_pwrite, MRB_ARGS_ANY()); /* Ruby 2.5 feature */ mrb_define_method_id(mrb, io, MRB_SYM(getbyte), io_getbyte, MRB_ARGS_NONE()); diff --git a/mrbgems/mruby-io/test/file.rb b/mrbgems/mruby-io/test/file.rb index e6eda5434..b65f7f8f8 100644 --- a/mrbgems/mruby-io/test/file.rb +++ b/mrbgems/mruby-io/test/file.rb @@ -315,6 +315,11 @@ assert('File.expand_path (with getenv(3))') do assert_equal "#{MRubyIOTestUtil::ENV_HOME}/user", File.expand_path("user", MRubyIOTestUtil::ENV_HOME), "relative with base_dir" end +assert('File.absolute_path') do + assert_equal File.expand_path("./~"), File.absolute_path("~") + assert_equal File.expand_path("./~user1"), File.absolute_path("~user1") +end + if MRubyIOTestUtil.win? assert('File.absolute_path? (for windows)') do assert_true File.absolute_path?("c:/") @@ -375,6 +380,8 @@ assert('File.chmod') do begin assert_equal 1, File.chmod(0400, "#{$mrbtest_io_wfname}.chmod-test") ensure + # On Windows, must restore write permission before deletion + File.chmod(0600, "#{$mrbtest_io_wfname}.chmod-test") rescue nil File.delete("#{$mrbtest_io_wfname}.chmod-test") end end diff --git a/mrbgems/mruby-io/test/io.rb b/mrbgems/mruby-io/test/io.rb index e87cbe8fb..c5887d969 100644 --- a/mrbgems/mruby-io/test/io.rb +++ b/mrbgems/mruby-io/test/io.rb @@ -2,7 +2,7 @@ # IO Test MRubyIOTestUtil.io_test_setup -$cr, $crlf, $cmd = MRubyIOTestUtil.win? ? [1, "\r\n", "cmd /c "] : [0, "\n", ""] +$cr, $cmd = MRubyIOTestUtil.win? ? [1, "cmd /c "] : [0, ""] def assert_io_open(meth) assert "assert_io_open" do @@ -658,7 +658,8 @@ end assert('`cmd`') do begin - assert_equal `#{$cmd}echo foo`, "foo#{$crlf}" + result = `#{$cmd}echo foo` + assert_equal "foo", result.chomp rescue NotImplementedError => e skip e.message end diff --git a/mrbgems/mruby-kernel-ext/src/kernel.c b/mrbgems/mruby-kernel-ext/src/kernel.c index bd056db7d..df2e52e10 100644 --- a/mrbgems/mruby-kernel-ext/src/kernel.c +++ b/mrbgems/mruby-kernel-ext/src/kernel.c @@ -92,7 +92,7 @@ mrb_f_caller(mrb_state *mrb, mrb_value self) * __method__ -> symbol * * Returns the called name of the current method as a Symbol. - * If called outside of a method, it returns nil. + * If called outside of a method, it returns `nil`. * */ static mrb_value @@ -113,7 +113,7 @@ mrb_f_method(mrb_state *mrb, mrb_value self) * __callee__ -> symbol * * Returns the called name of the current method as a Symbol. - * If called outside of a method, it returns nil. + * If called outside of a method, it returns `nil`. * */ static mrb_value @@ -131,16 +131,16 @@ mrb_f_callee(mrb_state *mrb, mrb_value self) * call-seq: * Integer(arg,base=0) -> integer * - * Converts arg to a Integer. + * Converts *arg* to a `Integer`. * Numeric types are converted directly (with floating-point numbers - * being truncated). base (0, or between 2 and 36) is a base for - * integer string representation. If arg is a String, - * when base is omitted or equals to zero, radix indicators - * (0, 0b, and 0x) are honored. + * being truncated). *base* (0, or between 2 and 36) is a base for + * integer string representation. If *arg* is a `String`, + * when *base* is omitted or equals to zero, radix indicators + * (`0`, `0b`, and `0x`) are honored. * In any case, strings should be strictly conformed to numeric * representation. This behavior is different from that of - * String#to_i. Non string values will be treated as integers. - * Passing nil raises a TypeError. + * `String#to_i`. Non string values will be treated as integers. + * Passing `nil` raises a TypeError. * * Integer(123.999) #=> 123 * Integer("0x1a") #=> 26 @@ -149,6 +149,12 @@ mrb_f_callee(mrb_state *mrb, mrb_value self) * Integer("111", 2) #=> 7 * Integer(nil) #=> TypeError */ +static mrb_noreturn void +arg_error(mrb_state *mrb) +{ + mrb_raise(mrb, E_ARGUMENT_ERROR, "base specified for non string value"); +} + static mrb_value mrb_f_integer(mrb_state *mrb, mrb_value self) { @@ -157,18 +163,18 @@ mrb_f_integer(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "o|i", &val, &base); if (mrb_nil_p(val)) { - if (base != 0) goto arg_error; + if (base != 0) arg_error(mrb); mrb_raise(mrb, E_TYPE_ERROR, "can't convert nil into Integer"); } switch (mrb_type(val)) { #ifndef MRB_NO_FLOAT case MRB_TT_FLOAT: - if (base != 0) goto arg_error; + if (base != 0) arg_error(mrb); return mrb_float_to_integer(mrb, val); #endif case MRB_TT_INTEGER: - if (base != 0) goto arg_error; + if (base != 0) arg_error(mrb); return val; case MRB_TT_STRING: @@ -184,8 +190,7 @@ mrb_f_integer(mrb_state *mrb, mrb_value self) val = tmp; goto string_conv; } -arg_error: - mrb_raise(mrb, E_ARGUMENT_ERROR, "base specified for non string value"); + arg_error(mrb); } /* to raise TypeError */ return mrb_ensure_integer_type(mrb, val); @@ -196,8 +201,8 @@ arg_error: * call-seq: * Float(arg) -> float * - * Returns arg converted to a float. Numeric types are converted - * directly, the rest are converted using arg.to_f. + * Returns *arg* converted to a float. Numeric types are converted + * directly, the rest are converted using *arg*.to_f. * * Float(1) #=> 1.0 * Float(123.456) #=> 123.456 @@ -220,8 +225,8 @@ mrb_f_float(mrb_state *mrb, mrb_value self) * call-seq: * String(arg) -> string * - * Returns arg as an String. - * converted using to_s method. + * Returns *arg* as an `String`. + * converted using `to_s` method. * * String(self) #=> "main" * String(self.class) #=> "Object" @@ -231,9 +236,7 @@ static mrb_value mrb_f_string(mrb_state *mrb, mrb_value self) { mrb_value arg = mrb_get_arg1(mrb); - mrb_value tmp; - - tmp = mrb_type_convert(mrb, arg, MRB_TT_STRING, MRB_SYM(to_s)); + mrb_value tmp = mrb_type_convert(mrb, arg, MRB_TT_STRING, MRB_SYM(to_s)); return tmp; } @@ -241,7 +244,7 @@ mrb_f_string(mrb_state *mrb, mrb_value self) * call-seq: * Array(arg) -> array * - * Returns +arg+ as an Array using to_a method. + * Returns `arg` as an Array using to_a method. * * Array(1..5) #=> [1, 2, 3, 4, 5] * @@ -250,9 +253,7 @@ static mrb_value mrb_f_array(mrb_state *mrb, mrb_value self) { mrb_value arg = mrb_get_arg1(mrb); - mrb_value tmp; - - tmp = mrb_type_convert_check(mrb, arg, MRB_TT_ARRAY, MRB_SYM(to_a)); + mrb_value tmp = mrb_type_convert_check(mrb, arg, MRB_TT_ARRAY, MRB_SYM(to_a)); if (mrb_nil_p(tmp)) { return mrb_ary_new_from_values(mrb, 1, &arg); } @@ -264,9 +265,9 @@ mrb_f_array(mrb_state *mrb, mrb_value self) * call-seq: * Hash(arg) -> hash * - * Returns a Hash if arg is a Hash. - * Returns an empty Hash when arg is nil - * or []. + * Returns a `Hash` if *arg* is a `Hash`. + * Returns an empty `Hash` when *arg* is `nil` + * or `[]`. * * Hash([]) #=> {} * Hash(nil) #=> {} diff --git a/mrbgems/mruby-math/src/math.c b/mrbgems/mruby-math/src/math.c index 0d2f4cc48..bf5ee9b5b 100644 --- a/mrbgems/mruby-math/src/math.c +++ b/mrbgems/mruby-math/src/math.c @@ -184,7 +184,7 @@ log2(double x) * call-seq: * Math.sin(x) -> float * - * Computes the sine of x (expressed in radians). Returns + * Computes the sine of *x* (expressed in radians). Returns * -1..1. */ static mrb_value @@ -198,7 +198,7 @@ math_sin(mrb_state *mrb, mrb_value obj) * call-seq: * Math.cos(x) -> float * - * Computes the cosine of x (expressed in radians). Returns + * Computes the cosine of *x* (expressed in radians). Returns * -1..1. */ static mrb_value @@ -212,7 +212,7 @@ math_cos(mrb_state *mrb, mrb_value obj) * call-seq: * Math.tan(x) -> float * - * Returns the tangent of x (expressed in radians). + * Returns the tangent of *x* (expressed in radians). */ static mrb_value math_tan(mrb_state *mrb, mrb_value obj) @@ -229,7 +229,7 @@ math_tan(mrb_state *mrb, mrb_value obj) * call-seq: * Math.asin(x) -> float * - * Computes the arc sine of x. + * Computes the arc sine of *x*. * @return computed value between `-(PI/2)` and `(PI/2)`. */ static mrb_value @@ -249,7 +249,7 @@ math_asin(mrb_state *mrb, mrb_value obj) * call-seq: * Math.acos(x) -> float * - * Computes the arc cosine of x. Returns 0..PI. + * Computes the arc cosine of *x*. Returns 0..PI. */ static mrb_value math_acos(mrb_state *mrb, mrb_value obj) @@ -268,7 +268,7 @@ math_acos(mrb_state *mrb, mrb_value obj) * call-seq: * Math.atan(x) -> float * - * Computes the arc tangent of x. Returns `-(PI/2) .. (PI/2)`. + * Computes the arc tangent of *x*. Returns `-(PI/2) .. (PI/2)`. */ static mrb_value math_atan(mrb_state *mrb, mrb_value obj) @@ -281,7 +281,7 @@ math_atan(mrb_state *mrb, mrb_value obj) * call-seq: * Math.atan2(y, x) -> float * - * Computes the arc tangent given y and x. Returns + * Computes the arc tangent given *y* and *x*. Returns * -PI..PI. * * Math.atan2(-0.0, -1.0) #=> -3.141592653589793 @@ -314,7 +314,7 @@ math_atan2(mrb_state *mrb, mrb_value obj) * call-seq: * Math.sinh(x) -> float * - * Computes the hyperbolic sine of x (expressed in + * Computes the hyperbolic sine of *x* (expressed in * radians). */ static mrb_value @@ -328,7 +328,7 @@ math_sinh(mrb_state *mrb, mrb_value obj) * call-seq: * Math.cosh(x) -> float * - * Computes the hyperbolic cosine of x (expressed in radians). + * Computes the hyperbolic cosine of *x* (expressed in radians). */ static mrb_value math_cosh(mrb_state *mrb, mrb_value obj) @@ -341,7 +341,7 @@ math_cosh(mrb_state *mrb, mrb_value obj) * call-seq: * Math.tanh() -> float * - * Computes the hyperbolic tangent of x (expressed in + * Computes the hyperbolic tangent of *x* (expressed in * radians). */ static mrb_value @@ -360,7 +360,7 @@ math_tanh(mrb_state *mrb, mrb_value obj) * call-seq: * Math.asinh(x) -> float * - * Computes the inverse hyperbolic sine of x. + * Computes the inverse hyperbolic sine of *x*. */ static mrb_value math_asinh(mrb_state *mrb, mrb_value obj) @@ -373,7 +373,7 @@ math_asinh(mrb_state *mrb, mrb_value obj) * call-seq: * Math.acosh(x) -> float * - * Computes the inverse hyperbolic cosine of x. + * Computes the inverse hyperbolic cosine of *x*. */ static mrb_value math_acosh(mrb_state *mrb, mrb_value obj) @@ -392,7 +392,7 @@ math_acosh(mrb_state *mrb, mrb_value obj) * call-seq: * Math.atanh(x) -> float * - * Computes the inverse hyperbolic tangent of x. + * Computes the inverse hyperbolic tangent of *x*. */ static mrb_value math_atanh(mrb_state *mrb, mrb_value obj) @@ -429,12 +429,42 @@ math_exp(mrb_state *mrb, mrb_value obj) return mrb_float_value(mrb, x); } +/* + * call-seq: + * Math.expm1(x) -> float + * + * Returns exp(x) - 1. + */ +static mrb_value +math_expm1(mrb_state *mrb, mrb_value obj) +{ + mrb_float x = expm1(get_float_arg(mrb)); + return mrb_float_value(mrb, x); +} + +/* + * call-seq: + * Math.log1p(x) -> float + * + * Returns log(1 + x). + */ +static mrb_value +math_log1p(mrb_state *mrb, mrb_value obj) +{ + mrb_float x = get_float_arg(mrb); + if (x < -1.0) { + domain_error(mrb, "log1p"); + } + x = log1p(x); + return mrb_float_value(mrb, x); +} + /* * call-seq: * Math.log(numeric) -> float * Math.log(num,base) -> float * - * Returns the natural logarithm of numeric. + * Returns the natural logarithm of *numeric*. * If additional second argument is given, it will be the base * of logarithm. * @@ -448,9 +478,8 @@ static mrb_value math_log(mrb_state *mrb, mrb_value obj) { mrb_float x, base; - mrb_int argc; + mrb_int argc = mrb_get_args(mrb, "f|f", &x, &base); - argc = mrb_get_args(mrb, "f|f", &x, &base); if (x < 0.0) { domain_error(mrb, "log"); } @@ -468,7 +497,7 @@ math_log(mrb_state *mrb, mrb_value obj) * call-seq: * Math.log2(numeric) -> float * - * Returns the base 2 logarithm of numeric. + * Returns the base 2 logarithm of *numeric*. * * Math.log2(1) #=> 0.0 * Math.log2(2) #=> 1.0 @@ -493,7 +522,7 @@ math_log2(mrb_state *mrb, mrb_value obj) * call-seq: * Math.log10(numeric) -> float * - * Returns the base 10 logarithm of numeric. + * Returns the base 10 logarithm of *numeric*. * * Math.log10(1) #=> 0.0 * Math.log10(10) #=> 1.0 @@ -517,7 +546,7 @@ math_log10(mrb_state *mrb, mrb_value obj) * call-seq: * Math.sqrt(numeric) -> float * - * Returns the square root of numeric. + * Returns the square root of *numeric*. * */ static mrb_value @@ -538,7 +567,7 @@ math_sqrt(mrb_state *mrb, mrb_value obj) * call-seq: * Math.cbrt(numeric) -> float * - * Returns the cube root of numeric. + * Returns the cube root of *numeric*. * * -9.upto(9) {|x| * p [x, Math.cbrt(x), Math.cbrt(x)**3] @@ -578,8 +607,8 @@ math_cbrt(mrb_state *mrb, mrb_value obj) * Math.frexp(numeric) -> [ fraction, exponent ] * * Returns a two-element array containing the normalized fraction (a - * Float) and exponent (a Integer) of - * numeric. + * `Float`) and exponent (a `Integer`) of + * *numeric*. * * fraction, exponent = Math.frexp(1234) #=> [0.6025390625, 11] * fraction * 2**exponent #=> 1234.0 @@ -599,7 +628,7 @@ math_frexp(mrb_state *mrb, mrb_value obj) * call-seq: * Math.ldexp(flt, int) -> float * - * Returns the value of flt*(2**int). + * Returns the value of *flt**(2***int*). * * fraction, exponent = Math.frexp(1234) * Math.ldexp(fraction, exponent) #=> 1234.0 @@ -621,7 +650,7 @@ math_ldexp(mrb_state *mrb, mrb_value obj) * Math.hypot(x, y) -> float * * Returns sqrt(x**2 + y**2), the hypotenuse of a right-angled triangle - * with sides x and y. + * with sides *x* and *y*. * * Math.hypot(3, 4) #=> 5.0 */ @@ -667,54 +696,55 @@ math_erfc(mrb_state *mrb, mrb_value obj) void mrb_mruby_math_gem_init(mrb_state* mrb) { - struct RClass *mrb_math; - mrb_math = mrb_define_module(mrb, "Math"); + struct RClass *math = mrb_define_module_id(mrb, MRB_SYM(Math)); - mrb_define_class_under_id(mrb, mrb_math, MRB_SYM(DomainError), E_STANDARD_ERROR); + mrb_define_class_under_id(mrb, math, MRB_SYM(DomainError), E_STANDARD_ERROR); #ifdef M_PI - mrb_define_const_id(mrb, mrb_math, MRB_SYM(PI), mrb_float_value(mrb, M_PI)); + mrb_define_const_id(mrb, math, MRB_SYM(PI), mrb_float_value(mrb, M_PI)); #else - mrb_define_const_id(mrb, mrb_math, MRB_SYM(PI), mrb_float_value(mrb, atan(1.0)*4.0)); + mrb_define_const_id(mrb, math, MRB_SYM(PI), mrb_float_value(mrb, atan(1.0)*4.0)); #endif #ifdef M_E - mrb_define_const_id(mrb, mrb_math, MRB_SYM(E), mrb_float_value(mrb, M_E)); + mrb_define_const_id(mrb, math, MRB_SYM(E), mrb_float_value(mrb, M_E)); #else - mrb_define_const_id(mrb, mrb_math, MRB_SYM(E), mrb_float_value(mrb, exp(1.0))); + mrb_define_const_id(mrb, math, MRB_SYM(E), mrb_float_value(mrb, exp(1.0))); #endif - mrb_define_module_function(mrb, mrb_math, "sin", math_sin, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "cos", math_cos, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "tan", math_tan, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(sin), math_sin, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(cos), math_cos, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(tan), math_tan, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "asin", math_asin, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "acos", math_acos, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "atan", math_atan, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "atan2", math_atan2, MRB_ARGS_REQ(2)); + mrb_define_module_function_id(mrb, math, MRB_SYM(asin), math_asin, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(acos), math_acos, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(atan), math_atan, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(atan2), math_atan2, MRB_ARGS_REQ(2)); - mrb_define_module_function(mrb, mrb_math, "sinh", math_sinh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "cosh", math_cosh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "tanh", math_tanh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(sinh), math_sinh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(cosh), math_cosh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(tanh), math_tanh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "asinh", math_asinh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "acosh", math_acosh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "atanh", math_atanh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(asinh), math_asinh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(acosh), math_acosh, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(atanh), math_atanh, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "exp", math_exp, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "log", math_log, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); - mrb_define_module_function(mrb, mrb_math, "log2", math_log2, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "log10", math_log10, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "sqrt", math_sqrt, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "cbrt", math_cbrt, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(exp), math_exp, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(expm1), math_expm1, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(log1p), math_log1p, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(log), math_log, MRB_ARGS_REQ(1)|MRB_ARGS_OPT(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(log2), math_log2, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(log10), math_log10, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(sqrt), math_sqrt, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(cbrt), math_cbrt, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "frexp", math_frexp, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "ldexp", math_ldexp, MRB_ARGS_REQ(2)); + mrb_define_module_function_id(mrb, math, MRB_SYM(frexp), math_frexp, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(ldexp), math_ldexp, MRB_ARGS_REQ(2)); - mrb_define_module_function(mrb, mrb_math, "hypot", math_hypot, MRB_ARGS_REQ(2)); + mrb_define_module_function_id(mrb, math, MRB_SYM(hypot), math_hypot, MRB_ARGS_REQ(2)); - mrb_define_module_function(mrb, mrb_math, "erf", math_erf, MRB_ARGS_REQ(1)); - mrb_define_module_function(mrb, mrb_math, "erfc", math_erfc, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(erf), math_erf, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, math, MRB_SYM(erfc), math_erfc, MRB_ARGS_REQ(1)); } void diff --git a/mrbgems/mruby-metaprog/src/metaprog.c b/mrbgems/mruby-metaprog/src/metaprog.c index 7d4bd8f74..7df9a5ce4 100644 --- a/mrbgems/mruby-metaprog/src/metaprog.c +++ b/mrbgems/mruby-metaprog/src/metaprog.c @@ -25,8 +25,8 @@ mrb_f_nil(mrb_state *mrb, mrb_value cv) * call-seq: * obj.instance_variable_defined?(symbol) -> true or false * - * Returns true if the given instance variable is - * defined in obj. + * Returns `true` if the given instance variable is + * defined in *obj*. * * class Fred * def initialize(p1, p2) @@ -54,9 +54,9 @@ mrb_obj_ivar_defined(mrb_state *mrb, mrb_value self) * obj.instance_variable_get(symbol) -> obj * * Returns the value of the given instance variable, or nil if the - * instance variable is not set. The @ part of the + * instance variable is not set. The `@` part of the * variable name should be included for regular instance - * variables. Throws a NameError exception if the + * variables. Throws a `NameError` exception if the * supplied symbol is not valid as an instance variable name. * * class Fred @@ -83,8 +83,8 @@ mrb_obj_ivar_get(mrb_state *mrb, mrb_value self) * call-seq: * obj.instance_variable_set(symbol, obj) -> obj * - * Sets the instance variable names by symbol to - * object, thereby frustrating the efforts of the class's + * Sets the instance variable names by *symbol* to + * *object*, thereby frustrating the efforts of the class's * author to attempt to provide proper encapsulation. The variable * did not have to exist prior to this call. * @@ -146,7 +146,7 @@ method_entry_i(mrb_state *mrb, mrb_sym mid, mrb_method_t m, void *p) if (vicheck(m.flags, s->visibility) && kh_get(st, mrb, s->set, mid) == kh_end(s->set)) { khint_t k = kh_put(st, mrb, s->set, mid); - kh_val(s->set, k) = !MRB_METHOD_UNDEF_P(m); + kh_val(st, s->set, k) = !MRB_METHOD_UNDEF_P(m); } return 0; } @@ -182,9 +182,9 @@ mrb_class_instance_method_list(mrb_state *mrb, mrb_bool recur, struct RClass *kl } ary = mrb_ary_new_capa(mrb, kh_size(set)); - KHASH_FOREACH(mrb, set, k) { - if (kh_val(set, k)) { - mrb_ary_push(mrb, ary, mrb_symbol_value(kh_key(set, k))); + KHASH_FOREACH(st, set, k) { + if (kh_val(st, set, k)) { + mrb_ary_push(mrb, ary, mrb_symbol_value(kh_key(st, set, k))); } } kh_destroy(st, mrb, set); @@ -234,8 +234,8 @@ mrb_obj_methods_m(mrb_state *mrb, mrb_value self) * call-seq: * obj.private_methods(all=true) -> array * - * Returns the list of private methods accessible to obj. If - * the all parameter is set to false, only those methods + * Returns the list of private methods accessible to *obj*. If + * the *all* parameter is set to `false`, only those methods * in the receiver will be listed. */ static mrb_value @@ -249,8 +249,8 @@ mrb_obj_private_methods(mrb_state *mrb, mrb_value self) * call-seq: * obj.protected_methods(all=true) -> array * - * Returns the list of protected methods accessible to obj. If - * the all parameter is set to false, only those methods + * Returns the list of protected methods accessible to *obj*. If + * the *all* parameter is set to `false`, only those methods * in the receiver will be listed. */ static mrb_value @@ -264,8 +264,8 @@ mrb_obj_protected_methods(mrb_state *mrb, mrb_value self) * call-seq: * obj.public_methods(all=true) -> array * - * Returns the list of public methods accessible to obj. If - * the all parameter is set to false, only those methods + * Returns the list of public methods accessible to *obj*. If + * the *all* parameter is set to `false`, only those methods * in the receiver will be listed. */ static mrb_value @@ -295,8 +295,8 @@ mrb_obj_singleton_methods(mrb_state *mrb, mrb_bool recur, mrb_value obj) } ary = mrb_ary_new(mrb); - KHASH_FOREACH(mrb, set, k) { - mrb_ary_push(mrb, ary, mrb_symbol_value(kh_key(set, k))); + KHASH_FOREACH(st, set, k) { + mrb_ary_push(mrb, ary, mrb_symbol_value(kh_key(st, set, k))); } kh_destroy(st, mrb, set); @@ -308,9 +308,9 @@ mrb_obj_singleton_methods(mrb_state *mrb, mrb_bool recur, mrb_value obj) * call-seq: * obj.singleton_methods(all=true) -> array * - * Returns an array of the names of singleton methods for obj. - * If the optional all parameter is true, the list will include - * methods in modules included in obj. + * Returns an array of the names of singleton methods for *obj*. + * If the optional *all* parameter is true, the list will include + * methods in modules included in *obj*. * Only public and protected singleton methods are returned. * * module Other @@ -374,7 +374,7 @@ check_cv_name_sym(mrb_state *mrb, mrb_sym id) * call-seq: * remove_class_variable(sym) -> obj * - * Removes the definition of the sym, returning that + * Removes the definition of the *sym*, returning that * constant's value. * * class Dummy @@ -419,8 +419,8 @@ mrb_mod_remove_cvar(mrb_state *mrb, mrb_value mod) * call-seq: * obj.class_variable_defined?(symbol) -> true or false * - * Returns true if the given class variable is defined - * in obj. + * Returns `true` if the given class variable is defined + * in *obj*. * * class Fred * @@foo = 99 @@ -445,7 +445,7 @@ mrb_mod_cvar_defined(mrb_state *mrb, mrb_value mod) * mod.class_variable_get(symbol) -> obj * * Returns the value of the given class variable (or throws a - * NameError exception). The @@ part of the + * `NameError` exception). The `@@` part of the * variable name should be included for regular class variables * * class Fred @@ -469,8 +469,8 @@ mrb_mod_cvar_get(mrb_state *mrb, mrb_value mod) * call-seq: * obj.class_variable_set(symbol, obj) -> obj * - * Sets the class variable names by symbol to - * object. + * Sets the class variable names by *symbol* to + * *object*. * * class Fred * @@foo = 99 @@ -532,9 +532,9 @@ mod_instance_methods(mrb_state *mrb, mrb_value mod, unsigned int visibility) * Returns an array containing the names of the public and protected instance * methods in the receiver. For a module, these are the public and protected methods; * for a class, they are the instance (not singleton) methods. With no - * argument, or with an argument that is false, the - * instance methods in mod are returned, otherwise the methods - * in mod and mod's superclasses are returned. + * argument, or with an argument that is `false`, the + * instance methods in *mod* are returned, otherwise the methods + * in *mod* and *mod*'s superclasses are returned. * * module A * def method1() end @@ -615,7 +615,7 @@ mrb_mod_undefined_methods(mrb_state *mrb, mrb_value mod) * remove_method(symbol) -> self * * Removes the method identified by _symbol_ from the current - * class. For an example, see Module.undef_method. + * class. For an example, see `Module.undef_method`. */ static mrb_value diff --git a/mrbgems/mruby-method/mrblib/method.rb b/mrbgems/mruby-method/mrblib/method.rb index 09b611eb7..efb1d5b35 100644 --- a/mrbgems/mruby-method/mrblib/method.rb +++ b/mrbgems/mruby-method/mrblib/method.rb @@ -1,4 +1,23 @@ class Method + # + # call-seq: + # meth.to_proc -> proc + # + # Returns a Proc object corresponding to this method. + # + # class Foo + # def bar + # "baz" + # end + # end + # + # m = Foo.new.method(:bar) + # p = m.to_proc + # p.call #=> "baz" + # + # # Can be used with &: + # %w[hello world].map(&:upcase) #=> ["HELLO", "WORLD"] + # def to_proc m = self lambda { |*args, **opts, &b| @@ -6,10 +25,48 @@ class Method } end + # + # call-seq: + # meth << other_proc -> proc + # + # Returns a proc that is the composition of this method and the given + # other_proc. The returned proc takes a variable number of arguments, + # calls other_proc with them then calls this method with the result. + # + # def f(x) + # x * x + # end + # + # def g(x) + # x + x + # end + # + # # (f << g).call(2) == f(g(2)) == f(4) == 16 + # p (method(:f) << method(:g)).call(2) #=> 16 + # def <<(other) ->(*args, **opts, &block) { call(other.call(*args, **opts, &block)) } end + # + # call-seq: + # meth >> other_proc -> proc + # + # Returns a proc that is the composition of this method and the given + # other_proc. The returned proc takes a variable number of arguments, + # calls this method with them then calls other_proc with the result. + # + # def f(x) + # x * x + # end + # + # def g(x) + # x + x + # end + # + # # (f >> g).call(2) == g(f(2)) == g(4) == 8 + # p (method(:f) >> method(:g)).call(2) #=> 8 + # def >>(other) ->(*args, **opts, &block) { other.call(call(*args, **opts, &block)) } end diff --git a/mrbgems/mruby-numeric-ext/README.md b/mrbgems/mruby-numeric-ext/README.md index b3bbdcdb5..c5fcc2279 100644 --- a/mrbgems/mruby-numeric-ext/README.md +++ b/mrbgems/mruby-numeric-ext/README.md @@ -21,6 +21,7 @@ This gem adds the following methods: - `allbits?(mask)`: Returns `true` if all bits of `self & mask` are 1. - `anybits?(mask)`: Returns `true` if any bits of `self & mask` are 1. - `nobits?(mask)`: Returns `true` if no bits of `self & mask` are 1. +- `bit_length`: Returns the number of bits of the absolute value of `self` in binary. `0.bit_length #=> 0`; `(-1).bit_length #=> 0`; `2.bit_length #=> 2`. - `ceildiv(other)`: Returns the result of `self` divided by `other`, rounded up to the nearest integer. - `integer?`: Returns `true` (overrides `Numeric#integer?`). - `remainder(numeric)`: Returns the remainder of `self` divided by `numeric`. Equivalent to `x - y * (x / y).truncate`. @@ -69,6 +70,7 @@ p 12345.digits # => [5, 4, 3, 2, 1] p 12345.digits(16) # => [9, 3, 0, 3] p 42.size # => (depends on machine, e.g., 4 or 8) +p 5.bit_length # => 3 p 5.odd? # => true p 4.even? # => true p Integer.sqrt(16) # => 4 diff --git a/mrbgems/mruby-numeric-ext/mrblib/numeric_ext.rb b/mrbgems/mruby-numeric-ext/mrblib/numeric_ext.rb index d50e8838c..c121fdec2 100644 --- a/mrbgems/mruby-numeric-ext/mrblib/numeric_ext.rb +++ b/mrbgems/mruby-numeric-ext/mrblib/numeric_ext.rb @@ -3,7 +3,7 @@ class Numeric # call-seq: # zero? -> true or false # - # Returns +true+ if +zero+ has a zero value, +false+ otherwise. + # Returns `true` if `zero` has a zero value, `false` otherwise. # # Of the Core and Standard Library classes, # only Rational and Complex use this implementation. @@ -16,8 +16,8 @@ class Numeric # call-seq: # nonzero? -> self or nil # - # Returns +self+ if +self+ is not a zero value, +nil+ otherwise; - # uses method zero? for the evaluation. + # Returns `self` if `self` is not a zero value, `nil` otherwise; + # uses method `zero?` for the evaluation. # def nonzero? if self == 0 @@ -31,7 +31,7 @@ class Numeric # call-seq: # positive? -> true or false # - # Returns +true+ if +self+ is greater than 0, +false+ otherwise. + # Returns `true` if `self` is greater than 0, `false` otherwise. # def positive? self > 0 @@ -41,7 +41,7 @@ class Numeric # call-seq: # negative? -> true or false # - # Returns +true+ if +self+ is less than 0, +false+ otherwise. + # Returns `true` if `self` is less than 0, `false` otherwise. # def negative? self < 0 @@ -66,7 +66,7 @@ class Integer # call-seq: # int.allbits?(mask) -> true or false # - # Returns +true+ if all bits of +int+ & +mask+ are 1. + # Returns `true` if all bits of ``int` & `mask`` are 1. # def allbits?(mask) (self & mask) == mask @@ -76,7 +76,7 @@ class Integer # call-seq: # int.anybits?(mask) -> true or false # - # Returns +true+ if any bits of +int+ & +mask+ are 1. + # Returns `true` if any bits of ``int` & `mask`` are 1. # def anybits?(mask) (self & mask) != 0 @@ -86,7 +86,7 @@ class Integer # call-seq: # int.nobits?(mask) -> true or false # - # Returns +true+ if no bits of +int+ & +mask+ are 1. + # Returns `true` if no bits of ``int` & `mask`` are 1. # def nobits?(mask) (self & mask) == 0 @@ -95,7 +95,7 @@ class Integer # call-seq: # ceildiv(other) -> integer # - # Returns the result of division +self+ by +other+. The + # Returns the result of division `self` by `other`. The # result is rounded up to the nearest integer. # # 3.ceildiv(3) # => 1 @@ -110,6 +110,15 @@ class Integer -div(-other) end + ## + # call-seq: + # int.integer? -> true + # + # Returns true since this is an Integer. + # + # 1.integer? #=> true + # 42.integer? #=> true + # def integer? true end diff --git a/mrbgems/mruby-numeric-ext/src/numeric_ext.c b/mrbgems/mruby-numeric-ext/src/numeric_ext.c index 445464438..e607eb89a 100644 --- a/mrbgems/mruby-numeric-ext/src/numeric_ext.c +++ b/mrbgems/mruby-numeric-ext/src/numeric_ext.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -8,11 +9,45 @@ static mrb_value flo_remainder(mrb_state *mrb, mrb_value self); #endif +/* + * call-seq: + * int.bit_length -> integer + * + * Returns the number of bits of the absolute value of self in binary representation. + * For zero, returns 0. For negative integers, behaves as (~self).bit_length + * (e.g., (-1).bit_length => 0, (-2).bit_length => 1). + */ +static mrb_value +int_bit_length(mrb_state *mrb, mrb_value self) +{ +#ifdef MRB_USE_BIGINT + if (mrb_bigint_p(self)) { + mrb_int sign = mrb_bint_sign(mrb, self); + if (sign == 0) return mrb_fixnum_value(0); + mrb_value v = self; + if (sign < 0) v = mrb_bint_rev(mrb, self); /* ~self = -self-1 */ + mrb_value s = mrb_bint_to_s(mrb, v, 2); + return mrb_int_value(mrb, (mrb_int)RSTRING_LEN(s)); + } +#endif + mrb_int x = mrb_integer(self); + if (x == 0) return mrb_fixnum_value(0); + + /* for negative fixnums, use ~x */ + mrb_uint ux = (mrb_uint)(x < 0 ? ~x : x); + mrb_int bits = 0; + while (ux) { + bits++; + ux >>= 1; + } + return mrb_int_value(mrb, bits); +} + /* * call-seq: * num.remainder(numeric) -> real * - * x.remainder(y) means x-y*(x/y).truncate. + * `x.remainder(y)` means `x-y*(x/y).truncate`. * * See Numeric#divmod. */ @@ -46,6 +81,90 @@ int_remainder(mrb_state *mrb, mrb_value x) 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; + } + + return x; +} + +/* + * call-seq: + * int.gcd(other_int) -> integer + * + * Returns the greatest common divisor of the two integers. + * The result is always positive. + */ +static mrb_value +int_gcd(mrb_state *mrb, mrb_value x) +{ + mrb_value y = mrb_get_arg1(mrb); + +#ifdef MRB_USE_BIGINT + if (mrb_bigint_p(x) || mrb_bigint_p(y)) { + if (!mrb_integer_p(y) && !mrb_bigint_p(y)) { + mrb_raisef(mrb, E_TYPE_ERROR, "can't convert %Y into Integer", y); + } + if (!mrb_bigint_p(x)) x = mrb_bint_new_int(mrb, mrb_integer(x)); + if (!mrb_bigint_p(y)) y = mrb_bint_new_int(mrb, mrb_integer(y)); + return mrb_bint_gcd(mrb, x, y); + } +#endif + + 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))); +} + +/* + * call-seq: + * int.lcm(other_int) -> integer + * + * Returns the least common multiple of the two integers. + * The result is always positive. + */ +static mrb_value +int_lcm(mrb_state *mrb, mrb_value x) +{ + mrb_value y = mrb_get_arg1(mrb); + mrb_int a, b, gcd_val; + +#ifdef MRB_USE_BIGINT + if (mrb_bigint_p(x) || mrb_bigint_p(y)) { + if (!mrb_integer_p(y) && !mrb_bigint_p(y)) { + mrb_raisef(mrb, E_TYPE_ERROR, "can't convert %Y into Integer", y); + } + if (!mrb_bigint_p(x)) x = mrb_bint_new_int(mrb, mrb_integer(x)); + if (!mrb_bigint_p(y)) y = mrb_bint_new_int(mrb, mrb_integer(y)); + return mrb_bint_lcm(mrb, x, y); + } +#endif + + if (!mrb_integer_p(y)) { + mrb_raisef(mrb, E_TYPE_ERROR, "can't convert %Y into Integer", y); + } + + a = mrb_integer(x); + b = mrb_integer(y); + + if (a == 0 || b == 0) return mrb_int_value(mrb, 0); + + gcd_val = mrb_int_gcd(a, b); + if (a < 0) a = -a; + if (b < 0) b = -b; + + return mrb_int_value(mrb, (a / gcd_val) * b); +} + /* * call-seq: * integer.pow(numeric) -> numeric @@ -125,15 +244,15 @@ int_powm(mrb_state *mrb, mrb_value x) * call-seq: * digits(base = 10) -> array_of_integers * - * Returns an array of integers representing the +base+-radix - * digits of +self+; + * Returns an array of integers representing the `base`-radix + * digits of `self`; * the first element of the array represents the least significant digit: * * 12345.digits # => [5, 4, 3, 2, 1] * 12345.digits(7) # => [4, 6, 6, 0, 5] * 12345.digits(100) # => [45, 23, 1] * - * Raises an exception if +self+ is negative or +base+ is less than 2. + * Raises an exception if `self` is negative or `base` is less than 2. * */ @@ -231,7 +350,7 @@ int_size(mrb_state *mrb, mrb_value self) * call-seq: * int.even? -> true or false * - * Returns +true+ if +int+ is an even number. + * Returns `true` if `int` is an even number. */ static mrb_value int_even(mrb_state *mrb, mrb_value self) @@ -250,7 +369,7 @@ int_even(mrb_state *mrb, mrb_value self) * call-seq: * int.odd? -> true or false * - * Returns +true+ if +int+ is an odd number. + * Returns `true` if `int` is an odd number. */ static mrb_value int_odd(mrb_state *mrb, mrb_value self) @@ -265,7 +384,7 @@ int_odd(mrb_state *mrb, mrb_value self) * call-seq: * num.remainder(numeric) -> real * - * x.remainder(y) means x-y*(x/y).truncate. + * `x.remainder(y)` means `x-y*(x/y).truncate`. * * See Numeric#divmod. */ @@ -309,7 +428,7 @@ isqrt(mrb_int n) * call-seq: * Integer.sqrt(n) -> integer * - * Returns the integer square root of the non-negative integer +n+, + * Returns the integer square root of the non-negative integer `n`, * which is the largest integer `i` such that `i*i <= n`. * * Integer.sqrt(0) # => 0 @@ -351,8 +470,11 @@ mrb_mruby_numeric_ext_gem_init(mrb_state* mrb) mrb_define_method_id(mrb, ic, MRB_SYM(pow), int_powm, MRB_ARGS_ARG(1,1)); mrb_define_method_id(mrb, ic, MRB_SYM(digits), int_digits, MRB_ARGS_OPT(1)); mrb_define_method_id(mrb, ic, MRB_SYM(size), int_size, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, ic, MRB_SYM(bit_length), int_bit_length, MRB_ARGS_NONE()); mrb_define_method_id(mrb, ic, MRB_SYM_Q(odd), int_odd, MRB_ARGS_NONE()); mrb_define_method_id(mrb, ic, MRB_SYM_Q(even), int_even, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, ic, MRB_SYM(gcd), int_gcd, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, ic, MRB_SYM(lcm), int_lcm, MRB_ARGS_REQ(1)); mrb_define_class_method_id(mrb, ic, MRB_SYM(sqrt), int_sqrt, MRB_ARGS_REQ(1)); #ifndef MRB_NO_FLOAT diff --git a/mrbgems/mruby-numeric-ext/test/numeric.rb b/mrbgems/mruby-numeric-ext/test/numeric.rb index bb4c461d6..08eb4e9d0 100644 --- a/mrbgems/mruby-numeric-ext/test/numeric.rb +++ b/mrbgems/mruby-numeric-ext/test/numeric.rb @@ -26,6 +26,30 @@ assert('Integer#pow') do assert_equal(361, 9.pow(1024,1000)) end +assert('Integer#gcd') do + assert_equal(1, 2.gcd(3)) + assert_equal(5, 10.gcd(15)) + assert_equal(6, 24.gcd(18)) + assert_equal(7, 7.gcd(0)) + assert_equal(7, 0.gcd(7)) + assert_equal(0, 0.gcd(0)) + assert_equal(5, (-10).gcd(15)) + assert_equal(5, 10.gcd(-15)) + assert_equal(5, (-10).gcd(-15)) +end + +assert('Integer#lcm') do + assert_equal(6, 2.lcm(3)) + assert_equal(30, 10.lcm(15)) + assert_equal(72, 24.lcm(18)) + assert_equal(0, 7.lcm(0)) + assert_equal(0, 0.lcm(7)) + assert_equal(0, 0.lcm(0)) + assert_equal(30, (-10).lcm(15)) + assert_equal(30, 10.lcm(-15)) + assert_equal(30, (-10).lcm(-15)) +end + assert('Integer#ceildiv') do assert_equal(0, 0.ceildiv(3)) assert_equal(1, 1.ceildiv(3)) @@ -87,3 +111,27 @@ assert('Integer.sqrt') do assert_equal(10, Integer.sqrt(100)) assert_equal(85, Integer.sqrt(7244)) end + +assert('Integer#bit_length') do + # zero + assert_equal 0, 0.bit_length + + # positives + assert_equal 1, 1.bit_length + assert_equal 2, 2.bit_length + assert_equal 2, 3.bit_length + assert_equal 3, 4.bit_length + assert_equal 3, 5.bit_length + + # negatives (use ~n semantics) + assert_equal 0, (-1).bit_length + assert_equal 1, (-2).bit_length + assert_equal 2, (-3).bit_length + assert_equal 2, (-4).bit_length + assert_equal 3, (-5).bit_length + + # bigint cases may be enabled depending on config + # assert_equal 100, (2**100 - 1).bit_length + # assert_equal 101, (2**100).bit_length + # assert_equal 0, (-1).bit_length +end diff --git a/mrbgems/mruby-object-ext/mrblib/object.rb b/mrbgems/mruby-object-ext/mrblib/object.rb index f014df469..e7e60692e 100644 --- a/mrbgems/mruby-object-ext/mrblib/object.rb +++ b/mrbgems/mruby-object-ext/mrblib/object.rb @@ -3,7 +3,7 @@ module Kernel # obj.yield_self {|_obj|...} -> an_object # obj.then {|_obj|...} -> an_object # - # Yields obj and returns the result. + # Yields *obj* and returns the result. # # 'my string'.yield_self {|s|s.upcase} #=> "MY STRING" # @@ -17,7 +17,7 @@ module Kernel # call-seq: # obj.tap{|x|...} -> obj # - # Yields x to the block, and then returns x. + # Yields `x` to the block, and then returns `x`. # The primary purpose of this method is to "tap into" a method chain, # in order to perform operations on intermediate results within the chain. # diff --git a/mrbgems/mruby-object-ext/src/object.c b/mrbgems/mruby-object-ext/src/object.c index 4efb96edf..0aeba480e 100644 --- a/mrbgems/mruby-object-ext/src/object.c +++ b/mrbgems/mruby-object-ext/src/object.c @@ -66,7 +66,7 @@ nil_to_i(mrb_state *mrb, mrb_value obj) * call-seq: * obj.itself -> an_object * - * Returns obj. + * Returns *obj*. * * string = 'my string' #=> "my string" * string.itself.object_id == string.object_id #=> true @@ -78,7 +78,7 @@ nil_to_i(mrb_state *mrb, mrb_value obj) * obj.instance_exec(arg...) {|var...| block } -> obj * * Executes the given block within the context of the receiver - * (_obj_). In order to set the context, the variable +self+ is set + * (_obj_). In order to set the context, the variable `self` is set * to _obj_ while the code is executing, giving the code access to * _obj_'s instance variables. Arguments are passed as block parameters. * diff --git a/mrbgems/mruby-objectspace/src/mruby_objectspace.c b/mrbgems/mruby-objectspace/src/mruby_objectspace.c index 4c7708111..0d30e7f64 100644 --- a/mrbgems/mruby-objectspace/src/mruby_objectspace.c +++ b/mrbgems/mruby-objectspace/src/mruby_objectspace.c @@ -50,7 +50,7 @@ os_count_object_type(mrb_state *mrb, struct RBasic *obj, void *data) * # ... * } * - * If the optional argument +result_hash+ is given, + * If the optional argument `result_hash` is given, * it is overwritten and returned. This is intended to avoid probe effect. * */ @@ -157,9 +157,9 @@ os_each_object_cb(mrb_state *mrb, struct RBasic *obj, void *ud) * * Calls the block once for each object in this Ruby process. * Returns the number of objects found. - * If the optional argument +module+ is given, + * If the optional argument `module` is given, * calls the block for only those classes or modules - * that match (or are a subclass of) +module+. + * that match (or are a subclass of) `module`. * * If no block is given, ArgumentError is raised. * diff --git a/mrbgems/mruby-pack/README.md b/mrbgems/mruby-pack/README.md index 96caac16a..9ecd2e080 100644 --- a/mrbgems/mruby-pack/README.md +++ b/mrbgems/mruby-pack/README.md @@ -53,6 +53,13 @@ arr2 = binary_data2.unpack("C3") # Unpacks three 8-bit unsigned integers binary_data3 = "Hello\x00World" arr3 = binary_data3.unpack("Z*Z*") # Unpacks two null-terminated strings # arr3 will be ["Hello", "World"] + +# BER (Basic Encoding Rules) example +arr4 = [127, 128, 16383] +binary_data4 = arr4.pack("w*") # BER-encodes integers of varying sizes +# binary_data4 will contain BER-compressed data +arr4_unpacked = binary_data4.unpack("w*") # Decodes BER data back to integers +# arr4_unpacked will be [127, 128, 16383] ``` ## Supported Template Directives @@ -90,9 +97,11 @@ Here is a list of supported template characters and their meanings: | `q` | 64-bit signed integer, native endian (`int64_t`) | | `S` | 16-bit unsigned integer, native endian (`uint16_t`) (Use `S>` for big-endian, `S<` for little-endian) | | `s` | 16-bit signed integer, native endian (`int16_t`) (Use `s>` for big-endian, `s<` for little-endian) | +| `u` | UU-encoded string (Unix-to-Unix encoding) | | `U` | UTF-8 character | | `V` | 32-bit unsigned integer, VAX (little-endian) byte order | | `v` | 16-bit unsigned integer, VAX (little-endian) byte order | +| `w` | BER-compressed integer (variable length encoding) | | `x` | Null byte (skip forward one byte) | | `X` | Back up one byte | | `Z` | Null-terminated string (when unpacking, reads until NULL; when packing, appends a NULL if count is `*`) | diff --git a/mrbgems/mruby-pack/src/pack.c b/mrbgems/mruby-pack/src/pack.c index 645900397..4342c87d4 100644 --- a/mrbgems/mruby-pack/src/pack.c +++ b/mrbgems/mruby-pack/src/pack.c @@ -42,6 +42,7 @@ enum pack_dir { PACK_DIR_HEX, /* h */ PACK_DIR_BSTR, /* b */ PACK_DIR_BASE64, /* m */ + PACK_DIR_UU, /* u */ PACK_DIR_QENC, /* M */ PACK_DIR_NUL, /* x */ PACK_DIR_BACK, /* X */ @@ -89,17 +90,263 @@ static const unsigned char base64_dec_tab[128] = { 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, IGN, IGN, IGN, IGN, IGN, }; +/* lookup table for hex character to integer conversion */ +static const signed char hex_lookup[256] = { + /* 0x00-0x0F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0x10-0x1F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0x20-0x2F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0x30-0x3F */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + /* 0x40-0x4F */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0x50-0x5F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0x60-0x6F */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0x70-0x7F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0x80-0x8F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0x90-0x9F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0xA0-0xAF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0xB0-0xBF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0xC0-0xCF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0xD0-0xDF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0xE0-0xEF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + /* 0xF0-0xFF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; +/* byte index arrays for endianness optimization */ +static const int be_idx16[2] = {1, 0}; /* big-endian 16-bit: MSB, LSB */ +static const int le_idx16[2] = {0, 1}; /* little-endian 16-bit: LSB, MSB */ +static const int be_idx32[4] = {3, 2, 1, 0}; /* big-endian 32-bit: MSB...LSB */ +static const int le_idx32[4] = {0, 1, 2, 3}; /* little-endian 32-bit: LSB...MSB */ +static const int be_idx64[8] = {7, 6, 5, 4, 3, 2, 1, 0}; /* big-endian 64-bit: MSB...LSB */ +static const int le_idx64[8] = {0, 1, 2, 3, 4, 5, 6, 7}; /* little-endian 64-bit: LSB...MSB */ /* little-endian 64-bit */ +/* lookup tables for binary string optimization */ +/* Convert character to bit value (0 or 1) */ +static inline uint8_t char_to_bit(unsigned char c) { + switch (c) { + case '0': return 0; + case '1': return 1; + default: return 0; + } +} + +static const char bit_to_char[2] = {'0', '1'}; + +/* 8-bit batch processing functions for binary strings */ +static inline uint8_t +pack_8_bits_msb(const char *src) +{ + uint8_t result = 0; + result |= char_to_bit((uint8_t)src[0]) << 7; + result |= char_to_bit((uint8_t)src[1]) << 6; + result |= char_to_bit((uint8_t)src[2]) << 5; + result |= char_to_bit((uint8_t)src[3]) << 4; + result |= char_to_bit((uint8_t)src[4]) << 3; + result |= char_to_bit((uint8_t)src[5]) << 2; + result |= char_to_bit((uint8_t)src[6]) << 1; + result |= char_to_bit((uint8_t)src[7]); + return result; +} + +static inline uint8_t +pack_8_bits_lsb(const char *src) +{ + uint8_t result = 0; + result |= char_to_bit((uint8_t)src[0]); + result |= char_to_bit((uint8_t)src[1]) << 1; + result |= char_to_bit((uint8_t)src[2]) << 2; + result |= char_to_bit((uint8_t)src[3]) << 3; + result |= char_to_bit((uint8_t)src[4]) << 4; + result |= char_to_bit((uint8_t)src[5]) << 5; + result |= char_to_bit((uint8_t)src[6]) << 6; + result |= char_to_bit((uint8_t)src[7]) << 7; + return result; +} + +static inline void +unpack_8_bits_msb(uint8_t byte, char *dst) +{ + dst[0] = bit_to_char[(byte >> 7) & 1]; + dst[1] = bit_to_char[(byte >> 6) & 1]; + dst[2] = bit_to_char[(byte >> 5) & 1]; + dst[3] = bit_to_char[(byte >> 4) & 1]; + dst[4] = bit_to_char[(byte >> 3) & 1]; + dst[5] = bit_to_char[(byte >> 2) & 1]; + dst[6] = bit_to_char[(byte >> 1) & 1]; + dst[7] = bit_to_char[byte & 1]; +} + +static inline void +unpack_8_bits_lsb(uint8_t byte, char *dst) +{ + dst[0] = bit_to_char[byte & 1]; + dst[1] = bit_to_char[(byte >> 1) & 1]; + dst[2] = bit_to_char[(byte >> 2) & 1]; + dst[3] = bit_to_char[(byte >> 3) & 1]; + dst[4] = bit_to_char[(byte >> 4) & 1]; + dst[5] = bit_to_char[(byte >> 5) & 1]; + dst[6] = bit_to_char[(byte >> 6) & 1]; + dst[7] = bit_to_char[(byte >> 7) & 1]; +} + +/* character classification for string format optimization */ +#define CHAR_NULL 0x01 +#define CHAR_SPACE 0x02 + +/* Character classification function */ +static inline uint8_t char_class(unsigned char c) { + switch (c) { + case '\0': return CHAR_NULL; + case ' ': + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': return CHAR_SPACE; + default: return 0; + } +} +/* UTF-8 optimization lookup tables */ +/* UTF-8 sequence length lookup table for non-ASCII bytes (0x80-0xFF) */ +/* Index = byte_value - 0x80, so table[0] = info for byte 0x80 */ +static const uint8_t utf8_seq_len_high[128] = { + /* 0x80-0xBF: Invalid start bytes (continuation bytes) - return 0 for error */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + + /* 0xC0-0xDF: 2-byte sequences */ + 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, + + /* 0xE0-0xEF: 3-byte sequences */ + 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, + + /* 0xF0-0xF7: 4-byte sequences */ + 4,4,4,4,4,4,4,4, + + /* 0xF8-0xFF: Invalid start bytes - return 0 for error */ + 0,0,0,0,0,0,0,0 +}; + +/* Fast validation for UTF-8 continuation bytes (0x80-0xBF) */ +/* Index = byte_value - 0x80 */ +static const uint8_t utf8_is_continuation[128] = { + /* 0x80-0xBF: Valid continuation bytes */ + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + + /* 0xC0-0xFF: Invalid continuation bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 +}; + +/* Helper macro for continuation byte validation */ +#define IS_UTF8_CONTINUATION(byte) \ + ((byte) >= 0x80 && utf8_is_continuation[(byte) - 0x80]) + +/* Quoted-printable optimization lookup table */ +/* 0=literal, 1=encode, 2=special (newline) */ +static const uint8_t qprint_encode_type[256] = { + /* 0x00-0x1F: Control characters - encode */ + 1,1,1,1,1,1,1,1, 1,0,2,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + /* \t \n */ + + /* 0x20-0x3F: Printable ASCII */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,1,0,0, + /* = */ + + /* 0x40-0x5F: Printable ASCII */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + + /* 0x60-0x7F: Printable ASCII */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,1, + /* DEL*/ + + /* 0x80-0xFF: High-bit characters - encode */ + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1 +}; + +/* UU-encoding optimization lookup tables */ +/* UU-encoding uses 6-bit values mapped to ASCII 32-95, but space (32) -> backtick (96) */ +static const char uu_encode_table[64] = { + '`', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_' +}; + +/* UU-decoding lookup table for ASCII 32-127 */ +static const int8_t uu_decode_table[96] = { + /* ASCII 32-47: ` ! " # $ % & ' ( ) * + , - . / */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + /* ASCII 48-63: 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + /* ASCII 64-79: @ A B C D E F G H I J K L M N O */ + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + /* ASCII 80-95: P Q R S T U V W X Y Z [ \ ] ^ _ */ + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + /* ASCII 96-127: ` a b c... (invalid for UU, but ` = 0 for decoding) */ + 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +/* template parsing optimization structures */ +typedef struct { + enum pack_dir dir; + enum pack_type type; + int size; + unsigned int base_flags; +} format_info_t; + +/* Format character lookup function */ +static inline format_info_t get_format_info(unsigned char c) { + switch (c) { + case 'A': return (format_info_t){PACK_DIR_STR, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2}; + case 'a': return (format_info_t){PACK_DIR_STR, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2 | PACK_FLAG_a}; + case 'B': return (format_info_t){PACK_DIR_BSTR, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2}; + case 'b': return (format_info_t){PACK_DIR_BSTR, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2 | PACK_FLAG_LSB}; + case 'C': return (format_info_t){PACK_DIR_CHAR, PACK_TYPE_INTEGER, 1, 0}; + case 'c': return (format_info_t){PACK_DIR_CHAR, PACK_TYPE_INTEGER, 1, PACK_FLAG_SIGNED}; + case 'D': return (format_info_t){PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED}; + case 'd': return (format_info_t){PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED}; + case 'E': return (format_info_t){PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED | PACK_FLAG_LT}; + case 'e': return (format_info_t){PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED | PACK_FLAG_LT}; + case 'F': return (format_info_t){PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED}; + case 'f': return (format_info_t){PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED}; + case 'G': return (format_info_t){PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED | PACK_FLAG_GT}; + case 'g': return (format_info_t){PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED | PACK_FLAG_GT}; + case 'H': return (format_info_t){PACK_DIR_HEX, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2}; + case 'h': return (format_info_t){PACK_DIR_HEX, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2 | PACK_FLAG_LSB}; + /* I, i, J, j are handled specially based on sizeof() */ + case 'L': return (format_info_t){PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, 0}; + case 'l': return (format_info_t){PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, PACK_FLAG_SIGNED}; + case 'M': return (format_info_t){PACK_DIR_QENC, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2}; + case 'm': return (format_info_t){PACK_DIR_BASE64, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2}; + case 'N': return (format_info_t){PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, PACK_FLAG_GT}; + case 'n': return (format_info_t){PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, PACK_FLAG_GT}; + case 'Q': return (format_info_t){PACK_DIR_QUAD, PACK_TYPE_INTEGER, 8, 0}; + case 'q': return (format_info_t){PACK_DIR_QUAD, PACK_TYPE_INTEGER, 8, PACK_FLAG_SIGNED}; + case 'S': return (format_info_t){PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, 0}; + case 's': return (format_info_t){PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, PACK_FLAG_SIGNED}; + case 'u': return (format_info_t){PACK_DIR_UU, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2}; + case 'U': return (format_info_t){PACK_DIR_UTF8, PACK_TYPE_INTEGER, 0, 0}; + case 'V': return (format_info_t){PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, PACK_FLAG_LT}; + case 'v': return (format_info_t){PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, PACK_FLAG_LT}; + case 'w': return (format_info_t){PACK_DIR_BER, PACK_TYPE_INTEGER, 0, PACK_FLAG_SIGNED}; + case 'x': return (format_info_t){PACK_DIR_NUL, PACK_TYPE_NONE, 0, 0}; + case 'X': return (format_info_t){PACK_DIR_BACK, PACK_TYPE_NONE, 0, 0}; + case '@': return (format_info_t){PACK_DIR_ABS, PACK_TYPE_NONE, 0, 0}; + case 'Z': return (format_info_t){PACK_DIR_STR, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2 | PACK_FLAG_Z}; + default: return (format_info_t){PACK_DIR_NONE, PACK_TYPE_NONE, 0, 0}; + } +} + + +#define IS_PADDING_CHAR_A(c) (char_class((unsigned char)(c)) & (CHAR_NULL | CHAR_SPACE)) +#define IS_PADDING_CHAR_Z(c) (char_class((unsigned char)(c)) & CHAR_NULL) + static int hex2int(unsigned char ch) { - if (ch >= '0' && ch <= '9') - return ch - '0'; - else if (ch >= 'A' && ch <= 'F') - return 10 + (ch - 'A'); - else if (ch >= 'a' && ch <= 'f') - return 10 + (ch - 'a'); - else - return -1; + return hex_lookup[ch]; } static mrb_value @@ -128,7 +375,7 @@ pack_char(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int } static int -unpack_char(mrb_state *mrb, const void *src, int srclen, mrb_value ary, unsigned int flags) +unpack_char(mrb_state *mrb, const void *src, mrb_int srclen, mrb_value ary, unsigned int flags) { if (flags & PACK_FLAG_SIGNED) mrb_ary_push(mrb, ary, mrb_fixnum_value(*(signed char*)src)); @@ -140,32 +387,25 @@ unpack_char(mrb_state *mrb, const void *src, int srclen, mrb_value ary, unsigned static int pack_short(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { - uint16_t n; - str = str_len_ensure(mrb, str, sidx + 2); - n = (uint16_t)mrb_integer(o); - if (flags & PACK_FLAG_LITTLEENDIAN) { - RSTRING_PTR(str)[sidx+0] = n % 256; - RSTRING_PTR(str)[sidx+1] = n / 256; - } - else { - RSTRING_PTR(str)[sidx+0] = n / 256; - RSTRING_PTR(str)[sidx+1] = n % 256; - } + uint16_t n = (uint16_t)mrb_integer(o); + char *dptr = RSTRING_PTR(str) + sidx; + + /* use lookup tables to eliminate branching */ + const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx16 : be_idx16; + dptr[idx[0]] = (char)(n & 0xff); + dptr[idx[1]] = (char)(n >> 8); + return 2; } static int -unpack_short(mrb_state *mrb, const unsigned char *src, int srclen, mrb_value ary, unsigned int flags) +unpack_short(mrb_state *mrb, const unsigned char *src, mrb_int srclen, mrb_value ary, unsigned int flags) { - int n; + /* use lookup tables to eliminate branching */ + const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx16 : be_idx16; + int n = (src[idx[1]] << 8) | src[idx[0]]; - if (flags & PACK_FLAG_LITTLEENDIAN) { - n = src[1] * 256 + src[0]; - } - else { - n = src[0] * 256 + src[1]; - } if ((flags & PACK_FLAG_SIGNED) && (n >= 0x8000)) { n -= 0x10000; } @@ -176,22 +416,17 @@ unpack_short(mrb_state *mrb, const unsigned char *src, int srclen, mrb_value ary static int pack_long(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { - uint32_t n; - str = str_len_ensure(mrb, str, sidx + 4); - n = (uint32_t)mrb_integer(o); - if (flags & PACK_FLAG_LITTLEENDIAN) { - RSTRING_PTR(str)[sidx+0] = (char)(n & 0xff); - RSTRING_PTR(str)[sidx+1] = (char)(n >> 8); - RSTRING_PTR(str)[sidx+2] = (char)(n >> 16); - RSTRING_PTR(str)[sidx+3] = (char)(n >> 24); - } - else { - RSTRING_PTR(str)[sidx+0] = (char)(n >> 24); - RSTRING_PTR(str)[sidx+1] = (char)(n >> 16); - RSTRING_PTR(str)[sidx+2] = (char)(n >> 8); - RSTRING_PTR(str)[sidx+3] = (char)(n & 0xff); - } + uint32_t n = (uint32_t)mrb_integer(o); + char *dptr = RSTRING_PTR(str) + sidx; + + /* use lookup tables to eliminate branching */ + const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx32 : be_idx32; + dptr[idx[0]] = (char)(n & 0xff); + dptr[idx[1]] = (char)(n >> 8); + dptr[idx[2]] = (char)(n >> 16); + dptr[idx[3]] = (char)(n >> 24); + return 4; } @@ -229,7 +464,7 @@ u32tostr(char *buf, size_t len, uint32_t n) #endif /* MRB_INT64 */ static int -unpack_long(mrb_state *mrb, const unsigned char *src, int srclen, mrb_value ary, unsigned int flags) +unpack_long(mrb_state *mrb, const unsigned char *src, mrb_int srclen, mrb_value ary, unsigned int flags) { #ifndef MRB_INT64 char msg[60]; @@ -237,18 +472,13 @@ unpack_long(mrb_state *mrb, const unsigned char *src, int srclen, mrb_value ary, uint32_t ul; mrb_int n; - if (flags & PACK_FLAG_LITTLEENDIAN) { - ul = (uint32_t)src[3] * 256*256*256; - ul += (uint32_t)src[2] *256*256; - ul += (uint32_t)src[1] *256; - ul += (uint32_t)src[0]; - } - else { - ul = (uint32_t)src[0] * 256*256*256; - ul += (uint32_t)src[1] *256*256; - ul += (uint32_t)src[2] *256; - ul += (uint32_t)src[3]; - } + /* use lookup tables to eliminate branching */ + const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx32 : be_idx32; + ul = ((uint32_t)src[idx[3]] << 24) | + ((uint32_t)src[idx[2]] << 16) | + ((uint32_t)src[idx[1]] << 8) | + (uint32_t)src[idx[0]]; + if (flags & PACK_FLAG_SIGNED) { n = (int32_t)ul; } @@ -268,30 +498,21 @@ unpack_long(mrb_state *mrb, const unsigned char *src, int srclen, mrb_value ary, static int pack_quad(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { - uint64_t n; - str = str_len_ensure(mrb, str, sidx + 8); - n = (uint64_t)mrb_integer(o); - if (flags & PACK_FLAG_LITTLEENDIAN) { - RSTRING_PTR(str)[sidx+0] = (char)(n & 0xff); - RSTRING_PTR(str)[sidx+1] = (char)(n >> 8); - RSTRING_PTR(str)[sidx+2] = (char)(n >> 16); - RSTRING_PTR(str)[sidx+3] = (char)(n >> 24); - RSTRING_PTR(str)[sidx+4] = (char)(n >> 32); - RSTRING_PTR(str)[sidx+5] = (char)(n >> 40); - RSTRING_PTR(str)[sidx+6] = (char)(n >> 48); - RSTRING_PTR(str)[sidx+7] = (char)(n >> 56); - } - else { - RSTRING_PTR(str)[sidx+0] = (char)(n >> 56); - RSTRING_PTR(str)[sidx+1] = (char)(n >> 48); - RSTRING_PTR(str)[sidx+2] = (char)(n >> 40); - RSTRING_PTR(str)[sidx+3] = (char)(n >> 32); - RSTRING_PTR(str)[sidx+4] = (char)(n >> 24); - RSTRING_PTR(str)[sidx+5] = (char)(n >> 16); - RSTRING_PTR(str)[sidx+6] = (char)(n >> 8); - RSTRING_PTR(str)[sidx+7] = (char)(n & 0xff); - } + uint64_t n = (uint64_t)mrb_integer(o); + char *dptr = RSTRING_PTR(str) + sidx; + + /* use lookup tables to eliminate branching */ + const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx64 : be_idx64; + dptr[idx[0]] = (char)(n & 0xff); + dptr[idx[1]] = (char)(n >> 8); + dptr[idx[2]] = (char)(n >> 16); + dptr[idx[3]] = (char)(n >> 24); + dptr[idx[4]] = (char)(n >> 32); + dptr[idx[5]] = (char)(n >> 40); + dptr[idx[6]] = (char)(n >> 48); + dptr[idx[7]] = (char)(n >> 56); + return 8; } @@ -344,26 +565,22 @@ i64tostr(char *buf, size_t len, int64_t n) #endif /* MRB_INT64 */ static int -unpack_quad(mrb_state *mrb, const unsigned char *src, int srclen, mrb_value ary, unsigned int flags) +unpack_quad(mrb_state *mrb, const unsigned char *src, mrb_int srclen, mrb_value ary, unsigned int flags) { char msg[60]; - uint64_t ull; - int i, pos, step; mrb_int n; - if (flags & PACK_FLAG_LITTLEENDIAN) { - pos = 7; - step = -1; - } - else { - pos = 0; - step = 1; - } - ull = 0; - for (i = 0; i < 8; i++) { - ull = ull * 256 + (uint64_t)src[pos]; - pos += step; - } + /* use lookup tables to eliminate branching */ + const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx64 : be_idx64; + uint64_t ull = ((uint64_t)src[idx[7]] << 56) | + ((uint64_t)src[idx[6]] << 48) | + ((uint64_t)src[idx[5]] << 40) | + ((uint64_t)src[idx[4]] << 32) | + ((uint64_t)src[idx[3]] << 24) | + ((uint64_t)src[idx[2]] << 16) | + ((uint64_t)src[idx[1]] << 8) | + (uint64_t)src[idx[0]]; + if (flags & PACK_FLAG_SIGNED) { int64_t sll = ull; #ifndef MRB_INT64 @@ -389,43 +606,70 @@ static int pack_BER(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { mrb_int n = mrb_integer(o); - int i; - char *p; if (n < 0) { mrb_raise(mrb, E_ARGUMENT_ERROR, "can't compress negative numbers"); } - for (i=1; i<(int)sizeof(mrb_int)+1; i++) { - mrb_int mask = ~((1L<<(7*i))-1); + + /* fast path for 1-byte values (0-127) */ + if (n < 128) { + str = str_len_ensure(mrb, str, sidx + 1); + RSTRING_PTR(str)[sidx] = (char)n; + return 1; + } + + /* fast path for 2-byte values (128-16383) */ + if (n < 16384) { + str = str_len_ensure(mrb, str, sidx + 2); + char *p = RSTRING_PTR(str) + sidx; + *p++ = (char)((n >> 7) | 0x80); + *p = (char)(n & 0x7f); + return 2; + } + + /* original algorithm for larger values */ + int i; + for (i = 1; i < (int)sizeof(mrb_int) + 1; i++) { + mrb_int mask = ~((1L << (7 * i)) - 1); if ((n & mask) == 0) break; } + str = str_len_ensure(mrb, str, sidx + i); - p = RSTRING_PTR(str)+sidx; - for (size_t j=i; j>0; p++,j--) { - mrb_int x = (n>>(7*(j-1)))&0x7f; + char *p = RSTRING_PTR(str) + sidx; + + for (size_t j = i; j > 0; p++, j--) { + mrb_int x = (n >> (7 * (j - 1))) & 0x7f; *p = (char)x; if (j > 1) *p |= 0x80; } + return i; } static int -unpack_BER(mrb_state *mrb, const unsigned char *src, int srclen, mrb_value ary, unsigned int flags) +unpack_BER(mrb_state *mrb, const unsigned char *src, mrb_int srclen, mrb_value ary, unsigned int flags) { - int i; mrb_int n = 0; const unsigned char *p = src; const unsigned char *e = p + srclen; if (srclen == 0) return 0; - for (i=1; p (MRB_INT_MAX>>7)) { + + /* calculate maximum safe bytes before potential overflow */ + const int max_safe_bytes = (sizeof(mrb_int) * 8 - 1) / 7; /* conservative estimate */ + + int i; + for (i = 1; p < e; p++, i++) { + /* check overflow before we might exceed safe limits */ + if (i > max_safe_bytes || n > (MRB_INT_MAX >> 7)) { mrb_raise(mrb, E_RANGE_ERROR, "BER unpacking 'w' overflow"); } + n <<= 7; n |= *p & 0x7f; if ((*p & 0x80) == 0) break; } + mrb_ary_push(mrb, ary, mrb_int_value(mrb, n)); return i; } @@ -434,64 +678,49 @@ unpack_BER(mrb_state *mrb, const unsigned char *src, int srclen, mrb_value ary, static int pack_double(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { - int i; - double d; - uint8_t *buffer = (uint8_t*)&d; - str = str_len_ensure(mrb, str, sidx + 8); - d = mrb_float(o); + union { + double d; + uint8_t bytes[8]; + } converter; - if (flags & PACK_FLAG_LITTLEENDIAN) { - if (littleendian) { - memcpy(RSTRING_PTR(str) + sidx, buffer, 8); - } - else { - for (i = 0; i < 8; i++) { - RSTRING_PTR(str)[sidx + i] = buffer[8 - i - 1]; - } - } - } - else { - if (littleendian) { - for (i = 0; i < 8; i++) { - RSTRING_PTR(str)[sidx + i] = buffer[8 - i - 1]; - } - } - else { - memcpy(RSTRING_PTR(str) + sidx, buffer, 8); - } - } + str = str_len_ensure(mrb, str, sidx + 8); + converter.d = mrb_float(o); + char *dptr = RSTRING_PTR(str) + sidx; + + /* use lookup tables to eliminate branching */ + const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx64 : be_idx64; + dptr[idx[0]] = converter.bytes[0]; + dptr[idx[1]] = converter.bytes[1]; + dptr[idx[2]] = converter.bytes[2]; + dptr[idx[3]] = converter.bytes[3]; + dptr[idx[4]] = converter.bytes[4]; + dptr[idx[5]] = converter.bytes[5]; + dptr[idx[6]] = converter.bytes[6]; + dptr[idx[7]] = converter.bytes[7]; return 8; } static int -unpack_double(mrb_state *mrb, const unsigned char * src, int srclen, mrb_value ary, unsigned int flags) +unpack_double(mrb_state *mrb, const unsigned char * src, mrb_int srclen, mrb_value ary, unsigned int flags) { - int i; - double d; - uint8_t *buffer = (uint8_t*)&d; + union { + double d; + uint8_t bytes[8]; + } converter; - if (flags & PACK_FLAG_LITTLEENDIAN) { - if (littleendian) { - memcpy(buffer, src, 8); - } - else { - for (i = 0; i < 8; i++) { - buffer[8 - i - 1] = src[i]; - } - } - } - else { - if (littleendian) { - for (i = 0; i < 8; i++) { - buffer[8 - i - 1] = src[i]; - } - } - else { - memcpy(buffer, src, 8); - } - } - mrb_ary_push(mrb, ary, mrb_float_value(mrb, d)); + /* use lookup tables to eliminate branching */ + const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx64 : be_idx64; + converter.bytes[0] = src[idx[0]]; + converter.bytes[1] = src[idx[1]]; + converter.bytes[2] = src[idx[2]]; + converter.bytes[3] = src[idx[3]]; + converter.bytes[4] = src[idx[4]]; + converter.bytes[5] = src[idx[5]]; + converter.bytes[6] = src[idx[6]]; + converter.bytes[7] = src[idx[7]]; + + mrb_ary_push(mrb, ary, mrb_float_value(mrb, converter.d)); return 8; } @@ -499,64 +728,41 @@ unpack_double(mrb_state *mrb, const unsigned char * src, int srclen, mrb_value a static int pack_float(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { - int i; - float f; - uint8_t *buffer = (uint8_t*)&f; - str = str_len_ensure(mrb, str, sidx + 4); - f = (float)mrb_float(o); + union { + float f; + uint8_t bytes[4]; + } converter; - if (flags & PACK_FLAG_LITTLEENDIAN) { - if (littleendian) { - memcpy(RSTRING_PTR(str) + sidx, buffer, 4); - } - else { - for (i = 0; i < 4; i++) { - RSTRING_PTR(str)[sidx + i] = buffer[4 - i - 1]; - } - } - } - else { - if (littleendian) { - for (i = 0; i < 4; i++) { - RSTRING_PTR(str)[sidx + i] = buffer[4 - i - 1]; - } - } - else { - memcpy(RSTRING_PTR(str) + sidx, buffer, 4); - } - } + str = str_len_ensure(mrb, str, sidx + 4); + converter.f = (float)mrb_float(o); + char *dptr = RSTRING_PTR(str) + sidx; + + /* use lookup tables to eliminate branching */ + const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx32 : be_idx32; + dptr[idx[0]] = converter.bytes[0]; + dptr[idx[1]] = converter.bytes[1]; + dptr[idx[2]] = converter.bytes[2]; + dptr[idx[3]] = converter.bytes[3]; return 4; } static int -unpack_float(mrb_state *mrb, const unsigned char * src, int srclen, mrb_value ary, unsigned int flags) +unpack_float(mrb_state *mrb, const unsigned char * src, mrb_int srclen, mrb_value ary, unsigned int flags) { - int i; - float f; - uint8_t *buffer = (uint8_t*)&f; + union { + float f; + uint8_t bytes[4]; + } converter; - if (flags & PACK_FLAG_LITTLEENDIAN) { - if (littleendian) { - memcpy(buffer, src, 4); - } - else { - for (i = 0; i < 4; i++) { - buffer[4 - i - 1] = src[i]; - } - } - } - else { - if (littleendian) { - for (i = 0; i < 4; i++) { - buffer[4 - i - 1] = src[i]; - } - } - else { - memcpy(buffer, src, 4); - } - } - mrb_ary_push(mrb, ary, mrb_float_value(mrb, f)); + /* use lookup tables to eliminate branching */ + const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx32 : be_idx32; + converter.bytes[0] = src[idx[0]]; + converter.bytes[1] = src[idx[1]]; + converter.bytes[2] = src[idx[2]]; + converter.bytes[3] = src[idx[3]]; + + mrb_ary_push(mrb, ary, mrb_float_value(mrb, converter.f)); return 4; } @@ -605,90 +811,94 @@ pack_utf8(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, int count, u return len; } -static const unsigned long utf8_limits[] = { - 0x0, /* 1 */ - 0x80, /* 2 */ - 0x800, /* 3 */ - 0x10000, /* 4 */ - 0x200000, /* 5 */ - 0x4000000, /* 6 */ - 0x80000000, /* 7 */ -}; -static unsigned long -utf8_to_uv(mrb_state *mrb, const char *p, long *lenp) -{ - int c = *p++ & 0xff; - unsigned long uv = c; - long n = 1; - - if (!(uv & 0x80)) { - *lenp = 1; - return uv; - } - if (!(uv & 0x40)) { - *lenp = 1; - malformed: - mrb_raise(mrb, E_ARGUMENT_ERROR, "malformed UTF-8 character"); - } - - if (!(uv & 0x20)) { n = 2; uv &= 0x1f; } - else if (!(uv & 0x10)) { n = 3; uv &= 0x0f; } - else if (!(uv & 0x08)) { n = 4; uv &= 0x07; } - else if (!(uv & 0x04)) { n = 5; uv &= 0x03; } - else if (!(uv & 0x02)) { n = 6; uv &= 0x01; } - else { - *lenp = 1; - goto malformed; - } - if (n > *lenp) { - mrb_raisef(mrb, E_ARGUMENT_ERROR, "malformed UTF-8 character (expected %d bytes, given %d bytes)", - n, *lenp); - } - *lenp = n--; - if (n != 0) { - while (n--) { - c = *p++ & 0xff; - if ((c & 0xc0) != 0x80) { - *lenp -= n + 1; - goto malformed; - } - else { - c &= 0x3f; - uv = uv << 6 | c; - } - } - } - n = *lenp - 1; - if (uv < utf8_limits[n]) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "redundant UTF-8 sequence"); - } - return uv; -} static int -unpack_utf8(mrb_state *mrb, const unsigned char * src, int srclen, mrb_value ary, unsigned int flags) +unpack_utf8(mrb_state *mrb, const unsigned char * src, mrb_int srclen, mrb_value ary, unsigned int flags) { - unsigned long uv; - long lenp = srclen; - if (srclen == 0) { return 1; } - uv = utf8_to_uv(mrb, (const char*)src, &lenp); - mrb_ary_push(mrb, ary, mrb_fixnum_value((mrb_int)uv)); - return (int)lenp; + + const unsigned char *p = src; + uint8_t first_byte = *p; + + /* ASCII fast path - most common case */ + if (first_byte < 0x80) { + mrb_ary_push(mrb, ary, mrb_fixnum_value(first_byte)); + return 1; + } + + /* Multi-byte UTF-8 with optimized lookup table */ + uint8_t seq_len = utf8_seq_len_high[first_byte - 0x80]; + if (seq_len == 0 || seq_len > srclen) { + goto malformed; + } + + /* Inline 2-byte sequence optimization - common case */ + if (seq_len == 2) { + if (!IS_UTF8_CONTINUATION(p[1])) { + goto malformed; + } + uint32_t uv = ((first_byte & 0x1F) << 6) | (p[1] & 0x3F); + /* validate minimum value for 2-byte sequence */ + if (uv >= 0x80) { + mrb_ary_push(mrb, ary, mrb_fixnum_value((mrb_int)uv)); + return 2; + } + goto redundant; + } + + /* Inline 3-byte sequence optimization */ + if (seq_len == 3) { + if (!IS_UTF8_CONTINUATION(p[1]) || !IS_UTF8_CONTINUATION(p[2])) { + goto malformed; + } + uint32_t uv = ((first_byte & 0x0F) << 12) | + ((p[1] & 0x3F) << 6) | + (p[2] & 0x3F); + /* validate minimum value for 3-byte sequence */ + if (uv >= 0x800) { + mrb_ary_push(mrb, ary, mrb_fixnum_value((mrb_int)uv)); + return 3; + } + goto redundant; + } + + /* 4-byte sequence - less common, use original implementation */ + if (seq_len == 4) { + if (!IS_UTF8_CONTINUATION(p[1]) || !IS_UTF8_CONTINUATION(p[2]) || !IS_UTF8_CONTINUATION(p[3])) { + goto malformed; + } + uint32_t uv = ((first_byte & 0x07) << 18) | + ((p[1] & 0x3F) << 12) | + ((p[2] & 0x3F) << 6) | + (p[3] & 0x3F); + /* validate minimum value and maximum valid Unicode */ + if (uv >= 0x10000 && uv <= 0x10FFFF) { + mrb_ary_push(mrb, ary, mrb_fixnum_value((mrb_int)uv)); + return 4; + } + if (uv < 0x10000) goto redundant; + goto malformed; + } + +malformed: + mrb_raise(mrb, E_ARGUMENT_ERROR, "malformed UTF-8 character"); + +redundant: + mrb_raise(mrb, E_ARGUMENT_ERROR, "redundant UTF-8 sequence"); } static int pack_str(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count, unsigned int flags) { - mrb_int copylen, slen, padlen; - char *dptr, *dptr0, pad, *sptr; - - sptr = RSTRING_PTR(src); - slen = RSTRING_LEN(src); + const char *sptr = RSTRING_PTR(src); + mrb_int slen = RSTRING_LEN(src); + mrb_int copylen, padlen; + char pad; + /* determine padding character based on format */ if ((flags & PACK_FLAG_a) || (flags & PACK_FLAG_Z)) pad = '\0'; else @@ -710,12 +920,21 @@ pack_str(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count, padlen = count - slen; } + /* pre-allocate exact buffer size */ dst = str_len_ensure(mrb, dst, didx + copylen + padlen); - dptr0 = dptr = RSTRING_PTR(dst) + didx; - memcpy(dptr, sptr, copylen); - dptr += copylen; - while (padlen-- > 0) { - *dptr++ = pad; + char *dptr = RSTRING_PTR(dst) + didx; + char *dptr0 = dptr; + + /* copy string data */ + if (copylen > 0) { + memcpy(dptr, sptr, copylen); + dptr += copylen; + } + + /* bulk padding using memset instead of loop */ + if (padlen > 0) { + memset(dptr, pad, padlen); + dptr += padlen; } return (int)(dptr - dptr0); @@ -729,53 +948,50 @@ pack_str(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count, } while (0) static int -unpack_str(mrb_state *mrb, const void *src, int slen, mrb_value ary, int count, unsigned int flags) +unpack_str(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary, int count, unsigned int flags) { CHECK_UNPACK_LEN(mrb, slen, ary); - mrb_value dst; - const char *sptr; + const char *sptr = (const char*)src; int copylen; - sptr = (const char*)src; - if (count != -1 && count < slen) { + if (count != -1 && count < slen) { slen = count; } - copylen = slen; + copylen = (int)slen; - if (slen >= 0 && flags & PACK_FLAG_Z) { /* "Z" */ - const char *cp; - - if ((cp = (const char*)memchr(sptr, '\0', slen)) != NULL) { + if (slen >= 0 && flags & PACK_FLAG_Z) { /* "Z" format */ + const char *cp = (const char*)memchr(sptr, '\0', slen); + if (cp != NULL) { copylen = (int)(cp - sptr); if (count == -1) { slen = copylen + 1; } } } - else if (!(flags & PACK_FLAG_a)) { /* "A" */ - while (copylen > 0 && (sptr[copylen - 1] == '\0' || ISSPACE(sptr[copylen - 1]))) { + else if (!(flags & PACK_FLAG_a)) { /* "A" format - trim spaces and nulls */ + /* optimized reverse trimming using lookup table */ + while (copylen > 0 && IS_PADDING_CHAR_A(sptr[copylen - 1])) { copylen--; } } + /* "a" format does no trimming */ if (copylen < 0) copylen = 0; - dst = mrb_str_new(mrb, sptr, (mrb_int)copylen); + + mrb_value dst = mrb_str_new(mrb, sptr, (mrb_int)copylen); mrb_ary_push(mrb, ary, dst); - return slen; + return (int)slen; } static int pack_hex(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count, unsigned int flags) { + char *sptr = RSTRING_PTR(src); + long slen = (long)RSTRING_LEN(src); + unsigned int ashift, bshift; - long slen; - char *dptr, *dptr0, *sptr; - - sptr = RSTRING_PTR(src); - slen = (long)RSTRING_LEN(src); - if (flags & PACK_FLAG_LSB) { ashift = 0; bshift = 4; @@ -792,40 +1008,41 @@ pack_hex(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count, slen = count; } - dst = str_len_ensure(mrb, dst, didx + count); - dptr = RSTRING_PTR(dst) + didx; + /* calculate output buffer size needed - one byte per two hex chars */ + /* use count/2 + (count&1) to avoid overflow when count == INT_MAX */ + int output_bytes = count / 2 + (count & 1); + dst = str_len_ensure(mrb, dst, didx + output_bytes); + char *dptr = RSTRING_PTR(dst) + didx; + char *dptr0 = dptr; - dptr0 = dptr; - for (; count > 0; count -= 2) { - int a = 0, b = 0; + /* process pairs of hex characters */ + while (slen >= 2) { + int a = hex2int((unsigned char)*sptr++); + if (a < 0) break; + int b = hex2int((unsigned char)*sptr++); + if (b < 0) break; - if (slen > 0) { - a = hex2int(*sptr++); - if (a < 0) break; - slen--; - } - if (slen > 0) { - b = hex2int(*sptr++); - if (b < 0) break; - slen--; - } *dptr++ = (a << ashift) + (b << bshift); + slen -= 2; + } + + /* handle odd remaining character */ + if (slen > 0) { + int a = hex2int((unsigned char)*sptr); + if (a >= 0) { + *dptr++ = (a << ashift); + } } return (int)(dptr - dptr0); } static int -unpack_hex(mrb_state *mrb, const void *src, int slen, mrb_value ary, int count, unsigned int flags) +unpack_hex(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary, int count, unsigned int flags) { CHECK_UNPACK_LEN(mrb, slen, ary); - mrb_value dst; - int a, ashift, b, bshift; - const char *sptr, *sptr0; - char *dptr, *dptr0; - const char hexadecimal[] = "0123456789abcdef"; - + int ashift, bshift; if (flags & PACK_FLAG_LSB) { ashift = 0; bshift = 4; @@ -835,29 +1052,32 @@ unpack_hex(mrb_state *mrb, const void *src, int slen, mrb_value ary, int count, bshift = 0; } - sptr = (const char*)src; + const char *sptr = (const char*)src; + const char *sptr0 = sptr; if (count == -1) - count = slen * 2; + count = (int)(slen * 2); - dst = mrb_str_new(mrb, NULL, count); - dptr = RSTRING_PTR(dst); + mrb_value dst = mrb_str_new(mrb, NULL, count); + char *dptr = RSTRING_PTR(dst); + char *dptr0 = dptr; - sptr0 = sptr; - dptr0 = dptr; - while (slen > 0 && count > 0) { - a = (*sptr >> ashift) & 0x0f; - b = (*sptr >> bshift) & 0x0f; - sptr++; + const char hexadecimal[] = "0123456789abcdef"; + + /* process full bytes when we need pairs of hex characters */ + while (slen > 0 && count >= 2) { + unsigned char byte = *sptr++; slen--; - *dptr++ = hexadecimal[a]; - count--; + *dptr++ = hexadecimal[(byte >> ashift) & 0x0f]; + *dptr++ = hexadecimal[(byte >> bshift) & 0x0f]; + count -= 2; + } - if (count > 0) { - *dptr++ = hexadecimal[b]; - count--; - } + /* handle remaining single character if count is odd */ + if (slen > 0 && count > 0) { + unsigned char byte = *sptr++; + *dptr++ = hexadecimal[(byte >> ashift) & 0x0f]; } dst = mrb_str_resize(mrb, dst, (mrb_int)(dptr - dptr0)); @@ -878,74 +1098,74 @@ pack_bstr(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count, slen = count; } - dst = str_len_ensure(mrb, dst, didx + count); + /* calculate exact output size: (slen + 7) / 8 */ + int output_bytes = (slen + 7) / 8; + dst = str_len_ensure(mrb, dst, didx + output_bytes); char *dptr = RSTRING_PTR(dst) + didx; char *dptr0 = dptr; - unsigned int byte = 0; - for (int i=0; i++ < slen; sptr++) { - if (flags & PACK_FLAG_LSB) { - if (*sptr & 1) - byte |= 128; - if (i & 7) - byte >>= 1; - else { - char c = (char)(byte&0xff); - *dptr++ = c; - byte = 0; - } - } - else { - byte |= *sptr & 1; - if (i & 7) - byte <<= 1; - else { - char c = (char)(byte&0xff); - *dptr++ = c; - byte = 0; - } - } + /* select batch processing function based on bit order */ + uint8_t (*pack_func)(const char *) = (flags & PACK_FLAG_LSB) ? pack_8_bits_lsb : pack_8_bits_msb; + + /* process 8 characters at a time */ + int full_bytes = slen / 8; + for (int i = 0; i < full_bytes; i++) { + *dptr++ = (char)pack_func(sptr); + sptr += 8; } - if (slen & 7) { - if (flags & PACK_FLAG_LSB) { - byte >>= 7 - (slen & 7); + + /* handle remaining bits (partial byte) */ + int remaining_bits = slen % 8; + if (remaining_bits > 0) { + char temp_chars[8] = {'0', '0', '0', '0', '0', '0', '0', '0'}; + /* copy remaining characters, padding with '0' */ + for (int i = 0; i < remaining_bits; i++) { + temp_chars[i] = sptr[i]; } - else { - byte <<= 7 - (slen & 7); - } - char c = (char)(byte&0xff); - *dptr++ = c; + *dptr++ = (char)pack_func(temp_chars); } + return (int)(dptr - dptr0); } static int -unpack_bstr(mrb_state *mrb, const void *src, int slen, mrb_value ary, int count, unsigned int flags) +unpack_bstr(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary, int count, unsigned int flags) { CHECK_UNPACK_LEN(mrb, slen, ary); const char *sptr0 = (const char*)src; const char *sptr = sptr0; - if (count == -1 || count > slen * 8) - count = slen * 8; + if (count == -1 || count > slen * 8) + count = (int)(slen * 8); + + /* pre-allocate exact output size */ mrb_value dst = mrb_str_new(mrb, NULL, count); char *dptr = RSTRING_PTR(dst); - const char *dptr0 = dptr; - int bits = 0; + char *dptr0 = dptr; - for (int i=0; i>= 1; - else bits = (unsigned char)*sptr++; - *dptr++ = (bits & 1) ? '1' : '0'; - } - else { - if (i & 7) bits <<= 1; - else bits = (unsigned char)*sptr++; - *dptr++ = (bits & 128) ? '1' : '0'; + /* select batch processing function based on bit order */ + void (*unpack_func)(uint8_t, char *) = (flags & PACK_FLAG_LSB) ? unpack_8_bits_lsb : unpack_8_bits_msb; + + /* process 8 bits (1 byte) at a time */ + int full_bytes = count / 8; + for (int i = 0; i < full_bytes; i++) { + unpack_func((uint8_t)*sptr++, dptr); + dptr += 8; + } + + /* handle remaining bits (partial byte) */ + int remaining_bits = count % 8; + if (remaining_bits > 0) { + char temp_chars[8]; + unpack_func((uint8_t)*sptr++, temp_chars); + /* copy only the required number of characters */ + for (int i = 0; i < remaining_bits; i++) { + *dptr++ = temp_chars[i]; } } + + /* ensure string is properly sized */ dst = mrb_str_resize(mrb, dst, (mrb_int)(dptr - dptr0)); mrb_ary_push(mrb, ary, dst); return (int)(sptr - sptr0); @@ -954,93 +1174,130 @@ unpack_bstr(mrb_state *mrb, const void *src, int slen, mrb_value ary, int count, static int pack_base64(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count) { - mrb_int dstlen; - unsigned long l; - mrb_int column, srclen; - char *srcptr, *dstptr, *dstptr0; + char *srcptr = RSTRING_PTR(src); + mrb_int srclen = RSTRING_LEN(src); - srcptr = RSTRING_PTR(src); - srclen = RSTRING_LEN(src); - - if (srclen == 0) /* easy case */ + if (srclen == 0) { return 0; + } - if (count != 0 && count < 3) { /* -1, 1 or 2 */ + if (count != 0 && count < 3) { count = 45; } else if (count >= 3) { count -= count % 3; } - dstlen = (srclen+2) / 3 * 4; + /* precise memory allocation */ + mrb_int dstlen = (srclen + 2) / 3 * 4; if (count > 0) { dstlen += (srclen / count) + ((srclen % count) == 0 ? 0 : 1); } dst = str_len_ensure(mrb, dst, didx + dstlen); - dstptr = RSTRING_PTR(dst) + didx; + char *dstptr = RSTRING_PTR(dst) + didx; + char *dstptr0 = dstptr; - dstptr0 = dstptr; - for (column = 3; srclen >= 3; srclen -= 3, column += 3) { - l = (unsigned char)*srcptr++ << 16; - l += (unsigned char)*srcptr++ << 8; - l += (unsigned char)*srcptr++; + if (count == 0) { + /* fast path: no line wrapping */ + while (srclen >= 3) { + unsigned long l = (unsigned char)*srcptr++ << 16; + l += (unsigned char)*srcptr++ << 8; + l += (unsigned char)*srcptr++; + srclen -= 3; - *dstptr++ = base64chars[(l >> 18) & 0x3f]; - *dstptr++ = base64chars[(l >> 12) & 0x3f]; - *dstptr++ = base64chars[(l >> 6) & 0x3f]; - *dstptr++ = base64chars[ l & 0x3f]; - - if (column == count) { - *dstptr++ = '\n'; - column = 0; + *dstptr++ = base64chars[(l >> 18) & 0x3f]; + *dstptr++ = base64chars[(l >> 12) & 0x3f]; + *dstptr++ = base64chars[(l >> 6) & 0x3f]; + *dstptr++ = base64chars[l & 0x3f]; } } + else { + /* line wrapping path */ + mrb_int column = 3; + while (srclen >= 3) { + unsigned long l = (unsigned char)*srcptr++ << 16; + l += (unsigned char)*srcptr++ << 8; + l += (unsigned char)*srcptr++; + srclen -= 3; + + *dstptr++ = base64chars[(l >> 18) & 0x3f]; + *dstptr++ = base64chars[(l >> 12) & 0x3f]; + *dstptr++ = base64chars[(l >> 6) & 0x3f]; + *dstptr++ = base64chars[l & 0x3f]; + + if (column == count) { + *dstptr++ = '\n'; + column = 0; + } + column += 3; + } + + /* handle remaining 1-2 bytes */ + if (srclen == 1) { + unsigned long l = (unsigned char)*srcptr << 16; + *dstptr++ = base64chars[(l >> 18) & 0x3f]; + *dstptr++ = base64chars[(l >> 12) & 0x3f]; + *dstptr++ = '='; + *dstptr++ = '='; + column += 3; + } + else if (srclen == 2) { + unsigned long l = (unsigned char)*srcptr++ << 16; + l += (unsigned char)*srcptr << 8; + *dstptr++ = base64chars[(l >> 18) & 0x3f]; + *dstptr++ = base64chars[(l >> 12) & 0x3f]; + *dstptr++ = base64chars[(l >> 6) & 0x3f]; + *dstptr++ = '='; + column += 3; + } + + if (column > 0) { + *dstptr++ = '\n'; + } + return (int)(dstptr - dstptr0); + } + + /* handle remaining 1-2 bytes for fast path */ if (srclen == 1) { - l = (unsigned char)*srcptr++ << 16; + unsigned long l = (unsigned char)*srcptr << 16; *dstptr++ = base64chars[(l >> 18) & 0x3f]; *dstptr++ = base64chars[(l >> 12) & 0x3f]; *dstptr++ = '='; *dstptr++ = '='; - column += 3; } else if (srclen == 2) { - l = (unsigned char)*srcptr++ << 16; - l += (unsigned char)*srcptr++ << 8; + unsigned long l = (unsigned char)*srcptr++ << 16; + l += (unsigned char)*srcptr << 8; *dstptr++ = base64chars[(l >> 18) & 0x3f]; *dstptr++ = base64chars[(l >> 12) & 0x3f]; - *dstptr++ = base64chars[(l >> 6) & 0x3f]; + *dstptr++ = base64chars[(l >> 6) & 0x3f]; *dstptr++ = '='; - column += 3; - } - if (column > 0 && count > 0) { - *dstptr++ = '\n'; } return (int)(dstptr - dstptr0); } static int -unpack_base64(mrb_state *mrb, const void *src, int slen, mrb_value ary) +unpack_base64(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary) { CHECK_UNPACK_LEN(mrb, slen, ary); - mrb_value dst; - int dlen; - unsigned long l; - int i, padding; - unsigned char c, ch[4]; - const char *sptr, *sptr0; - char *dptr, *dptr0; + const char *sptr0 = (const char*)src; + const char *sptr = sptr0; - sptr0 = sptr = (const char*)src; + /* estimate buffer size - may be shorter due to padding/whitespace */ + int dlen = (int)(slen / 4 * 3); + mrb_value dst = mrb_str_new(mrb, NULL, dlen); + char *dptr0 = RSTRING_PTR(dst); + char *dptr = dptr0; - dlen = slen / 4 * 3; /* an estimated value - may be shorter */ - dst = mrb_str_new(mrb, NULL, dlen); - dptr0 = dptr = RSTRING_PTR(dst); - - padding = 0; + int padding = 0; while (slen >= 4) { - for (i = 0; i < 4; i++) { + unsigned char ch[4]; + + /* collect 4 valid base64 characters */ + for (int i = 0; i < 4; i++) { + unsigned char c; do { if (slen-- == 0) goto done; @@ -1055,7 +1312,8 @@ unpack_base64(mrb_state *mrb, const void *src, int slen, mrb_value ary) } while (c >= sizeof(base64_dec_tab) || ch[i] == PACK_BASE64_IGNORE); } - l = (ch[0] << 18) + (ch[1] << 12) + (ch[2] << 6) + ch[3]; + /* decode 4 characters to 3 bytes */ + unsigned long l = (ch[0] << 18) + (ch[1] << 12) + (ch[2] << 6) + ch[3]; if (padding == 0) { *dptr++ = (l >> 16) & 0xff; @@ -1090,36 +1348,57 @@ pack_qenc(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count) int dlen = 0; if (count <= 1) count = 72; + while (s < send) { - if ((*s > 126) || - (*s < 32 && *s != '\n' && *s != '\t') || - (*s == '=')) { + unsigned char byte = (unsigned char)*s; + uint8_t encode_type = qprint_encode_type[byte]; + + /* ASCII printable fast path - most common case */ + if (encode_type == 0) { + /* Handle space/tab at line end special case */ + if ((byte == ' ' || byte == '\t') && s + 1 < send && *(s + 1) == '\n') { + /* Space/tab before newline needs encoding */ + buff[i++] = '='; + buff[i++] = hex_table[(byte & 0xf0) >> 4]; + buff[i++] = hex_table[byte & 0x0f]; + n += 3; + prev = EOF; + } + else { + /* Regular printable character - direct copy */ + buff[i++] = byte; + n++; + prev = byte; + } + } + /* Newline special handling */ + else if (encode_type == 2) { + if (prev == ' ' || prev == '\t') { + buff[i++] = '='; + buff[i++] = byte; + } + buff[i++] = byte; + n = 0; + prev = byte; + } + /* Character needs encoding */ + else { buff[i++] = '='; - buff[i++] = hex_table[(*s & 0xf0) >> 4]; - buff[i++] = hex_table[*s & 0x0f]; + buff[i++] = hex_table[(byte & 0xf0) >> 4]; + buff[i++] = hex_table[byte & 0x0f]; n += 3; prev = EOF; } - else if (*s == '\n') { - if (prev == ' ' || prev == '\t') { - buff[i++] = '='; - buff[i++] = *s; - } - buff[i++] = *s; - n = 0; - prev = *s; - } - else { - buff[i++] = *s; - n++; - prev = *s; - } + + /* Check line length limit */ if (n > count) { buff[i++] = '='; buff[i++] = '\n'; n = 0; prev = '\n'; } + + /* Flush buffer if getting full */ if (i > 1024 - 5) { str_len_ensure(mrb, dst, didx+dlen+i); memcpy(RSTRING_PTR(dst)+didx+dlen, buff, i); @@ -1128,51 +1407,215 @@ pack_qenc(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count) } s++; } + + /* Add final soft line break if needed */ if (n > 0) { buff[i++] = '='; buff[i++] = '\n'; } + + /* Flush remaining buffer */ if (i > 0) { str_len_ensure(mrb, dst, didx+dlen+i); memcpy(RSTRING_PTR(dst)+didx+dlen, buff, i); dlen += i; } + return dlen; } static int -unpack_qenc(mrb_state *mrb, const void *src, int slen, mrb_value ary) +unpack_qenc(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary) { CHECK_UNPACK_LEN(mrb, slen, ary); mrb_value buf = mrb_str_new(mrb, 0, slen); - const char *s = (const char*)src, *ss = s; + const char *s = (const char*)src; const char *send = s + slen; char *ptr = RSTRING_PTR(buf); int c1, c2; while (s < send) { - if (*s == '=') { - if (++s == send) break; - if (s+1 < send && *s == '\r' && *(s+1) == '\n') - s++; - if (*s != '\n') { - if ((c1 = hex2int(*s)) == -1) break; - if (++s == send) break; - if ((c2 = hex2int(*s)) == -1) break; - *ptr++ = (char)(c1 << 4 | c2); + /* Fast path for non-encoded characters - most common case */ + if (*s != '=') { + *ptr++ = *s; + s++; + continue; + } + + /* Handle =XX encoded sequences */ + if (++s == send) break; + + /* Handle soft line breaks: =\r\n or =\n */ + if (s + 1 < send && *s == '\r' && *(s + 1) == '\n') { + s += 2; /* Skip \r\n */ + continue; + } + if (*s == '\n') { + s++; /* Skip \n */ + continue; + } + + /* Decode =XX hex sequence */ + if ((c1 = hex2int(*s)) == -1) break; + if (++s == send) break; + if ((c2 = hex2int(*s)) == -1) break; + *ptr++ = (char)(c1 << 4 | c2); + s++; + } + + buf = mrb_str_resize(mrb, buf, (mrb_int)(ptr - RSTRING_PTR(buf))); + mrb_ary_push(mrb, ary, buf); + return (int)slen; +} + +static int +pack_uu(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count) +{ + char *s = RSTRING_PTR(src); + int slen = (int)RSTRING_LEN(src); + int lines_written = 0; + int dlen = 0; + + if (count <= 1) count = 45; /* default line length for UU-encoding */ + + /* Calculate buffer size by accounting for per-line encoding + * Each line encodes separately, so padding happens per line, not globally + */ + mrb_int num_lines = (slen + count - 1) / count; /* Number of lines */ + mrb_int total_encoded = 0; + mrb_int temp_slen = slen; + + /* Calculate actual encoded size line by line */ + while (temp_slen > 0) { + mrb_int line_len = (temp_slen > count) ? count : temp_slen; + total_encoded += ((line_len + 2) / 3) * 4; /* Each line's encoded size */ + temp_slen -= line_len; + } + + /* Total buffer = encoded data + (length char + newline) per line + terminating line */ + mrb_int buffer_size = total_encoded + num_lines * 2 + 2; + str_len_ensure(mrb, dst, didx + buffer_size); + char *dptr = RSTRING_PTR(dst) + didx; + + while (slen > 0) { + int line_len = (slen > count) ? count : slen; + + /* Write line length character */ + *dptr++ = uu_encode_table[line_len & 0x3F]; + dlen++; + + int processed = 0; + while (processed < line_len) { + /* Process groups of 3 bytes -> 4 characters */ + uint32_t group = 0; + int bytes_in_group = 0; + + /* Read up to 3 bytes */ + for (int i = 0; i < 3 && processed < line_len; i++) { + group = (group << 8) | (unsigned char)s[processed++]; + bytes_in_group++; } + + /* Pad incomplete group with zeros */ + group <<= (3 - bytes_in_group) * 8; + + /* Extract 4 groups of 6 bits and encode */ + *dptr++ = uu_encode_table[(group >> 18) & 0x3F]; + *dptr++ = uu_encode_table[(group >> 12) & 0x3F]; + *dptr++ = uu_encode_table[(group >> 6) & 0x3F]; + *dptr++ = uu_encode_table[group & 0x3F]; + dlen += 4; + } + + /* Add newline */ + *dptr++ = '\n'; + dlen++; + + s += line_len; + slen -= line_len; + lines_written++; + } + + /* Add terminating line if data was processed */ + if (lines_written > 0) { + *dptr++ = uu_encode_table[0]; /* length 0 */ + *dptr++ = '\n'; + dlen += 2; + } + + return dlen; +} + +static int +unpack_uu(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary) +{ + const unsigned char *s = (const unsigned char*)src; + const unsigned char *send = s + slen; + mrb_value result = mrb_str_new(mrb, 0, slen * 3 / 4); /* estimate result size */ + char *dptr = RSTRING_PTR(result); + char *dptr_start = dptr; + + while (s < send) { + /* Skip empty lines and whitespace */ + while (s < send && (*s == '\n' || *s == '\r' || *s == ' ' || *s == '\t')) { + s++; + } + if (s >= send) break; + + /* Read line length */ + int line_len = 0; + if (*s >= 32 && *s < 128 && uu_decode_table[*s - 32] >= 0) { + line_len = uu_decode_table[*s - 32]; + s++; } else { - *ptr++ = *s; + break; /* Invalid length character */ + } + + /* Empty line indicates end */ + if (line_len == 0) { + break; + } + + /* Decode line data */ + int bytes_decoded = 0; + while (bytes_decoded < line_len && s + 3 < send) { + /* Decode 4 characters to 3 bytes */ + int c[4]; + int valid = 1; + + for (int i = 0; i < 4; i++) { + if (s >= send || *s < 32 || *s >= 128 || uu_decode_table[*s - 32] < 0) { + valid = 0; + break; + } + c[i] = uu_decode_table[*s++ - 32]; + } + + if (!valid) break; + + /* Combine 4 x 6-bit values into 3 x 8-bit values */ + uint32_t group = (c[0] << 18) | (c[1] << 12) | (c[2] << 6) | c[3]; + + /* Extract up to 3 bytes, don't exceed line length */ + int bytes_to_extract = (line_len - bytes_decoded > 3) ? 3 : (line_len - bytes_decoded); + + for (int i = 0; i < bytes_to_extract; i++) { + *dptr++ = (group >> (16 - i * 8)) & 0xFF; + bytes_decoded++; + } + } + + /* Skip to end of line */ + while (s < send && *s != '\n' && *s != '\r') { + s++; } - s++; - ss = s; } - buf = mrb_str_resize(mrb, buf, (mrb_int)(ptr - RSTRING_PTR(buf))); - mrb_str_cat(mrb, buf, ss, send-ss); - mrb_ary_push(mrb, ary, buf); - return slen; + + result = mrb_str_resize(mrb, result, (mrb_int)(dptr - dptr_start)); + mrb_ary_push(mrb, ary, result); + return (int)slen; } static int @@ -1215,7 +1658,7 @@ read_tmpl(mrb_state *mrb, struct tmpl *tmpl, enum pack_type *typep, mrb_int *siz int ch, size = 0; enum pack_dir dir; enum pack_type type; - int count = 1; + mrb_int count = 1; unsigned int flags = 0; const char *tptr; @@ -1226,84 +1669,14 @@ read_tmpl(mrb_state *mrb, struct tmpl *tmpl, enum pack_type *typep, mrb_int *siz if (tmpl->idx >= tlen) return PACK_DIR_NONE; t = tptr[tmpl->idx++]; alias: + + /* Handle whitespace - skip and restart */ + if (ISSPACE((char)t)) { + goto restart; + } + + /* Special handling for runtime-dependent formats and special characters */ switch (t) { - case 'A': - dir = PACK_DIR_STR; - type = PACK_TYPE_STRING; - flags |= PACK_FLAG_WIDTH | PACK_FLAG_COUNT2; - break; - case 'a': - dir = PACK_DIR_STR; - type = PACK_TYPE_STRING; - flags |= PACK_FLAG_WIDTH | PACK_FLAG_COUNT2 | PACK_FLAG_a; - break; - case 'C': - dir = PACK_DIR_CHAR; - type = PACK_TYPE_INTEGER; - size = 1; - break; - case 'c': - dir = PACK_DIR_CHAR; - type = PACK_TYPE_INTEGER; - size = 1; - flags |= PACK_FLAG_SIGNED; - break; - case 'D': case 'd': - dir = PACK_DIR_DOUBLE; - type = PACK_TYPE_FLOAT; - size = 8; - flags |= PACK_FLAG_SIGNED; - break; - case 'F': case 'f': - dir = PACK_DIR_FLOAT; - type = PACK_TYPE_FLOAT; - size = 4; - flags |= PACK_FLAG_SIGNED; - break; - case 'E': - dir = PACK_DIR_DOUBLE; - type = PACK_TYPE_FLOAT; - size = 8; - flags |= PACK_FLAG_SIGNED | PACK_FLAG_LT; - break; - case 'e': - dir = PACK_DIR_FLOAT; - type = PACK_TYPE_FLOAT; - size = 4; - flags |= PACK_FLAG_SIGNED | PACK_FLAG_LT; - break; - case 'G': - dir = PACK_DIR_DOUBLE; - type = PACK_TYPE_FLOAT; - size = 8; - flags |= PACK_FLAG_SIGNED | PACK_FLAG_GT; - break; - case 'g': - dir = PACK_DIR_FLOAT; - type = PACK_TYPE_FLOAT; - size = 4; - flags |= PACK_FLAG_SIGNED | PACK_FLAG_GT; - break; - case 'H': - dir = PACK_DIR_HEX; - type = PACK_TYPE_STRING; - flags |= PACK_FLAG_COUNT2; - break; - case 'h': - dir = PACK_DIR_HEX; - type = PACK_TYPE_STRING; - flags |= PACK_FLAG_COUNT2 | PACK_FLAG_LSB; - break; - case 'B': - dir = PACK_DIR_BSTR; - type = PACK_TYPE_STRING; - flags |= PACK_FLAG_COUNT2; - break; - case 'b': - dir = PACK_DIR_BSTR; - type = PACK_TYPE_STRING; - flags |= PACK_FLAG_COUNT2 | PACK_FLAG_LSB; - break; case 'I': switch (sizeof(int)) { case 2: t = 'S'; goto alias; @@ -1338,115 +1711,33 @@ read_tmpl(mrb_state *mrb, struct tmpl *tmpl, enum pack_type *typep, mrb_int *siz mrb_raisef(mrb, E_RUNTIME_ERROR, "mruby-pack does not support sizeof(intptr_t) == %d", (int)sizeof(intptr_t)); } break; - case 'L': - dir = PACK_DIR_LONG; - type = PACK_TYPE_INTEGER; - size = 4; - break; - case 'l': - dir = PACK_DIR_LONG; - type = PACK_TYPE_INTEGER; - size = 4; - flags |= PACK_FLAG_SIGNED; - break; - case 'w': - dir = PACK_DIR_BER; - type = PACK_TYPE_INTEGER; - flags |= PACK_FLAG_SIGNED; - break; - case 'm': - dir = PACK_DIR_BASE64; - type = PACK_TYPE_STRING; - flags |= PACK_FLAG_WIDTH | PACK_FLAG_COUNT2; - break; - case 'M': - dir = PACK_DIR_QENC; - type = PACK_TYPE_STRING; - flags |= PACK_FLAG_WIDTH | PACK_FLAG_COUNT2; - break; - case 'N': /* = "L>" */ - dir = PACK_DIR_LONG; - type = PACK_TYPE_INTEGER; - size = 4; - flags |= PACK_FLAG_GT; - break; - case 'n': /* = "S>" */ - dir = PACK_DIR_SHORT; - type = PACK_TYPE_INTEGER; - size = 2; - flags |= PACK_FLAG_GT; - break; - case 'Q': - dir = PACK_DIR_QUAD; - type = PACK_TYPE_INTEGER; - size = 8; - break; - case 'q': - dir = PACK_DIR_QUAD; - type = PACK_TYPE_INTEGER; - size = 8; - flags |= PACK_FLAG_SIGNED; - break; - case 'S': - dir = PACK_DIR_SHORT; - type = PACK_TYPE_INTEGER; - size = 2; - break; - case 's': - dir = PACK_DIR_SHORT; - type = PACK_TYPE_INTEGER; - size = 2; - flags |= PACK_FLAG_SIGNED; - break; - case 'U': - dir = PACK_DIR_UTF8; - type = PACK_TYPE_INTEGER; - break; - case 'V': /* = "L<" */ - dir = PACK_DIR_LONG; - type = PACK_TYPE_INTEGER; - size = 4; - flags |= PACK_FLAG_LT; - break; - case 'v': /* = "S<" */ - dir = PACK_DIR_SHORT; - type = PACK_TYPE_INTEGER; - size = 2; - flags |= PACK_FLAG_LT; - break; - case 'x': - dir = PACK_DIR_NUL; - type = PACK_TYPE_NONE; - break; - case 'X': - dir = PACK_DIR_BACK; - type = PACK_TYPE_NONE; - break; - case '@': - dir = PACK_DIR_ABS; - type = PACK_TYPE_NONE; - break; - case 'Z': - dir = PACK_DIR_STR; - type = PACK_TYPE_STRING; - flags |= PACK_FLAG_WIDTH | PACK_FLAG_COUNT2 | PACK_FLAG_Z; - break; case '#': while (++tmpl->idx < tlen && tptr[tmpl->idx] != '\n') ; goto restart; - case 'p': case 'P': case '%': mrb_raisef(mrb, E_ARGUMENT_ERROR, "%c is not supported", (char)t); break; default: - if (!ISSPACE((char)t)) { - char c = (char)t; - mrb_value s = mrb_str_new(mrb, &c, 1); - mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown unpack directive %!v", s); + /* Use O(1) lookup table for standard format characters */ + if (t >= 0 && t < 256) { + format_info_t info_val = get_format_info((unsigned char)t); + const format_info_t *info = &info_val; + if (info->dir != PACK_DIR_NONE) { + /* Valid format character found in lookup table */ + dir = info->dir; + type = info->type; + size = info->size; + flags = info->base_flags; + break; + } } - goto restart; + + /* Handle invalid characters */ + char c = (char)t; + mrb_value s = mrb_str_new(mrb, &c, 1); + mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown unpack directive %!v", s); } /* read suffix [0-9*_!<>] */ @@ -1458,7 +1749,7 @@ read_tmpl(mrb_state *mrb, struct tmpl *tmpl, enum pack_type *typep, mrb_int *siz if (!mrb_read_int(tptr+tmpl->idx, tptr+tlen, &e, &n) || INT_MAX < n) { mrb_raise(mrb, E_RUNTIME_ERROR, "too big template length"); } - count = (int)n; + count = n; tmpl->idx = (int)(e - tptr); continue; } @@ -1539,16 +1830,17 @@ read_tmpl(mrb_state *mrb, struct tmpl *tmpl, enum pack_type *typep, mrb_int *siz static mrb_value mrb_pack_pack(mrb_state *mrb, mrb_value ary) { - mrb_value o, result; + mrb_value o; struct tmpl tmpl; enum pack_type type; - mrb_int count, size; + mrb_int count; + mrb_int size; unsigned int flags; enum pack_dir dir; prepare_tmpl(mrb, &tmpl); - result = mrb_str_new(mrb, NULL, 128); /* allocate initial buffer */ + mrb_value result = mrb_str_new(mrb, NULL, 128); /* allocate initial buffer */ mrb_int aidx = 0; mrb_int ridx = 0; while (has_tmpl(&tmpl)) { @@ -1558,7 +1850,7 @@ mrb_pack_pack(mrb_state *mrb, mrb_value ary) if (dir == PACK_DIR_NUL) { grow: if (ridx > INT_MAX - count) goto overflow; - ridx += pack_nul(mrb, result, ridx, count); + ridx += pack_nul(mrb, result, ridx, (int)count); continue; } else if (dir == PACK_DIR_BACK) { @@ -1599,7 +1891,9 @@ mrb_pack_pack(mrb_state *mrb, mrb_value ary) } } + /* Optimized dispatch using grouped format handling for better branch prediction */ switch (dir) { + /* Integer formats - all use (mrb, o, result, ridx, flags) signature */ case PACK_DIR_CHAR: ridx += pack_char(mrb, o, result, ridx, flags); break; @@ -1615,22 +1909,9 @@ mrb_pack_pack(mrb_state *mrb, mrb_value ary) case PACK_DIR_BER: ridx += pack_BER(mrb, o, result, ridx, flags); break; - case PACK_DIR_BASE64: - ridx += pack_base64(mrb, o, result, ridx, count); - break; - case PACK_DIR_QENC: - ridx += pack_qenc(mrb, o, result, ridx, count); - break; - case PACK_DIR_HEX: - ridx += pack_hex(mrb, o, result, ridx, count, flags); - break; - case PACK_DIR_BSTR: - ridx += pack_bstr(mrb, o, result, ridx, count, flags); - break; - case PACK_DIR_STR: - ridx += pack_str(mrb, o, result, ridx, count, flags); - break; + #ifndef MRB_NO_FLOAT + /* Float formats - all use (mrb, o, result, ridx, flags) signature */ case PACK_DIR_DOUBLE: ridx += pack_double(mrb, o, result, ridx, flags); break; @@ -1638,9 +1919,34 @@ mrb_pack_pack(mrb_state *mrb, mrb_value ary) ridx += pack_float(mrb, o, result, ridx, flags); break; #endif - case PACK_DIR_UTF8: - ridx += pack_utf8(mrb, o, result, ridx, count, flags); + + /* String formats with count - use (mrb, o, result, ridx, count, flags) signature */ + case PACK_DIR_HEX: + ridx += pack_hex(mrb, o, result, ridx, (int)count, flags); break; + case PACK_DIR_BSTR: + ridx += pack_bstr(mrb, o, result, ridx, (int)count, flags); + break; + case PACK_DIR_STR: + ridx += pack_str(mrb, o, result, ridx, (int)count, flags); + break; + + /* String formats with count only - use (mrb, o, result, ridx, count) signature */ + case PACK_DIR_BASE64: + ridx += pack_base64(mrb, o, result, ridx, (int)count); + break; + case PACK_DIR_UU: + ridx += pack_uu(mrb, o, result, ridx, (int)count); + break; + case PACK_DIR_QENC: + ridx += pack_qenc(mrb, o, result, ridx, (int)count); + break; + + /* UTF8 format - special signature (mrb, o, result, ridx, count, flags) */ + case PACK_DIR_UTF8: + ridx += pack_utf8(mrb, o, result, ridx, (int)count, flags); + break; + default: break; } @@ -1666,23 +1972,21 @@ mrb_pack_pack(mrb_state *mrb, mrb_value ary) static mrb_value pack_unpack(mrb_state *mrb, mrb_value str, mrb_bool single) { - mrb_value result; struct tmpl tmpl; mrb_int count; unsigned int flags; - enum pack_dir dir; enum pack_type type; - mrb_int size, srcidx, srclen; + mrb_int size; const unsigned char *sptr; prepare_tmpl(mrb, &tmpl); - srcidx = 0; - srclen = (int)RSTRING_LEN(str); + mrb_int srcidx = 0; + mrb_int srclen = RSTRING_LEN(str); - result = mrb_ary_new(mrb); + mrb_value result = mrb_ary_new(mrb); while (has_tmpl(&tmpl)) { - dir = read_tmpl(mrb, &tmpl, &type, &size, &count, &flags); + enum pack_dir dir = read_tmpl(mrb, &tmpl, &type, &size, &count, &flags); if (dir == PACK_DIR_NONE) break; if (dir == PACK_DIR_NUL) { @@ -1701,29 +2005,37 @@ pack_unpack(mrb_state *mrb, mrb_value str, mrb_bool single) continue; } - /* PACK_FLAG_COUNT2 directions */ + /* Optimized dispatch for PACK_FLAG_COUNT2 formats - grouped by signature */ sptr = (const unsigned char*)RSTRING_PTR(str) + srcidx; switch (dir) { + /* String formats with count and flags - (mrb, sptr, len, result, count, flags) */ case PACK_DIR_HEX: - srcidx += unpack_hex(mrb, sptr, srclen - srcidx, result, count, flags); + srcidx += unpack_hex(mrb, sptr, srclen - srcidx, result, (int)count, flags); if (single) goto single_return; continue; case PACK_DIR_BSTR: - srcidx += unpack_bstr(mrb, sptr, srclen - srcidx, result, count, flags); + srcidx += unpack_bstr(mrb, sptr, srclen - srcidx, result, (int)count, flags); if (single) goto single_return; continue; case PACK_DIR_STR: - srcidx += unpack_str(mrb, sptr, srclen - srcidx, result, count, flags); + srcidx += unpack_str(mrb, sptr, srclen - srcidx, result, (int)count, flags); if (single) goto single_return; continue; + + /* String formats without flags - (mrb, sptr, len, result) */ case PACK_DIR_BASE64: srcidx += unpack_base64(mrb, sptr, srclen - srcidx, result); if (single) goto single_return; continue; + case PACK_DIR_UU: + srcidx += unpack_uu(mrb, sptr, srclen - srcidx, result); + if (single) goto single_return; + continue; case PACK_DIR_QENC: srcidx += unpack_qenc(mrb, sptr, srclen - srcidx, result); if (single) goto single_return; continue; + default: break; } @@ -1737,7 +2049,9 @@ pack_unpack(mrb_state *mrb, mrb_value str, mrb_bool single) } sptr = (const unsigned char*)RSTRING_PTR(str) + srcidx; + /* Optimized dispatch for element-by-element formats - grouped by signature */ switch (dir) { + /* Integer formats - all use (mrb, sptr, len, result, flags) signature */ case PACK_DIR_CHAR: srcidx += unpack_char(mrb, sptr, srclen - srcidx, result, flags); break; @@ -1753,7 +2067,9 @@ pack_unpack(mrb_state *mrb, mrb_value str, mrb_bool single) case PACK_DIR_BER: srcidx += unpack_BER(mrb, sptr, srclen - srcidx, result, flags); break; + #ifndef MRB_NO_FLOAT + /* Float formats - all use (mrb, sptr, len, result, flags) signature */ case PACK_DIR_FLOAT: srcidx += unpack_float(mrb, sptr, srclen - srcidx, result, flags); break; @@ -1761,9 +2077,12 @@ pack_unpack(mrb_state *mrb, mrb_value str, mrb_bool single) srcidx += unpack_double(mrb, sptr, srclen - srcidx, result, flags); break; #endif + + /* UTF8 format - uses (mrb, sptr, len, result, flags) signature */ case PACK_DIR_UTF8: srcidx += unpack_utf8(mrb, sptr, srclen - srcidx, result, flags); break; + default: mrb_raise(mrb, E_RUNTIME_ERROR, "mruby-pack's bug"); } diff --git a/mrbgems/mruby-pack/test/pack.rb b/mrbgems/mruby-pack/test/pack.rb index a3f110ba8..61750e0b9 100644 --- a/mrbgems/mruby-pack/test/pack.rb +++ b/mrbgems/mruby-pack/test/pack.rb @@ -38,6 +38,35 @@ assert('pack("M")') do assert_equal ["あ"], "=E3=81=82=\n".unpack("M") end +# pack & unpack 'u' (UU-encode) +assert('pack("u")') do + # Basic string test with known good values + assert_pack "u", "-2&5L;&\\L(%=Oupper || MRB_PROC_CFUNC_P(proc->upper)) { env = NULL; proc = NULL; diff --git a/mrbgems/mruby-proc-ext/mrblib/proc.rb b/mrbgems/mruby-proc-ext/mrblib/proc.rb index 0c0df205e..4e7dfac57 100644 --- a/mrbgems/mruby-proc-ext/mrblib/proc.rb +++ b/mrbgems/mruby-proc-ext/mrblib/proc.rb @@ -1,17 +1,73 @@ class Proc + # + # call-seq: + # prc === obj -> result_of_proc + # + # Invokes the block with obj as the parameter like Proc#call. + # This allows a proc object to be the target of a when clause + # in a case statement. + # + # def the_answer + # 42 + # end + # + # case the_answer + # when proc { |x| x > 40 } + # "correct" + # else + # "incorrect" + # end + # #=> "correct" + # def ===(*args) call(*args) end + # + # call-seq: + # prc.yield(params,...) -> obj + # + # Invokes the block with the given arguments. This method is provided + # for compatibility and is equivalent to Proc#call. + # + # prc = proc { |x| x * 2 } + # prc.yield(5) #=> 10 + # def yield(*args) call(*args) end + # + # call-seq: + # prc.to_proc -> prc + # + # Part of the protocol for converting objects to Proc objects. + # Instances of class Proc simply return themselves. + # + # prc = proc { "hello" } + # prc.to_proc #=> # + # def to_proc self end + # + # call-seq: + # prc.curry -> curried_proc + # prc.curry(arity) -> curried_proc + # + # Returns a curried proc. If the optional arity argument is given, it + # determines the number of arguments. A curried proc receives some + # arguments. If a sufficient number of arguments are supplied, it passes + # the supplied arguments to the original proc and returns the result. + # Otherwise, returns another curried proc that takes the rest of arguments. + # + # b = proc {|x, y, z| (x||0) + (y||0) + (z||0) } + # p b.curry[1][2][3] #=> 6 + # p b.curry[1, 2][3, 4] #=> 6 + # p b.curry(5)[1][2][3][4][5] #=> 6 + # def curry(arity=self.arity) type = :proc abs = lambda {|a| a < 0 ? -a - 1 : a} @@ -39,10 +95,34 @@ class Proc make_curry.call end + # + # call-seq: + # prc << other_proc -> new_proc + # + # Returns a new Proc which is the composition of this proc and the given + # other_proc. The returned proc takes a variable number of arguments, calls + # other_proc with them then calls this proc with the result. + # + # f = proc {|x| x * x } + # g = proc {|x| x + x } + # p (f << g).call(2) #=> 16 + # def <<(other) ->(*args, **opts, &block) { call(other.call(*args, **opts, &block)) } end + # + # call-seq: + # prc >> other_proc -> new_proc + # + # Returns a new Proc which is the composition of this proc and the given + # other_proc. The returned proc takes a variable number of arguments, calls + # this proc with them then calls other_proc with the result. + # + # f = proc {|x| x * x } + # g = proc {|x| x + x } + # p (f >> g).call(2) #=> 8 + # def >>(other) ->(*args, **opts, &block) { other.call(call(*args, **opts, &block)) } end diff --git a/mrbgems/mruby-proc-ext/src/proc.c b/mrbgems/mruby-proc-ext/src/proc.c index 3fbce8bce..7d9bb21dd 100644 --- a/mrbgems/mruby-proc-ext/src/proc.c +++ b/mrbgems/mruby-proc-ext/src/proc.c @@ -10,9 +10,9 @@ * call-seq: * prc.lambda? -> true or false * - * Returns +true+ if +prc+ is a lambda, +false+ if it is a proc. - * The difference is how they react to a +return+ statement. In a lambda, - * +return+ makes the lambda return. In a proc, +return+ makes the method + * Returns `true` if `prc` is a lambda, `false` if it is a proc. + * The difference is how they react to a `return` statement. In a lambda, + * `return` makes the lambda return. In a proc, `return` makes the method * that called the proc return. * * def gen_times(factor) @@ -71,7 +71,7 @@ mrb_proc_source_location(mrb_state *mrb, const struct RProc *p) * prc.source_location -> [filename, line] or nil * * Returns the Ruby source filename and line number containing this proc - * or +nil+ if this proc was not defined in Ruby (i.e. native). + * or `nil` if this proc was not defined in Ruby (i.e. native). * * p = proc { puts "hello" } * p.source_location #=> ["prog.rb", 1] @@ -134,7 +134,7 @@ proc_inspect(mrb_state *mrb, mrb_value self) * call-seq: * proc { |...| block } -> a_proc * - * Equivalent to Proc.new. + * Equivalent to `Proc.new`. * * def proc(&block) * block diff --git a/mrbgems/mruby-proc-ext/test/proc.c b/mrbgems/mruby-proc-ext/test/proc.c index 1de8debe7..9a6bae3ca 100644 --- a/mrbgems/mruby-proc-ext/test/proc.c +++ b/mrbgems/mruby-proc-ext/test/proc.c @@ -35,11 +35,11 @@ static mrb_value cfunc_env_get(mrb_state *mrb, mrb_value self) { mrb_sym n; - const mrb_value *argv; mrb_int argc; - mrb_method_t m; - struct RProc *p; + const mrb_value *argv; + mrb_int argc; mrb_get_args(mrb, "na", &n, &argv, &argc); - p = mrb_proc_new_cfunc_with_env(mrb, return_env, argc, argv); + struct RProc *p = mrb_proc_new_cfunc_with_env(mrb, return_env, argc, argv); + mrb_method_t m; MRB_METHOD_FROM_PROC(m, p); mrb_define_method_raw(mrb, mrb_class_ptr(self), n, m); return self; diff --git a/mrbgems/mruby-random/README.md b/mrbgems/mruby-random/README.md index b57316b84..414dbe599 100644 --- a/mrbgems/mruby-random/README.md +++ b/mrbgems/mruby-random/README.md @@ -254,9 +254,18 @@ The `sample` method chooses one or more random elements from the array. ## Algorithm -The `mruby-random` mrbgem primarily utilizes the **xoshiro128++ 1.0** algorithm for pseudo-random number generation. +The `mruby-random` mrbgem uses the **PCG-XSH-RR** (Permuted Congruential Generator - XorShift High, Random Rotate) algorithm for pseudo-random number generation. -In certain 32-bit configurations (specifically when `MRB_INT32` is defined and `MRB_64BIT` is not), a different algorithm, **XORSHIFT96**, might be employed. +### Key Features + +- **Compact State**: Uses only 64 bits of state (compared to 128 bits in the previous xoshiro128++ implementation), reducing memory footprint by 50% +- **Excellent Statistical Quality**: Passes rigorous statistical test suites (TestU01, PractRand) +- **Platform-Optimized**: Automatically adapts to platform characteristics for optimal performance + - On **32-bit platforms** (`MRB_32BIT`): Uses an optimized 32-bit multiplier that requires only 2 multiply operations instead of 3 + - On **64-bit platforms**: Uses the standard 64-bit multiplier for maximum statistical quality +- **Fast Performance**: Competitive speed with modern PRNG algorithms while maintaining smaller memory footprint + +The PCG family of algorithms was developed by Melissa O'Neill and is widely used in production systems. For more details, see . ## License diff --git a/mrbgems/mruby-random/src/random.c b/mrbgems/mruby-random/src/random.c index aa9a0799e..31765996d 100644 --- a/mrbgems/mruby-random/src/random.c +++ b/mrbgems/mruby-random/src/random.c @@ -13,51 +13,59 @@ #include #include #include +#include #include -/* Written in 2019 by David Blackman and Sebastiano Vigna (vigna@acm.org) +/* PCG Random Number Generation + Based on the PCG family by Melissa O'Neill -To the extent possible under law, the author has dedicated all copyright -and related and neighboring rights to this software to the public domain -worldwide. This software is distributed without any warranty. - -See . */ - -/* This is xoshiro128++ 1.0, one of our 32-bit all-purpose, rock-solid - generators. It has excellent speed, a state size (128 bits) that is - large enough for mild parallelism, and it passes all tests we are aware - of. - - For generating just single-precision (i.e., 32-bit) floating-point - numbers, xoshiro128+ is even faster. - - The state must be seeded so that it is not everywhere zero. */ + This implements PCG-XSH-RR with 64-bit state and 32-bit output. + On 32-bit platforms, uses an optimized 32-bit multiplier for better + performance. On 64-bit platforms, uses the standard 64-bit multiplier + for maximum statistical quality. + See for details. */ +/* Platform-adaptive multiplier selection: + - 32-bit platforms: 0xf13283ad requires only 2 multiplies instead of 3 + - 64-bit platforms: standard multiplier for best statistical quality */ #ifdef MRB_32BIT -# define XORSHIFT96 -# define NSEEDS 3 -# define SEEDPOS 2 +# define PCG_MULTIPLIER 0xf13283adULL #else -# define NSEEDS 4 -# define SEEDPOS 0 +# define PCG_MULTIPLIER 6364136223846793005ULL #endif -#define LASTSEED (NSEEDS-1) +#define PCG_INCREMENT 1442695040888963407ULL typedef struct rand_state { - uint32_t seed[NSEEDS]; +#ifdef MRB_32BIT + /* On 32-bit platforms, split state to avoid alignment padding */ + uint32_t state_lo; + uint32_t state_hi; +#else + uint64_t state; +#endif + uint32_t seed_value; /* Track last seed for srand compatibility */ } rand_state; +/* Helper macros for 64-bit state access */ +#ifdef MRB_32BIT +# define GET_STATE(t) (((uint64_t)(t)->state_hi << 32) | (t)->state_lo) +# define SET_STATE(t, val) do { \ + uint64_t v_ = (val); \ + (t)->state_lo = (uint32_t)v_; \ + (t)->state_hi = (uint32_t)(v_ >> 32); \ + } while (0) +#else +# define GET_STATE(t) ((t)->state) +# define SET_STATE(t, val) ((t)->state = (val)) +#endif + static void rand_init(rand_state *t) { - t->seed[0] = 123456789; - t->seed[1] = 362436069; - t->seed[2] = 521288629; -#ifndef XORSHIFT96 - t->seed[3] = 88675123; -#endif + SET_STATE(t, 0x853c49e6748fea9bULL); + t->seed_value = 521288629; } static uint32_t rand_uint32(rand_state *state); @@ -65,54 +73,36 @@ static uint32_t rand_uint32(rand_state *state); static uint32_t rand_seed(rand_state *t, uint32_t seed) { - uint32_t old_seed = t->seed[SEEDPOS]; - rand_init(t); - t->seed[SEEDPOS] = seed; + uint32_t old_seed = t->seed_value; + + /* PCG initialization: state=0, step, add seed, step, then mix */ + SET_STATE(t, 0); + rand_uint32(t); + SET_STATE(t, GET_STATE(t) + seed); for (int i = 0; i < 10; i++) { rand_uint32(t); } + + t->seed_value = seed; return old_seed; } -#ifndef XORSHIFT96 -static inline uint32_t -rotl(const uint32_t x, int k) { - return (x << k) | (x >> (32 - k)); -} -#endif - static uint32_t -rand_uint32(rand_state *state) +rand_uint32(rand_state *rng) { -#ifdef XORSHIFT96 - uint32_t *seed = state->seed; - uint32_t x = seed[0]; - uint32_t y = seed[1]; - uint32_t z = seed[2]; - uint32_t t = (x ^ (x << 3)) ^ (y ^ (y >> 19)) ^ (z ^ (z << 6)); + /* PCG-XSH-RR: XorShift High (xorshift), then Random Rotate */ + uint64_t oldstate = GET_STATE(rng); - x = y; y = z; z = t; - seed[0] = x; - seed[1] = y; - seed[2] = z; + /* LCG step: advance internal state */ + SET_STATE(rng, oldstate * PCG_MULTIPLIER + PCG_INCREMENT); - return z; -#else - uint32_t *s = state->seed; - const uint32_t result = rotl(s[0] + s[3], 7) + s[0]; - const uint32_t t = s[1] << 9; + /* Output function: xorshift, then rotate by top bits */ + uint32_t xorshifted = (uint32_t)(((oldstate >> 18u) ^ oldstate) >> 27u); + uint32_t rot = (uint32_t)(oldstate >> 59u); - s[2] ^= s[0]; - s[3] ^= s[1]; - s[1] ^= s[2]; - s[0] ^= s[3]; - - s[2] ^= t; - s[3] = rotl(s[3], 11); - - return result; -#endif /* XORSHIFT96 */ - } + /* Rotate right by rot bits (handles rot=0 case correctly) */ + return (xorshifted >> rot) | (xorshifted << ((32 - rot) & 31)); +} #ifndef MRB_NO_FLOAT static double @@ -139,7 +129,14 @@ random_rand(mrb_state *mrb, rand_state *t, mrb_int max) static mrb_int rand_i(rand_state *t, mrb_int max) { - return rand_uint32(t) % max; + /* return uniform integer in [0, max) without modulo bias */ + if (max <= 0) return 0; + uint32_t threshold = (uint32_t)(-max) % (uint32_t)max; /* power-of-two fast path => 0 */ + uint32_t r; + do { + r = rand_uint32(t); + } while (r < threshold); + return (mrb_int)(r % (uint32_t)max); } static mrb_value @@ -210,6 +207,22 @@ random_rand_impl(mrb_state *mrb, rand_state *t, mrb_value self) return random_range(mrb, t, arg); } +#ifdef MRB_USE_BIGINT + if (mrb_bigint_p(arg)) { + if (mrb_bint_sign(mrb, arg) < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "negative value as random limit"); + } + mrb_int size = mrb_bint_size(mrb, arg); + mrb_value bytes = mrb_str_new(mrb, NULL, size); + uint8_t *p = (uint8_t*)RSTRING_PTR(bytes); + for (mrb_int i = 0; i < size; i++) { + p[i] = (uint8_t)rand_uint32(t); + } + mrb_value rand_bint = mrb_bint_from_bytes(mrb, p, size); + return mrb_bint_mod(mrb, rand_bint, arg); + } +#endif + range_error(mrb, arg); } @@ -324,11 +337,26 @@ random_m_bytes(mrb_state *mrb, mrb_value self) { rand_state *t = random_ptr(self); mrb_int i = mrb_as_int(mrb, mrb_get_arg1(mrb)); + if (i < 0) mrb_raise(mrb, E_ARGUMENT_ERROR, "negative string size"); mrb_value bytes = mrb_str_new(mrb, NULL, i); uint8_t *p = (uint8_t*)RSTRING_PTR(bytes); - for (; i > 0; i--, p++) { - *p = (uint8_t)rand_uint32(t); + /* write 4 bytes per PRNG call */ + while (i >= 4) { + uint32_t x = rand_uint32(t); + p[0] = (uint8_t)(x); + p[1] = (uint8_t)(x >> 8); + p[2] = (uint8_t)(x >> 16); + p[3] = (uint8_t)(x >> 24); + p += 4; + i -= 4; + } + if (i > 0) { + uint32_t x = rand_uint32(t); + while (i-- > 0) { + *p++ = (uint8_t)x; + x >>= 8; + } } return bytes; @@ -369,8 +397,9 @@ mrb_ary_shuffle_bang(mrb_state *mrb, mrb_value ary) mrb_get_args(mrb, ":", &kw); rand_state *random = check_random_arg(mrb, r); mrb_ary_modify(mrb, mrb_ary_ptr(ary)); - for (mrb_int i = RARRAY_LEN(ary) - 1; i > 0; i--) { - mrb_value *ptr = RARRAY_PTR(ary); + mrb_int len = RARRAY_LEN(ary); + mrb_value *ptr = RARRAY_PTR(ary); + for (mrb_int i = len - 1; i > 0; i--) { mrb_int j = rand_i(random, i + 1); mrb_value tmp = ptr[i]; ptr[i] = ptr[j]; @@ -402,13 +431,13 @@ mrb_ary_shuffle(mrb_state *mrb, mrb_value ary) * ary.sample -> obj * ary.sample(n) -> new_ary * - * Choose a random element or +n+ random elements from the array. + * Choose a random element or `n` random elements from the array. * * The elements are chosen by using random and unique indices into the array * in order to ensure that an element doesn't repeat itself unless the array * already contained duplicate elements. * - * If the array is empty the first form returns +nil+ and the second form + * If the array is empty the first form returns `nil` and the second form * returns an empty array. */ @@ -437,28 +466,25 @@ mrb_ary_sample(mrb_state *mrb, mrb_value ary) else { if (n < 0) mrb_raise(mrb, E_ARGUMENT_ERROR, "negative sample number"); if (n > len) n = len; - mrb_value result = mrb_ary_new_capa(mrb, n); - for (mrb_int i=0; i 0 ? n : 1)); + for (mrb_int i = 0; i < n; i++) { + mrb_int v; for (;;) { retry: - idx = rand_i(random, len); - - for (mrb_int j=0; j obj # rng.first(n) -> an_array # - # Returns the first object in the range, or an array of the first +n+ + # Returns the first object in the range, or an array of the first `n` # elements. # # (10..20).first #=> 10 @@ -33,10 +33,10 @@ class Range # rng.last(n) -> an_array # # Returns the last object in the range, - # or an array of the last +n+ elements. + # or an array of the last `n` elements. # - # Note that with no arguments +last+ will return the object that defines - # the end of the range even if #exclude_end? is +true+. + # Note that with no arguments `last` will return the object that defines + # the end of the range even if #exclude_end? is `true`. # # (10..20).last #=> 20 # (10...20).last #=> 20 diff --git a/mrbgems/mruby-rational/mrblib/rational.rb b/mrbgems/mruby-rational/mrblib/rational.rb index 688c660ab..153dd48f6 100644 --- a/mrbgems/mruby-rational/mrblib/rational.rb +++ b/mrbgems/mruby-rational/mrblib/rational.rb @@ -1,12 +1,46 @@ class Rational < Numeric + # + # call-seq: + # rat.inspect -> string + # + # Returns the value as a string for inspection. + # + # Rational(2).inspect #=> "(2/1)" + # Rational(-8, 6).inspect #=> "(-4/3)" + # Rational(1, 2).inspect #=> "(1/2)" + # def inspect "(#{to_s})" end + # + # call-seq: + # rat.to_s -> string + # + # Returns the value as a string. + # + # Rational(2).to_s #=> "2/1" + # Rational(-8, 6).to_s #=> "-4/3" + # Rational(1, 2).to_s #=> "1/2" + # def to_s "#{numerator}/#{denominator}" end + # + # call-seq: + # rat <=> numeric -> -1, 0, +1, or nil + # + # Returns -1, 0, or +1 depending on whether rat is less than, equal to, + # or greater than numeric. This is the basis for the tests in the Comparable module. + # Returns nil if the two values are incomparable. + # + # Rational(2, 3) <=> Rational(2, 3) #=> 0 + # Rational(5) <=> 5 #=> 0 + # Rational(2, 3) <=> Rational(1, 3) #=> 1 + # Rational(1, 3) <=> 1 #=> -1 + # Rational(1, 3) <=> 0.3 #=> 1 + # def <=>(other) return nil unless other.kind_of?(Numeric) self.to_f <=> other.to_f @@ -16,6 +50,16 @@ class Rational < Numeric end class Numeric + # + # call-seq: + # num.to_r -> rational + # + # Returns the value as a rational. + # + # 1.to_r #=> (1/1) + # (1+2i).to_r #=> (1+2i)/1) + # nil.to_r #=> TypeError + # def to_r Rational(self, 1) end diff --git a/mrbgems/mruby-rational/src/rational.c b/mrbgems/mruby-rational/src/rational.c index 9421067e3..6e0a02393 100644 --- a/mrbgems/mruby-rational/src/rational.c +++ b/mrbgems/mruby-rational/src/rational.c @@ -110,6 +110,16 @@ rat_numerator(mrb_state *mrb, mrb_value self) return mrb_int_value(mrb, p->numerator); } +/* + * call-seq: + * rational.numerator -> integer + * + * Returns the numerator of the rational number. + * + * Rational(3, 4).numerator #=> 3 + * Rational(-2, 5).numerator #=> -2 + * Rational(6, 8).numerator #=> 3 (reduced form) + */ /* normalized version of rat_numerator() */ static mrb_value rational_numerator(mrb_state *mrb, mrb_value self) @@ -134,6 +144,17 @@ rat_denominator(mrb_state *mrb, mrb_value self) return mrb_int_value(mrb, p->denominator); } +/* + * call-seq: + * rational.denominator -> integer + * + * Returns the denominator of the rational number. + * The denominator is always positive. + * + * Rational(3, 4).denominator #=> 4 + * Rational(-2, 5).denominator #=> 5 + * Rational(6, 8).denominator #=> 4 (reduced form) + */ /* normalized version of rat_denominator() */ static mrb_value rational_denominator(mrb_state *mrb, mrb_value self) @@ -378,6 +399,16 @@ mrb_rational_to_f(mrb_state *mrb, mrb_value self) } #endif +/* + * call-seq: + * rational.to_i -> integer + * + * Returns the rational number truncated to an integer. + * + * Rational(3, 4).to_i #=> 0 + * Rational(7, 3).to_i #=> 2 + * Rational(-5, 2).to_i #=> -2 + */ mrb_value mrb_rational_to_i(mrb_state *mrb, mrb_value self) { @@ -414,6 +445,16 @@ mrb_as_rational(mrb_state *mrb, mrb_value x) } } +/* + * call-seq: + * rational.negative? -> true or false + * + * Returns true if the rational number is negative, false otherwise. + * + * Rational(-1, 2).negative? #=> true + * Rational(1, 2).negative? #=> false + * Rational(0, 1).negative? #=> false + */ static mrb_value rational_negative_p(mrb_state *mrb, mrb_value self) { @@ -428,6 +469,17 @@ rational_negative_p(mrb_state *mrb, mrb_value self) } #ifndef MRB_NO_FLOAT +/* + * call-seq: + * float.to_r -> rational + * + * Converts the float to a rational number. The conversion preserves + * the exact value of the float as a fraction. + * + * 0.5.to_r #=> Rational(1, 2) + * 0.25.to_r #=> Rational(1, 4) + * 1.5.to_r #=> Rational(3, 2) + */ static mrb_value float_to_r(mrb_state *mrb, mrb_value self) { @@ -435,6 +487,16 @@ float_to_r(mrb_state *mrb, mrb_value self) } #endif +/* + * call-seq: + * integer.to_r -> rational + * + * Converts the integer to a rational number with denominator 1. + * + * 5.to_r #=> Rational(5, 1) + * (-3).to_r #=> Rational(-3, 1) + * 0.to_r #=> Rational(0, 1) + */ static mrb_value int_to_r(mrb_state *mrb, mrb_value self) { @@ -446,6 +508,14 @@ int_to_r(mrb_state *mrb, mrb_value self) return rational_new_i(mrb, mrb_integer(self), 1); } +/* + * call-seq: + * nil.to_r -> rational + * + * Converts nil to Rational(0, 1). + * + * nil.to_r #=> Rational(0, 1) + */ static mrb_value nil_to_r(mrb_state *mrb, mrb_value self) { @@ -477,6 +547,18 @@ rational_new(mrb_state *mrb, mrb_value a, mrb_value b) #endif } +/* + * call-seq: + * Rational(numerator, denominator = 1) -> rational + * + * Creates a rational number from numerator and denominator. + * The rational is automatically reduced to lowest terms. + * + * Rational(1, 2) #=> Rational(1, 2) + * Rational(6, 8) #=> Rational(3, 4) + * Rational(5) #=> Rational(5, 1) + * Rational(-2, 4) #=> Rational(-1, 2) + */ static mrb_value rational_m(mrb_state *mrb, mrb_value self) { @@ -487,6 +569,18 @@ rational_m(mrb_state *mrb, mrb_value self) #else +/* + * call-seq: + * Rational(numerator, denominator = 1) -> rational + * + * Creates a rational number from numerator and denominator. + * The rational is automatically reduced to lowest terms. + * + * Rational(1, 2) #=> Rational(1, 2) + * Rational(6, 8) #=> Rational(3, 4) + * Rational(5) #=> Rational(5, 1) + * Rational(-2, 4) #=> Rational(-1, 2) + */ static mrb_value rational_m(mrb_state *mrb, mrb_value self) { @@ -547,6 +641,17 @@ rational_eq_b(mrb_state *mrb, mrb_value x, mrb_value y) return mrb_bool_value(result); } +/* + * call-seq: + * rational == other -> true or false + * + * Returns true if rational equals other. Comparison is done by cross-multiplication + * to avoid floating point precision issues. + * + * Rational(1, 2) == Rational(2, 4) #=> true + * Rational(1, 2) == 0.5 #=> true + * Rational(1, 2) == Rational(1, 3) #=> false + */ static mrb_value rational_eq(mrb_state *mrb, mrb_value x) { @@ -602,6 +707,15 @@ rational_eq(mrb_state *mrb, mrb_value x) return mrb_bool_value(result); } +/* + * call-seq: + * -rational -> rational + * + * Returns the negation of the rational number. + * + * -Rational(1, 2) #=> Rational(-1, 2) + * -Rational(-3, 4) #=> Rational(3, 4) + */ static mrb_value rational_minus(mrb_state *mrb, mrb_value x) { @@ -712,6 +826,17 @@ mrb_rational_add(mrb_state *mrb, mrb_value x, mrb_value y) } } +/* + * call-seq: + * rational + numeric -> rational or numeric + * + * Returns the sum of rational and numeric. If numeric is a rational, + * returns a rational. If numeric is a float, returns a float. + * + * Rational(1, 2) + Rational(1, 3) #=> Rational(5, 6) + * Rational(1, 2) + 1 #=> Rational(3, 2) + * Rational(1, 2) + 0.5 #=> 1.0 + */ static mrb_value rational_add(mrb_state *mrb, mrb_value x) { @@ -806,6 +931,17 @@ mrb_rational_sub(mrb_state *mrb, mrb_value x, mrb_value y) } } +/* + * call-seq: + * rational - numeric -> rational or numeric + * + * Returns the difference of rational and numeric. If numeric is a rational, + * returns a rational. If numeric is a float, returns a float. + * + * Rational(1, 2) - Rational(1, 3) #=> Rational(1, 6) + * Rational(3, 2) - 1 #=> Rational(1, 2) + * Rational(1, 2) - 0.25 #=> 0.25 + */ static mrb_value rational_sub(mrb_state *mrb, mrb_value x) { @@ -893,6 +1029,17 @@ mrb_rational_mul(mrb_state *mrb, mrb_value x, mrb_value y) } } +/* + * call-seq: + * rational * numeric -> rational or numeric + * + * Returns the product of rational and numeric. Uses standard rational + * multiplication: (a/b) * (c/d) = (a*c)/(b*d). + * + * Rational(1, 2) * Rational(2, 3) #=> Rational(1, 3) + * Rational(1, 2) * 3 #=> Rational(3, 2) + * Rational(1, 2) * 2.0 #=> 1.0 + */ static mrb_value rational_mul(mrb_state *mrb, mrb_value x) { @@ -984,6 +1131,18 @@ mrb_rational_div(mrb_state *mrb, mrb_value x, mrb_value y) } } +/* + * call-seq: + * rational / numeric -> rational or numeric + * rational.quo(numeric) -> rational or numeric + * + * Returns the quotient of rational divided by numeric. Uses standard rational + * division: (a/b) / (c/d) = (a/b) * (d/c) = (a*d)/(b*c). + * + * Rational(1, 2) / Rational(1, 3) #=> Rational(3, 2) + * Rational(3, 4) / 2 #=> Rational(3, 8) + * Rational(1, 2) / 0.5 #=> 1.0 + */ static mrb_value rational_div(mrb_state *mrb, mrb_value x) { @@ -993,6 +1152,17 @@ rational_div(mrb_state *mrb, mrb_value x) mrb_value mrb_int_pow(mrb_state *mrb, mrb_value x, mrb_value y); +/* + * call-seq: + * rational ** numeric -> numeric + * + * Returns rational raised to the power of numeric. The result is typically + * a float unless the result can be exactly represented as a rational. + * + * Rational(1, 2) ** 2 #=> Rational(1, 4) + * Rational(4, 1) ** 0.5 #=> 2.0 + * Rational(2, 1) ** 3 #=> Rational(8, 1) + */ static mrb_value rational_pow(mrb_state *mrb, mrb_value x) { @@ -1019,6 +1189,15 @@ rational_pow(mrb_state *mrb, mrb_value x) #endif } +/* + * call-seq: + * rational.hash -> integer + * + * Returns a hash value for the rational number. Two rationals with + * the same value will have the same hash value. + * + * Rational(1, 2).hash == Rational(2, 4).hash #=> true + */ static mrb_value rational_hash(mrb_state *mrb, mrb_value rat) { @@ -1051,7 +1230,7 @@ void mrb_mruby_rational_gem_init(mrb_state *mrb) mrb_define_method_id(mrb, rat, MRB_SYM(to_f), mrb_rational_to_f, MRB_ARGS_NONE()); #endif mrb_define_method_id(mrb, rat, MRB_SYM(to_i), mrb_rational_to_i, MRB_ARGS_NONE()); - mrb_define_method_id(mrb, rat, MRB_SYM(to_r), mrb_obj_itself, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, rat, MRB_SYM(to_r), mrb_obj_itself, MRB_ARGS_NONE()); /* Returns self - already a rational */ mrb_define_method_id(mrb, rat, MRB_SYM_Q(negative), rational_negative_p, MRB_ARGS_NONE()); mrb_define_method_id(mrb, rat, MRB_OPSYM(eq), rational_eq, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, rat, MRB_OPSYM(minus), rational_minus, MRB_ARGS_NONE()); diff --git a/mrbgems/mruby-set/mrbgem.rake b/mrbgems/mruby-set/mrbgem.rake index 1533ebc32..1495b34ba 100644 --- a/mrbgems/mruby-set/mrbgem.rake +++ b/mrbgems/mruby-set/mrbgem.rake @@ -1,6 +1,7 @@ MRuby::Gem::Specification.new('mruby-set') do |spec| spec.license = 'MIT' spec.authors = 'yui-knk' + spec.summary = 'Set class' spec.build.defines << "MRB_USE_SET" spec.add_dependency "mruby-hash-ext", :core => "mruby-hash-ext" diff --git a/mrbgems/mruby-set/src/set.c b/mrbgems/mruby-set/src/set.c index 20022482c..a8a02d0df 100644 --- a/mrbgems/mruby-set/src/set.c +++ b/mrbgems/mruby-set/src/set.c @@ -14,375 +14,54 @@ #include #include #include +#include -/* Compact set implementation - memory optimized for struct RSet embedding */ -typedef uint32_t kset_int_t; -typedef kset_int_t kset_iter_t; +/* Use khash.h for set implementation - set mode (no values, only keys) */ +KHASH_DECLARE(set_val, mrb_value, char, FALSE) /* FALSE = set mode */ -#ifndef KSET_DEFAULT_SIZE -# define KSET_DEFAULT_SIZE 8 -#endif -#define KSET_MIN_SIZE 8 - -#define KSET_UPPER_BOUND(x) ((x)>>2|(x)>>1) - -/* Flag masks for empty/deleted status - 2 bits per bucket */ -static const uint8_t kset_empty_mask[] = {0x02, 0x08, 0x20, 0x80}; -static const uint8_t kset_del_mask[] = {0x01, 0x04, 0x10, 0x40}; -static const uint8_t kset_either_mask[] = {0x03, 0x0c, 0x30, 0xc0}; - -#define KSET_IS_EMPTY(flags, i) (flags[(i)/4] & kset_empty_mask[(i)%4]) -#define KSET_IS_DEL(flags, i) (flags[(i)/4] & kset_del_mask[(i)%4]) -#define KSET_IS_EITHER(flags, i) (flags[(i)/4] & kset_either_mask[(i)%4]) - -#define kset_power2(v) do { \ - v--; \ - v |= v >> 1; \ - v |= v >> 2; \ - v |= v >> 4; \ - v |= v >> 8; \ - v |= v >> 16; \ - v++; \ -} while (0) - -#define kset_mask(s) ((s)->n_buckets - 1) -#define kset_upper_bound(s) (KSET_UPPER_BOUND((s)->n_buckets)) -#define kset_end(s) ((s)->n_buckets) - -/* Compact set structure - exactly 3 pointers in size */ -typedef struct kset { - void *data; /* Combined keys + flags memory block */ - kset_int_t n_buckets; /* Number of buckets (power of 2) */ - kset_int_t size; /* Number of elements */ -} kset_t; - -/* Memory layout: [keys...][flags...] */ -#define kset_keys(s) ((mrb_value*)(s)->data) -#define kset_flags(s) ((uint8_t*)((s)->data) + sizeof(mrb_value) * (s)->n_buckets) - -/* Fill flags with pattern */ -static inline void -kset_fill_flags(uint8_t *p, uint8_t c, size_t len) -{ - while (len-- > 0) { - *p++ = c; - } -} - -/* Hash function for mrb_value */ -static inline kset_int_t +/* Hash and equality functions for mrb_value keys */ +static inline khint_t kset_hash_value(mrb_state *mrb, mrb_value key) { - return (kset_int_t)mrb_obj_hash_code(mrb, key); + return (khint_t)mrb_obj_hash_code(mrb, key); } -/* Equality function for mrb_value */ static inline mrb_bool kset_equal_value(mrb_state *mrb, mrb_value a, mrb_value b) { return mrb_eql(mrb, a, b); } -/* - * Inserts a key into the provided hash table arrays (keys and flags). - * This function encapsulates the core logic of finding a slot and inserting the key. - * - * Parameters: - * mrb: mrb_state pointer - * key: mrb_value to insert - * keys_array: pointer to the keys array - * flags_array: pointer to the flags array - * n_buckets_val: number of buckets in the arrays - * size_ptr: pointer to the size counter, which is incremented on successful insertion of a new element - * ret_status: pointer to an int to store the status of the operation. - * - 0 if the key already exists. - * - 1 if the key was inserted into a new empty slot. - * - 2 if the key was inserted into a previously deleted slot. - * If NULL, status is not reported. - * - * Returns: - * The iterator (index) of the key in the keys_array. - */ -static inline kset_iter_t -kset_raw_put(mrb_state *mrb, mrb_value key, mrb_value *keys_array, uint8_t *flags_array, - kset_int_t n_buckets_val, kset_int_t *size_ptr, int *ret_status) -{ - kset_int_t k, del_k, step = 0; - kset_int_t mask = n_buckets_val - 1; +KHASH_DEFINE(set_val, mrb_value, char, FALSE, kset_hash_value, kset_equal_value) - k = kset_hash_value(mrb, key) & mask; - del_k = n_buckets_val; /* Represents an invalid/not-found slot initially */ +#define KSET_INITIAL_SIZE 4 +#define GOLDEN_RATIO_PRIME 0x9e3779b97f4a7c15ULL - while (!KSET_IS_EMPTY(flags_array, k)) { - if (!KSET_IS_DEL(flags_array, k)) { - if (kset_equal_value(mrb, keys_array[k], key)) { - if (ret_status != NULL) { *ret_status = 0; } /* Key already exists */ - return k; - } - } - else if (del_k == n_buckets_val) { /* Found a deleted slot, mark it if not already marked */ - del_k = k; - } - k = (k + (++step)) & mask; - } +/* Compatibility layer and type definitions */ +typedef kh_set_val_t kset_t; +typedef khint_t kset_iter_t; - if (del_k != n_buckets_val) { - /* Use the previously found deleted slot */ - keys_array[del_k] = key; - flags_array[del_k/4] &= ~kset_del_mask[del_k%4]; /* Clear only the deleted flag bit */ - (*size_ptr)++; - if (ret_status != NULL) { *ret_status = 2; } /* Used deleted slot */ - return del_k; - } - else { - /* Use the new empty slot found */ - keys_array[k] = key; - flags_array[k/4] &= ~kset_empty_mask[k%4]; /* Clear only the empty flag bit */ - (*size_ptr)++; - if (ret_status != NULL) { *ret_status = 1; } /* Used empty slot */ - return k; - } -} +/* API Aliases to khash.h */ +#define kset_init(mrb) kh_init(set_val, mrb) +#define kset_init_data(mrb, s, sz) kh_init_data(set_val, mrb, s, sz) +#define kset_destroy_data(mrb, s) kh_destroy_data(set_val, mrb, s) +#define kset_clear(mrb, s) kh_clear(set_val, mrb, s) +#define kset_resize(mrb, s, sz) kh_resize(set_val, mrb, s, sz) +#define kset_put(mrb, s, k) kh_put(set_val, mrb, s, k) +#define kset_put2(mrb, s, k, r) kh_put2(set_val, mrb, s, k, r) +#define kset_get(mrb, s, k) kh_get(set_val, mrb, s, k) +#define kset_del(mrb, s, k) kh_del(set_val, mrb, s, k) +#define kset_exist(s, k) kh_exist(set_val, s, k) +#define kset_key(s, k) kh_key(set_val, s, k) +#define kset_size(s) kh_size(s) +#define kset_end(s) kh_end(s) +#define kset_is_end(s, k) kh_is_end(s, k) -/* Convenience macros for common operations */ -#define kset_is_uninitialized(s) (!(s)->data) -#define kset_is_empty(s) (!(s)->data || (s)->size == 0) +#define KSET_FOREACH(s, k) KHASH_FOREACH(set_val, s, k) -/* Macro for iterating over all elements in a kset */ -#define KSET_FOREACH(s, k) \ - for (kset_iter_t k = 0; k != kset_end(s); k++) \ - if (kset_exist(s, k)) - -/* Initialize set with specific size */ -static kset_t* -kset_init_size(mrb_state *mrb, kset_int_t size) -{ - kset_t *s = (kset_t*)mrb_calloc(mrb, 1, sizeof(kset_t)); - - if (size < KSET_MIN_SIZE) { - size = KSET_MIN_SIZE; - } - kset_power2(size); - - s->n_buckets = size; - s->size = 0; - - /* Allocate combined memory block for keys and flags */ - size_t keys_size = sizeof(mrb_value) * size; - size_t flags_size = size / 4; - s->data = mrb_malloc(mrb, keys_size + flags_size); - - /* Initialize flags to empty (0xaa pattern) */ - kset_fill_flags(kset_flags(s), 0xaa, flags_size); - - return s; -} - -/* Initialize empty set */ -static kset_t* -kset_init(mrb_state *mrb) -{ - return kset_init_size(mrb, KSET_DEFAULT_SIZE); -} - -/* Destroy set */ -static void -kset_destroy(mrb_state *mrb, kset_t *s) -{ - if (s) { - if (s->data) { - mrb_free(mrb, s->data); - } - mrb_free(mrb, s); - } -} - -/* Clear set */ -static void -kset_clear(mrb_state *mrb, kset_t *s) -{ - (void)mrb; - if (s && s->data) { - kset_fill_flags(kset_flags(s), 0xaa, s->n_buckets / 4); - s->size = 0; - } -} - -/* Find key in set */ -static kset_iter_t -kset_get(mrb_state *mrb, kset_t *s, mrb_value key) -{ - kset_int_t k = kset_hash_value(mrb, key) & kset_mask(s); - kset_int_t step = 0; - uint8_t *flags = kset_flags(s); - mrb_value *keys = kset_keys(s); - - while (!KSET_IS_EMPTY(flags, k)) { - if (!KSET_IS_DEL(flags, k)) { - if (kset_equal_value(mrb, keys[k], key)) { - return k; - } - } - k = (k + (++step)) & kset_mask(s); - } - return kset_end(s); -} - -/* Resize set */ -static void -kset_resize(mrb_state *mrb, kset_t *s, kset_int_t new_n_buckets) -{ - if (new_n_buckets < KSET_MIN_SIZE) { - new_n_buckets = KSET_MIN_SIZE; - } - kset_power2(new_n_buckets); - - if (s->n_buckets == new_n_buckets) return; /* No change needed */ - - /* Save old data references */ - void *old_data_ptr = s->data; - kset_int_t old_n_buckets = s->n_buckets; - mrb_value *old_keys = (mrb_value*)old_data_ptr; /* Equivalent to kset_keys(s) before s->data is changed */ - uint8_t *old_flags = (uint8_t*)old_data_ptr + sizeof(mrb_value) * old_n_buckets; /* Equivalent to kset_flags(s) */ - - /* Allocate new data block */ - size_t new_keys_bytes = sizeof(mrb_value) * new_n_buckets; - size_t new_flags_bytes = new_n_buckets / 4; - void *new_data_ptr = mrb_malloc(mrb, new_keys_bytes + new_flags_bytes); - - mrb_value *new_keys = (mrb_value*)new_data_ptr; - uint8_t *new_flags = (uint8_t*)new_data_ptr + new_keys_bytes; - - /* Initialize new flags to empty (0xaa pattern) */ - kset_fill_flags(new_flags, 0xaa, new_flags_bytes); - - kset_int_t new_size = 0; - kset_iter_t dummy_iter; /* kset_raw_put requires an iterator, but it's not used here */ - - /* Rehash old elements into the new data arrays */ - /* Iterate only if old_data_ptr is valid (set was not empty/uninitialized) */ - if (old_data_ptr) { - for (kset_int_t i = 0; i < old_n_buckets; i++) { - if (!KSET_IS_EITHER(old_flags, i)) { - /* Use kset_raw_put to insert the key into new_keys and new_flags */ - /* Pass NULL for ret_status as kset_resize doesn't use the status */ - dummy_iter = kset_raw_put(mrb, old_keys[i], new_keys, new_flags, new_n_buckets, &new_size, NULL); - } - } - } - (void)dummy_iter; /* Mark as intentionally unused to suppress warning */ - - /* Free the old data block */ - if (old_data_ptr) { - mrb_free(mrb, old_data_ptr); - } - - /* Update the set structure with the new data block and properties */ - s->data = new_data_ptr; - s->n_buckets = new_n_buckets; - s->size = new_size; -} - -/* Resize set (rehash with current bucket size, mainly for re-compacting deleted slots) */ -static void -kset_rehash(mrb_state *mrb, kset_t *s) -{ - kset_resize(mrb, s, s->n_buckets); -} - -/* Add key to set with return status */ -static kset_iter_t -kset_put2(mrb_state *mrb, kset_t *s, mrb_value key, int *ret) -{ - kset_iter_t result_iter; - - if (s->size >= kset_upper_bound(s)) { - kset_resize(mrb, s, s->n_buckets * 2); - } - - /* Use the kset_raw_put function to handle the insertion logic */ - result_iter = kset_raw_put(mrb, key, kset_keys(s), kset_flags(s), s->n_buckets, &s->size, ret); - - return result_iter; -} - -/* Add key to set */ -static kset_iter_t -kset_put(mrb_state *mrb, kset_t *s, mrb_value key) -{ - return kset_put2(mrb, s, key, NULL); -} - -/* Delete key from set */ -static void -kset_del(mrb_state *mrb, kset_t *s, kset_iter_t x) -{ - (void)mrb; - mrb_assert(x != s->n_buckets && !KSET_IS_EITHER(kset_flags(s), x)); - kset_flags(s)[x/4] |= kset_del_mask[x%4]; - s->size--; -} - -/* Check if iterator exists */ -static inline mrb_bool -kset_exist(kset_t *s, kset_iter_t x) -{ - return !KSET_IS_EITHER(kset_flags(s), x); -} - -/* Get key at iterator */ -static inline mrb_value -kset_key(kset_t *s, kset_iter_t x) -{ - return kset_keys(s)[x]; -} - -/* Initialize embedded set */ -static void -kset_init_embedded(mrb_state *mrb, kset_t *s) -{ - kset_int_t size = KSET_DEFAULT_SIZE; - if (size < KSET_MIN_SIZE) { - size = KSET_MIN_SIZE; - } - kset_power2(size); - - s->n_buckets = size; - s->size = 0; - - /* Allocate combined memory block for keys and flags */ - size_t keys_size = sizeof(mrb_value) * size; - size_t flags_size = size / 4; - s->data = mrb_malloc(mrb, keys_size + flags_size); - - /* Initialize flags to empty (0xaa pattern) */ - kset_fill_flags(kset_flags(s), 0xaa, flags_size); -} - -/* Destroy embedded set */ -static void -kset_destroy_embedded(mrb_state *mrb, kset_t *s) -{ - if (s && s->data) { - mrb_free(mrb, s->data); - s->data = NULL; - s->n_buckets = 0; - s->size = 0; - } -} - -/* Copy elements from one set to another */ -static void -kset_copy_elements(mrb_state *mrb, kset_t *target, kset_t *source) -{ - if (!source || !target) return; - - int ai = mrb_gc_arena_save(mrb); - KSET_FOREACH(source, k) { - kset_put(mrb, target, kset_key(source, k)); - mrb_gc_arena_restore(mrb, ai); - } -} +/* Helper macros for set state checking */ +#define kset_is_uninitialized(s) ((s)->data == NULL) +#define kset_is_empty(s) (kset_is_uninitialized(s) || kset_size(s) == 0) /* Embedded set structure in RSet - exactly 3 pointers */ struct RSet { @@ -402,6 +81,25 @@ set_get_kset(mrb_state *mrb, mrb_value self) return &mrb_set_ptr(self)->set; } +/* Get RSet pointer from embedded kset_t pointer */ +#define kset_to_rset(kset) ((struct RBasic*)((char*)(kset) - offsetof(struct RSet, set))) + +/* Copy all elements from src to dst (merge operation) */ +static void +kset_copy_merge(mrb_state *mrb, kset_t *dst, kset_t *src) +{ + if (!kset_is_empty(src)) { + struct RBasic *dst_obj = kset_to_rset(dst); + int ai = mrb_gc_arena_save(mrb); + KSET_FOREACH(src, k) { + mrb_value key = kset_key(src, k); + kset_put(mrb, dst, key); + mrb_field_write_barrier_value(mrb, dst_obj, key); + mrb_gc_arena_restore(mrb, ai); + } + } +} + /* Helper function to ensure set is initialized */ static void set_ensure_initialized(mrb_state *mrb, kset_t *set) @@ -429,16 +127,17 @@ void mrb_gc_free_set(mrb_state *mrb, struct RBasic *obj) { struct RSet *s = (struct RSet*)obj; - kset_destroy_embedded(mrb, &s->set); + kset_destroy_data(mrb, &s->set); } size_t mrb_set_memsize(mrb_value set) { - size_t size = mrb_objspace_page_slot_size(); + size_t size = sizeof(struct RSet); struct RSet *s = mrb_set_ptr(set); kset_t *kset = &s->set; if (kset->data) { + /* New khash layout: keys + flags in single allocation */ size += sizeof(mrb_value) * kset->n_buckets; /* keys */ size += kset->n_buckets / 4; /* flags */ } @@ -465,7 +164,10 @@ static mrb_value set_init(mrb_state *mrb, mrb_value self) { kset_t *set = set_get_kset(mrb, self); - kset_init_embedded(mrb, set); + if (!kset_is_uninitialized(set)) { + mrb_raise(mrb, E_RUNTIME_ERROR, "already initialized set"); + } + kset_init_data(mrb, set, KSET_INITIAL_SIZE); return self; } @@ -490,8 +192,12 @@ set_init_copy(mrb_state *mrb, mrb_value self) set_ensure_initialized(mrb, orig_set); kset_t *self_set = set_get_kset(mrb, self); - kset_init_embedded(mrb, self_set); - kset_copy_elements(mrb, self_set, orig_set); + /* Free existing data if already initialized (for replace semantics) */ + if (!kset_is_uninitialized(self_set)) { + kset_destroy_data(mrb, self_set); + } + kset_init_data(mrb, self_set, kset_size(orig_set)); + kh_replace(set_val, mrb, self_set, orig_set); return self; } @@ -508,7 +214,7 @@ set_size(mrb_state *mrb, mrb_value self) { kset_t *set = set_get_kset(mrb, self); if (kset_is_empty(set)) return mrb_fixnum_value(0); - return mrb_fixnum_value(set->size); + return mrb_fixnum_value(kset_size(set)); } /* @@ -553,7 +259,7 @@ set_to_a(mrb_state *mrb, mrb_value self) if (kset_is_empty(set)) return mrb_ary_new(mrb); - mrb_value ary = mrb_ary_new_capa(mrb, set->size); + mrb_value ary = mrb_ary_new_capa(mrb, kset_size(set)); int ai = mrb_gc_arena_save(mrb); KSET_FOREACH(set, k) { @@ -579,8 +285,7 @@ set_include_p(mrb_state *mrb, mrb_value self) kset_t *set = set_get_kset(mrb, self); if (kset_is_empty(set)) return mrb_false_value(); - kset_iter_t k = kset_get(mrb, set, obj); - return mrb_bool_value(k != kset_end(set)); + return mrb_bool_value(!kset_is_end(set, kset_get(mrb, set, obj))); } /* @@ -598,6 +303,7 @@ set_add(mrb_state *mrb, mrb_value self) set_ensure_initialized(mrb, set); kset_put(mrb, set, obj); + mrb_field_write_barrier_value(mrb, kset_to_rset(set), obj); return self; } @@ -617,6 +323,7 @@ set_add_p(mrb_state *mrb, mrb_value self) int ret; kset_put2(mrb, set, obj, &ret); + mrb_field_write_barrier_value(mrb, kset_to_rset(set), obj); return (ret == 0) ? mrb_nil_value() : self; } @@ -634,7 +341,7 @@ set_delete(mrb_state *mrb, mrb_value self) if (kset_is_empty(set)) return self; kset_iter_t k = kset_get(mrb, set, obj); - if (k != kset_end(set)) { + if (!kset_is_end(set, k)) { kset_del(mrb, set, k); } return self; @@ -655,7 +362,7 @@ set_delete_p(mrb_state *mrb, mrb_value self) if (kset_is_empty(set)) return mrb_nil_value(); kset_iter_t k = kset_get(mrb, set, obj); - if (k != kset_end(set)) { + if (!kset_is_end(set, k)) { kset_del(mrb, set, k); return self; } @@ -682,7 +389,7 @@ set_core_merge(mrb_state *mrb, mrb_value self) set_ensure_initialized(mrb, self_set); if (!kset_is_empty(other_set)) { - kset_copy_elements(mrb, self_set, other_set); + kset_copy_merge(mrb, self_set, other_set); } return mrb_true_value(); @@ -711,7 +418,7 @@ set_core_subtract(mrb_state *mrb, mrb_value self) KSET_FOREACH(other_set, k) { mrb_value key = kset_key(other_set, k); kset_iter_t self_k = kset_get(mrb, self_set, key); - if (self_k != kset_end(self_set)) { + if (!kset_is_end(self_set, self_k)) { kset_del(mrb, self_set, self_k); } } @@ -735,15 +442,15 @@ set_core_union(mrb_state *mrb, mrb_value self) /* Create a new set by duplicating self */ mrb_value result = mrb_obj_dup(mrb, self); kset_t *result_set = set_get_kset(mrb, result); - if (!result_set->data) { + if (kset_is_uninitialized(result_set)) { /* If self is empty, initialize the set */ - kset_init_embedded(mrb, result_set); + kset_init_data(mrb, result_set, KSET_INITIAL_SIZE); } /* Add all elements from other set */ kset_t *other_set = set_get_kset(mrb, other); - if (other_set->data) { - kset_copy_elements(mrb, result_set, other_set); + if (!kset_is_uninitialized(other_set)) { + kset_copy_merge(mrb, result_set, other_set); } return result; @@ -765,18 +472,18 @@ set_core_difference(mrb_state *mrb, mrb_value self) /* Create a new set by duplicating self */ mrb_value result = mrb_obj_dup(mrb, self); kset_t *result_set = set_get_kset(mrb, result); - if (!result_set->data) { + if (kset_is_uninitialized(result_set)) { /* If self is empty, return an empty set */ return result; } /* Remove all elements that are in other set */ kset_t *other_set = set_get_kset(mrb, other); - if (other_set->data) { + if (!kset_is_uninitialized(other_set)) { KSET_FOREACH(other_set, k) { mrb_value key = kset_key(other_set, k); kset_iter_t result_k = kset_get(mrb, result_set, key); - if (result_k != kset_end(result_set)) { + if (!kset_is_end(result_set, result_k)) { kset_del(mrb, result_set, result_k); } } @@ -804,18 +511,19 @@ set_core_intersection(mrb_state *mrb, mrb_value self) kset_t *result_set = set_get_kset(mrb, result); kset_t *self_set = set_get_kset(mrb, self); - if (!self_set->data) return result; + if (kset_is_uninitialized(self_set)) return result; kset_t *other_set = set_get_kset(mrb, other); - if (!other_set->data) return result; + if (kset_is_uninitialized(other_set)) return result; KSET_FOREACH(other_set, k) { mrb_value key = kset_key(other_set, k); kset_iter_t self_k = kset_get(mrb, self_set, key); /* If key exists in self, add it to result */ - if (self_k != kset_end(self_set)) { + if (!kset_is_end(self_set, self_k)) { kset_put(mrb, result_set, key); + mrb_field_write_barrier_value(mrb, kset_to_rset(result_set), key); } } @@ -846,12 +554,12 @@ set_core_xor(mrb_state *mrb, mrb_value self) /* Handle empty sets */ if (kset_is_empty(self_set)) { if (!kset_is_empty(other_set)) { - kset_copy_elements(mrb, result_set, other_set); + kset_copy_merge(mrb, result_set, other_set); } return result; } if (kset_is_empty(other_set)) { - kset_copy_elements(mrb, result_set, self_set); + kh_replace(set_val, mrb, result_set, self_set); return result; } @@ -862,8 +570,9 @@ set_core_xor(mrb_state *mrb, mrb_value self) kset_iter_t other_k = kset_get(mrb, other_set, key); /* Add to result if not in other */ - if (other_k == kset_end(other_set)) { + if (kset_is_end(other_set, other_k)) { kset_put(mrb, result_set, key); + mrb_field_write_barrier_value(mrb, kset_to_rset(result_set), key); } mrb_gc_arena_restore(mrb, ai); } @@ -874,8 +583,9 @@ set_core_xor(mrb_state *mrb, mrb_value self) kset_iter_t self_k = kset_get(mrb, self_set, key); /* Add to result if not in self */ - if (self_k == kset_end(self_set)) { + if (kset_is_end(self_set, self_k)) { kset_put(mrb, result_set, key); + mrb_field_write_barrier_value(mrb, kset_to_rset(result_set), key); } mrb_gc_arena_restore(mrb, ai); } @@ -914,12 +624,12 @@ set_equal(mrb_state *mrb, mrb_value self) kset_t *other_set = set_get_kset(mrb, other); /* Fast path: both empty */ - if ((!self_set->data || self_set->size == 0) && (!other_set->data || other_set->size == 0)) { + if (kset_is_empty(self_set) && kset_is_empty(other_set)) { return mrb_true_value(); } /* Fast path: different sizes */ - if (!self_set->data || !other_set->data || self_set->size != other_set->size) { + if (kset_size(self_set) != kset_size(other_set)) { return mrb_false_value(); } @@ -927,7 +637,7 @@ set_equal(mrb_state *mrb, mrb_value self) int ai = mrb_gc_arena_save(mrb); KSET_FOREACH(self_set, k) { kset_iter_t k2 = kset_get(mrb, other_set, kset_key(self_set, k)); - if (k2 == kset_end(other_set)) { + if (kset_is_end(other_set, k2)) { return mrb_false_value(); /* Element in self not found in other */ } mrb_gc_arena_restore(mrb, ai); @@ -948,31 +658,28 @@ set_hash_m(mrb_state *mrb, mrb_value self) { kset_t *set = set_get_kset(mrb, self); - /* Use FNV-1a hash algorithm with better distribution properties */ - uint64_t hash = 0xcbf29ce484222325ULL; /* FNV offset basis */ - const uint64_t fnv_prime = 0x100000001b3ULL; /* FNV prime */ + /* Use order-independent hash algorithm for sets */ + uint64_t hash = 0; /* Start with zero for XOR accumulation */ /* Include the size of the set in the hash */ - size_t size = set ? set->size : 0; - hash ^= size; - hash *= fnv_prime; + size_t size = kset_size(set); + hash ^= size * GOLDEN_RATIO_PRIME; - if (set->data && size > 0) { - /* Process each element */ + if (!kset_is_uninitialized(set) && size > 0) { + /* Process each element - order independent using XOR */ int ai = mrb_gc_arena_save(mrb); KSET_FOREACH(set, k) { /* Get element's hash code */ - kset_int_t elem_hash = (kset_int_t)mrb_obj_hash_code(mrb, kset_key(set, k)); + khint_t elem_hash = (khint_t)mrb_obj_hash_code(mrb, kset_key(set, k)); - /* Mix using FNV-1a algorithm */ - hash ^= elem_hash; - hash *= fnv_prime; + /* XOR is commutative, so order doesn't matter */ + hash ^= elem_hash * GOLDEN_RATIO_PRIME; mrb_gc_arena_restore(mrb, ai); } } - /* Final mixing to improve avalanche effect */ + /* Final mixing to improve distribution */ hash ^= hash >> 32; return mrb_fixnum_value((mrb_int)hash); @@ -1001,12 +708,12 @@ set_superset_p(mrb_state *mrb, mrb_value self) return mrb_true_value(); /* Empty set is a subset of any set */ } - if (!self_set->data) { + if (kset_is_uninitialized(self_set)) { return mrb_false_value(); /* Empty set is not a superset of a non-empty set */ } /* Check size first - a superset must be at least as large as the subset */ - if (self_set->size < other_set->size) { + if (kset_size(self_set) < kset_size(other_set)) { return mrb_false_value(); } @@ -1014,7 +721,7 @@ set_superset_p(mrb_state *mrb, mrb_value self) int ai = mrb_gc_arena_save(mrb); KSET_FOREACH(other_set, k) { kset_iter_t self_k = kset_get(mrb, self_set, kset_key(other_set, k)); - if (self_k == kset_end(self_set)) { + if (kset_is_end(self_set, self_k)) { return mrb_false_value(); /* Element in other not found in self */ } mrb_gc_arena_restore(mrb, ai); @@ -1044,15 +751,15 @@ set_proper_superset_p(mrb_state *mrb, mrb_value self) /* Handle empty sets */ if (kset_is_empty(other_set)) { /* Empty set is a proper subset of any non-empty set */ - return self_set->data && self_set->size > 0 ? mrb_true_value() : mrb_false_value(); + return !kset_is_empty(self_set) ? mrb_true_value() : mrb_false_value(); } - if (!self_set->data) { + if (kset_is_uninitialized(self_set)) { return mrb_false_value(); /* Empty set is not a proper superset of any set */ } /* For a proper superset, self must be strictly larger than other */ - if (self_set->size <= other_set->size) { + if (kset_size(self_set) <= kset_size(other_set)) { return mrb_false_value(); } @@ -1060,7 +767,7 @@ set_proper_superset_p(mrb_state *mrb, mrb_value self) int ai = mrb_gc_arena_save(mrb); KSET_FOREACH(other_set, k) { kset_iter_t self_k = kset_get(mrb, self_set, kset_key(other_set, k)); - if (self_k == kset_end(self_set)) { + if (kset_is_end(self_set, self_k)) { return mrb_false_value(); /* Element in other not found in self */ } mrb_gc_arena_restore(mrb, ai); @@ -1092,12 +799,12 @@ set_subset_p(mrb_state *mrb, mrb_value self) return mrb_true_value(); /* Empty set is a subset of any set */ } - if (!other_set->data) { + if (kset_is_uninitialized(other_set)) { return mrb_false_value(); /* Non-empty set is not a subset of an empty set */ } /* Check size first - a subset cannot be larger than its superset */ - if (other_set->size < self_set->size) { + if (kset_size(other_set) < kset_size(self_set)) { return mrb_false_value(); } @@ -1105,7 +812,7 @@ set_subset_p(mrb_state *mrb, mrb_value self) int ai = mrb_gc_arena_save(mrb); KSET_FOREACH(self_set, k) { kset_iter_t other_k = kset_get(mrb, other_set, kset_key(self_set, k)); - if (other_k == kset_end(other_set)) { + if (kset_is_end(other_set, other_k)) { return mrb_false_value(); /* Element in self not found in other */ } mrb_gc_arena_restore(mrb, ai); @@ -1135,15 +842,15 @@ set_proper_subset_p(mrb_state *mrb, mrb_value self) /* Handle empty sets */ if (kset_is_empty(self_set)) { /* Empty set is a proper subset of any non-empty set */ - return other_set->data && other_set->size > 0 ? mrb_true_value() : mrb_false_value(); + return !kset_is_empty(other_set) ? mrb_true_value() : mrb_false_value(); } - if (!other_set->data) { + if (kset_is_uninitialized(other_set)) { return mrb_false_value(); /* Non-empty set is not a proper subset of an empty set */ } /* For a proper subset, self must be strictly smaller than other */ - if (other_set->size <= self_set->size) { + if (kset_size(other_set) <= kset_size(self_set)) { return mrb_false_value(); } @@ -1151,7 +858,7 @@ set_proper_subset_p(mrb_state *mrb, mrb_value self) int ai = mrb_gc_arena_save(mrb); KSET_FOREACH(self_set, k) { kset_iter_t other_k = kset_get(mrb, other_set, kset_key(self_set, k)); - if (other_k == kset_end(other_set)) { + if (kset_is_end(other_set, other_k)) { return mrb_false_value(); /* Element in self not found in other */ } mrb_gc_arena_restore(mrb, ai); @@ -1184,10 +891,10 @@ set_intersect_p(mrb_state *mrb, mrb_value self) /* Iterate through the smaller set for efficiency */ int ai = mrb_gc_arena_save(mrb); - if (self_set->size < other_set->size) { + if (kset_size(self_set) < kset_size(other_set)) { KSET_FOREACH(self_set, k) { kset_iter_t other_k = kset_get(mrb, other_set, kset_key(self_set, k)); - if (other_k != kset_end(other_set)) { + if (!kset_is_end(other_set, other_k)) { return mrb_true_value(); /* Found a common element */ } mrb_gc_arena_restore(mrb, ai); @@ -1196,7 +903,7 @@ set_intersect_p(mrb_state *mrb, mrb_value self) else { KSET_FOREACH(other_set, k) { kset_iter_t self_k = kset_get(mrb, self_set, kset_key(other_set, k)); - if (self_k != kset_end(self_set)) { + if (!kset_is_end(self_set, self_k)) { return mrb_true_value(); /* Found a common element */ } mrb_gc_arena_restore(mrb, ai); @@ -1254,14 +961,14 @@ set_cmp(mrb_state *mrb, mrb_value self) } /* Compare sizes */ - int size_cmp = self_set->size - other_set->size; + mrb_int size_diff = kset_size(self_set) - kset_size(other_set); - if (size_cmp < 0) { + if (size_diff < 0) { /* self might be a proper subset of other */ int ai = mrb_gc_arena_save(mrb); KSET_FOREACH(self_set, k) { kset_iter_t other_k = kset_get(mrb, other_set, kset_key(self_set, k)); - if (other_k == kset_end(other_set)) { + if (kset_is_end(other_set, other_k)) { /* Not a subset */ return mrb_nil_value(); /* Not comparable */ } @@ -1271,12 +978,12 @@ set_cmp(mrb_state *mrb, mrb_value self) /* All elements of self are in other, and self is smaller than other */ return mrb_fixnum_value(-1); /* self is a proper subset of other */ } - else if (size_cmp > 0) { + else if (size_diff > 0) { /* self might be a proper superset of other */ int ai = mrb_gc_arena_save(mrb); KSET_FOREACH(other_set, k) { kset_iter_t self_k = kset_get(mrb, self_set, kset_key(other_set, k)); - if (self_k == kset_end(self_set)) { + if (kset_is_end(self_set, self_k)) { /* Not a superset */ return mrb_nil_value(); /* Not comparable */ } @@ -1286,14 +993,14 @@ set_cmp(mrb_state *mrb, mrb_value self) /* All elements of other are in self, and self is larger than other */ return mrb_fixnum_value(1); /* self is a proper superset of other */ } - else { + else { /* size_diff == 0 */ /* Same size, check if they're equal */ mrb_bool is_equal = TRUE; int ai3 = mrb_gc_arena_save(mrb); KSET_FOREACH(self_set, k) { kset_iter_t other_k = kset_get(mrb, other_set, kset_key(self_set, k)); - if (other_k == kset_end(other_set)) { + if (kset_is_end(other_set, other_k)) { is_equal = FALSE; break; } @@ -1327,14 +1034,6 @@ set_join(mrb_state *mrb, mrb_value self) return mrb_str_new_lit(mrb, ""); } - /* Get separator string */ - const char *sep_ptr = ""; - mrb_int sep_len = 0; - if (!mrb_nil_p(separator)) { - sep_ptr = RSTRING_PTR(separator); - sep_len = RSTRING_LEN(separator); - } - /* Create result string */ mrb_value result = mrb_str_new_capa(mrb, 64); /* Initial capacity */ mrb_bool first = TRUE; @@ -1343,7 +1042,9 @@ set_join(mrb_state *mrb, mrb_value self) int ai = mrb_gc_arena_save(mrb); KSET_FOREACH(set, k) { if (!first) { - mrb_str_cat(mrb, result, sep_ptr, sep_len); + if (!mrb_nil_p(separator)) { + mrb_str_cat(mrb, result, RSTRING_PTR(separator), RSTRING_LEN(separator)); + } } else { first = FALSE; @@ -1385,7 +1086,7 @@ set_inspect(mrb_state *mrb, mrb_value self) } /* Estimate buffer size based on set size */ - size_t size = set->size; + size_t size = kset_size(set); size_t buffer_size = 16 + strlen(classname) + (size * 8); /* Rough estimate */ /* Create the beginning of the string with pre-allocated capacity */ @@ -1432,8 +1133,7 @@ set_reset(mrb_state *mrb, mrb_value self) kset_t *set = set_get_kset(mrb, self); if (!kset_is_empty(set)) { - /* Create a new set by copying the old one */ - kset_rehash(mrb, set); + kset_resize(mrb, set, kset_size(set)); } return self; @@ -1458,6 +1158,7 @@ set_add_all(mrb_state *mrb, mrb_value self) int ai = mrb_gc_arena_save(mrb); for (mrb_int i = 0; i < argc; i++) { kset_put(mrb, set, argv[i]); + mrb_field_write_barrier_value(mrb, kset_to_rset(set), argv[i]); mrb_gc_arena_restore(mrb, ai); } @@ -1488,6 +1189,7 @@ set_flatten_recursive(mrb_state *mrb, kset_t *target, kset_t *source, int *seen_ if (!source || !target) return 0; if (*seen_count >= MAX_NESTED_DEPTH) return -1; + struct RBasic *target_obj = kset_to_rset(target); int ai = mrb_gc_arena_save(mrb); /* Process each element in the source set */ KSET_FOREACH(source, k) { @@ -1513,12 +1215,46 @@ set_flatten_recursive(mrb_state *mrb, kset_t *target, kset_t *source, int *seen_ else { /* Add non-Set element directly */ kset_put(mrb, target, elem); + mrb_field_write_barrier_value(mrb, target_obj, elem); } mrb_gc_arena_restore(mrb, ai); } return 0; } +/* + * Helper function: Check if a set has any nested sets + * Returns TRUE if nested sets found, FALSE otherwise + */ +static mrb_bool +set_has_nested_sets(mrb_state *mrb, kset_t *set) +{ + if (kset_is_empty(set)) return FALSE; + + int ai = mrb_gc_arena_save(mrb); + KSET_FOREACH(set, k) { + if (set_is_set(kset_key(set, k))) { + return TRUE; + } + mrb_gc_arena_restore(mrb, ai); + } + return FALSE; +} + +/* + * Helper function: Perform the actual flattening operation + * Returns the flattened set (creates a new kset_t*) + */ +static void +set_do_flatten(mrb_state *mrb, kset_t *result_set, kset_t *source_set) +{ + int seen_count = 0; + + if (set_flatten_recursive(mrb, result_set, source_set, &seen_count) < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "flatten recursion depth too deep"); + } +} + /* * call-seq: * set.flatten -> new_set @@ -1537,32 +1273,15 @@ set_flatten(mrb_state *mrb, mrb_value self) } /* Fast path: check if there are any nested sets */ - mrb_bool has_nested_sets = FALSE; - int ai = mrb_gc_arena_save(mrb); - KSET_FOREACH(self_set, k) { - if (set_is_set(kset_key(self_set, k))) { - has_nested_sets = TRUE; - break; - } - mrb_gc_arena_restore(mrb, ai); - } - - /* If no nested sets, just return a duplicate */ - if (!has_nested_sets) { + if (!set_has_nested_sets(mrb, self_set)) { return mrb_obj_dup(mrb, self); } - /* Create a new set of the same class */ + /* Create a new set and flatten into it */ mrb_value result = mrb_obj_new(mrb, mrb_obj_class(mrb, self), 0, NULL); kset_t *result_set = set_get_kset(mrb, result); - /* Track recursion depth */ - int seen_count = 0; - - /* Flatten the set */ - if (set_flatten_recursive(mrb, result_set, self_set, &seen_count) < 0) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "flatten recursion depth too deep"); - } + set_do_flatten(mrb, result_set, self_set); return result; } @@ -1584,41 +1303,21 @@ set_flatten_bang(mrb_state *mrb, mrb_value self) return mrb_nil_value(); /* No changes needed for empty set */ } - /* First, check if there are any nested sets */ - mrb_bool has_nested_sets = FALSE; - int ai = mrb_gc_arena_save(mrb); - KSET_FOREACH(self_set, k) { - mrb_value elem = kset_key(self_set, k); - if (set_is_set(elem)) { - has_nested_sets = TRUE; - break; - } - mrb_gc_arena_restore(mrb, ai); - } - - if (!has_nested_sets) { + /* Check if there are any nested sets */ + if (!set_has_nested_sets(mrb, self_set)) { return mrb_nil_value(); /* No nested sets, no changes needed */ } - /* Create a temporary set for the flattened result */ - kset_t *new_set = kset_init(mrb); + /* Create a temporary set to flatten into (GC-protected) */ + mrb_value temp = mrb_obj_new(mrb, mrb_obj_class(mrb, self), 0, NULL); + kset_t *temp_set = set_get_kset(mrb, temp); - /* Track recursion depth */ - int seen_count = 0; + set_do_flatten(mrb, temp_set, self_set); - /* Flatten the set into the new set */ - if (set_flatten_recursive(mrb, new_set, self_set, &seen_count) < 0) { - /* Clean up the new set if an error occurred */ - kset_destroy(mrb, new_set); - - /* Raise appropriate exception */ - mrb_raise(mrb, E_ARGUMENT_ERROR, "flatten recursion depth too deep"); - } - - /* Replace the old data with the new one */ - kset_destroy_embedded(mrb, self_set); - *self_set = *new_set; - mrb_free(mrb, new_set); + /* Swap the data between self and temp */ + kset_t temp_data = *self_set; + *self_set = *temp_set; + *temp_set = temp_data; return self; } @@ -1637,12 +1336,12 @@ set_delete_all(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "*", &argv, &argc); kset_t *ks = set_get_kset(mrb, self); - if (!ks->data) return self; + if (kset_is_uninitialized(ks)) return self; int ai = mrb_gc_arena_save(mrb); for (mrb_int i = 0; i < argc; i++) { kset_iter_t k = kset_get(mrb, ks, argv[i]); - if (k != kset_end(ks)) { + if (!kset_is_end(ks, k)) { kset_del(mrb, ks, k); } mrb_gc_arena_restore(mrb, ai); @@ -1665,11 +1364,11 @@ set_include_all_p(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "*", &argv, &argc); kset_t *ks = set_get_kset(mrb, self); - if (!ks->data) return mrb_false_value(); + if (kset_is_uninitialized(ks)) return mrb_false_value(); for (mrb_int i = 0; i < argc; i++) { kset_iter_t k = kset_get(mrb, ks, argv[i]); - if (k == kset_end(ks)) { + if (kset_is_end(ks, k)) { return mrb_false_value(); } } @@ -1695,7 +1394,7 @@ set_include_any_p(mrb_state *mrb, mrb_value self) for (mrb_int i = 0; i < argc; i++) { kset_iter_t k = kset_get(mrb, ks, argv[i]); - if (k != kset_end(ks)) { + if (!kset_is_end(ks, k)) { return mrb_true_value(); } } @@ -1723,6 +1422,7 @@ set_s_create(mrb_state *mrb, mrb_value klass) for (mrb_int i = 0; i < argc; i++) { kset_put(mrb, ks, argv[i]); + mrb_field_write_barrier_value(mrb, kset_to_rset(ks), argv[i]); } return set; diff --git a/mrbgems/mruby-set/test/set.rb b/mrbgems/mruby-set/test/set.rb index afcaa4563..ca0add4c4 100644 --- a/mrbgems/mruby-set/test/set.rb +++ b/mrbgems/mruby-set/test/set.rb @@ -755,9 +755,10 @@ assert("Set operations with custom objects") do end assert("Set#hash") do - set1 = Set[1, 2, 3] - set2 = Set[1, 2, 3] - - assert_kind_of Integer, set1.hash - assert_equal(set1.hash, set2.hash) + set = Set[1, 2, 3] + assert_kind_of(Integer, set.hash) + hash = set.hash + assert_equal(hash, Set[3, 1, 2].hash) + assert_not_equal(hash, Set[1, 2, 4].hash) + assert_not_equal(hash, Set[].hash) end diff --git a/mrbgems/mruby-sleep/src/sleep.c b/mrbgems/mruby-sleep/src/sleep.c index 8f434957d..c77a3dfd8 100644 --- a/mrbgems/mruby-sleep/src/sleep.c +++ b/mrbgems/mruby-sleep/src/sleep.c @@ -39,7 +39,24 @@ #include #include -/* not implemented forever sleep (called without an argument)*/ +/* + * call-seq: + * sleep(duration) -> integer + * + * Suspends the current thread for duration seconds (which may be any number, + * including a Float with fractional seconds if floating point is enabled). + * Returns the actual number of seconds slept (rounded), which may be less than + * that asked for if another thread calls Thread#run. Zero arguments causes + * sleep to sleep forever. + * + * Time.new #=> 2008-03-08 19:56:19 +0900 + * sleep 1.2 #=> 1 + * Time.new #=> 2008-03-08 19:56:20 +0900 + * sleep 1.9 #=> 2 + * Time.new #=> 2008-03-08 19:56:22 +0900 + * + * Note: Forever sleep (called without an argument) is not implemented. + */ static mrb_value f_sleep(mrb_state *mrb, mrb_value self) { @@ -72,6 +89,21 @@ f_sleep(mrb_state *mrb, mrb_value self) } /* mruby special; needed for mruby without float numbers */ +/* + * call-seq: + * usleep(microseconds) -> 0 + * + * Suspends the current thread for microseconds microseconds (which should be + * an integer). This provides microsecond-level precision for short delays. + * Returns 0 on successful completion. + * + * usleep(500000) # Sleep for 0.5 seconds (500,000 microseconds) + * usleep(1000) # Sleep for 1 millisecond (1,000 microseconds) + * usleep(100) # Sleep for 100 microseconds + * + * Note: This function is useful for precise timing in embedded systems + * where sub-second delays are required. + */ static mrb_value f_usleep(mrb_state *mrb, mrb_value self) { @@ -83,7 +115,6 @@ f_usleep(mrb_state *mrb, mrb_value self) #else struct timeval st_tm,ed_tm; #endif - time_t slp_tm; #ifdef _WIN32 GetSystemTimeAsFileTime(&st_ft); @@ -111,7 +142,7 @@ f_usleep(mrb_state *mrb, mrb_value self) ed_time <<=32; ed_time |= ed_ft.dwLowDateTime; - slp_tm = (ed_time - st_time) / 10; + time_t slp_tm = (ed_time - st_time) / 10; #else gettimeofday(&ed_tm, NULL); @@ -126,13 +157,29 @@ f_usleep(mrb_state *mrb, mrb_value self) return mrb_fixnum_value((mrb_int)slp_tm); } +/* + * Initializes the mruby-sleep gem by defining sleep and usleep methods + * as private methods in the Kernel module, making them available globally. + * + * - sleep: requires 1 argument (duration in seconds), supports floating point + * when MRB_NO_FLOAT is not defined, otherwise uses integer seconds + * - usleep: requires 1 argument (duration in microseconds), integer only + * + * Both methods provide thread suspension capabilities for timing control + * in embedded Ruby environments with cross-platform support (Windows/Unix). + */ void mrb_mruby_sleep_gem_init(mrb_state *mrb) { - mrb_define_private_method_id(mrb, mrb->kernel_module, MRB_SYM(sleep), f_sleep, MRB_ARGS_REQ(1)); - mrb_define_private_method_id(mrb, mrb->kernel_module, MRB_SYM(usleep), f_usleep, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, mrb->kernel_module, MRB_SYM(sleep), f_sleep, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, mrb->kernel_module, MRB_SYM(usleep), f_usleep, MRB_ARGS_REQ(1)); } +/* + * Finalizes the mruby-sleep gem. Currently no cleanup is required + * as the sleep/usleep implementation uses system calls without + * persistent state or allocated resources. + */ void mrb_mruby_sleep_gem_final(mrb_state *mrb) { diff --git a/mrbgems/mruby-socket/include/socket_hal.h b/mrbgems/mruby-socket/include/socket_hal.h new file mode 100644 index 000000000..d92f2079f --- /dev/null +++ b/mrbgems/mruby-socket/include/socket_hal.h @@ -0,0 +1,83 @@ +/* +** socket_hal.h - Socket HAL (Hardware Abstraction Layer) interface +** +** See Copyright Notice in mruby.h +** +** This header defines the platform-independent socket HAL interface. +** Platform-specific implementations are provided by HAL gems: +** - hal-posix-socket: POSIX socket implementation (Linux, macOS, BSD, Unix) +** - hal-win-socket: Windows socket implementation (Windows, MinGW) +*/ + +#ifndef MRUBY_SOCKET_HAL_H +#define MRUBY_SOCKET_HAL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Socket HAL Initialization/Finalization + */ + +/* Initialize socket subsystem (e.g., WSAStartup on Windows) */ +void mrb_hal_socket_init(mrb_state *mrb); + +/* Finalize socket subsystem (e.g., WSACleanup on Windows) */ +void mrb_hal_socket_final(mrb_state *mrb); + +/* + * Socket Control Operations + */ + +/* Set non-blocking mode on socket + * Returns 0 on success, -1 on error (sets errno) */ +int mrb_hal_socket_set_nonblock(mrb_state *mrb, int fd, int nonblock); + +/* + * Address Conversion Functions + */ + +/* Convert network address to presentation format (string) + * af: address family (AF_INET, AF_INET6) + * src: network address in binary form + * dst: buffer for string result + * size: size of dst buffer + * Returns: dst on success, NULL on error */ +const char* mrb_hal_socket_inet_ntop(int af, const void *src, char *dst, size_t size); + +/* Convert presentation format (string) to network address + * af: address family (AF_INET, AF_INET6) + * src: string representation of address + * dst: buffer for network address result + * Returns: 1 on success, 0 if src is not valid, -1 on error */ +int mrb_hal_socket_inet_pton(int af, const char *src, void *dst); + +/* + * Platform-Specific Socket Features + */ + +/* Create Unix domain socket address structure + * path: Unix socket path + * Returns: packed sockaddr string, or raises exception if not supported */ +mrb_value mrb_hal_socket_sockaddr_un(mrb_state *mrb, const char *path, size_t pathlen); + +/* Create a pair of connected sockets + * domain: address family (e.g., AF_UNIX) + * type: socket type (e.g., SOCK_STREAM) + * protocol: protocol (usually 0) + * sv: array to receive the two socket descriptors + * Returns: 0 on success, -1 on error (sets errno) */ +int mrb_hal_socket_socketpair(mrb_state *mrb, int domain, int type, int protocol, int sv[2]); + +/* Get Unix socket path from sockaddr + * Returns: Unix socket path string, or raises exception if not supported */ +mrb_value mrb_hal_socket_unix_path(mrb_state *mrb, const char *sockaddr, size_t socklen); + +#ifdef __cplusplus +} +#endif + +#endif /* MRUBY_SOCKET_HAL_H */ diff --git a/mrbgems/mruby-socket/mrbgem.rake b/mrbgems/mruby-socket/mrbgem.rake index c3684cd0c..0702f5e0d 100644 --- a/mrbgems/mruby-socket/mrbgem.rake +++ b/mrbgems/mruby-socket/mrbgem.rake @@ -5,13 +5,33 @@ MRuby::Gem::Specification.new('mruby-socket') do |spec| #spec.cc.defines << "HAVE_SA_LEN=0" - # If Windows, use winsock - if spec.for_windows? - spec.linker.libraries << "wsock32" - spec.linker.libraries << "ws2_32" - end - spec.add_dependency('mruby-io', :core => 'mruby-io') spec.add_dependency('mruby-error', :core => '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 + end end diff --git a/mrbgems/mruby-socket/mrblib/socket.rb b/mrbgems/mruby-socket/mrblib/socket.rb index 90ece42da..7b7d986db 100644 --- a/mrbgems/mruby-socket/mrblib/socket.rb +++ b/mrbgems/mruby-socket/mrblib/socket.rb @@ -1,4 +1,15 @@ class Addrinfo + # + # call-seq: + # Addrinfo.new(sockaddr, family=Socket::PF_UNSPEC, socktype=0, protocol=0) -> addrinfo + # + # Creates a new Addrinfo object from socket address information. + # sockaddr can be a packed sockaddr string or an array representation. + # + # Addrinfo.new(Socket.sockaddr_in(80, "127.0.0.1")) + # Addrinfo.new(["AF_INET", 80, "localhost", "127.0.0.1"]) + # Addrinfo.new(["AF_UNIX", "/tmp/socket"]) + # def initialize(sockaddr, family=Socket::PF_UNSPEC, socktype=0, protocol=0) @hostname = nil if sockaddr.is_a? Array @@ -21,28 +32,93 @@ class Addrinfo @protocol = protocol end + # + # call-seq: + # Addrinfo.foreach(nodename, service, family=nil, socktype=nil, protocol=nil, flags=0) { |addrinfo| block } -> array + # + # Iterates over all address information for the given nodename and service. + # Returns an array of Addrinfo objects. + # + # Addrinfo.foreach("www.example.com", "http") { |ai| puts ai.ip_address } + # Addrinfo.foreach("localhost", 80) { |ai| puts ai.inspect } + # def self.foreach(nodename, service, family=nil, socktype=nil, protocol=nil, flags=0, &block) a = self.getaddrinfo(nodename, service, family, socktype, protocol, flags) a.each { |ai| block.call(ai) } a end + # + # call-seq: + # Addrinfo.ip(host) -> addrinfo + # + # Creates an Addrinfo object for the given host with port 0. + # Useful for creating address info without specifying a port. + # + # Addrinfo.ip("127.0.0.1") #=> # + # Addrinfo.ip("::1") #=> # + # def self.ip(host) Addrinfo.new(Socket.sockaddr_in(0, host)) end + # + # call-seq: + # Addrinfo.tcp(host, port) -> addrinfo + # + # Creates an Addrinfo object for TCP connection to the given host and port. + # + # Addrinfo.tcp("localhost", 80) #=> # + # Addrinfo.tcp("www.example.com", 443) #=> # + # def self.tcp(host, port) Addrinfo.getaddrinfo(host, port, nil, Socket::SOCK_STREAM, Socket::IPPROTO_TCP)[0] end + # + # call-seq: + # Addrinfo.udp(host, port) -> addrinfo + # + # Creates an Addrinfo object for UDP connection to the given host and port. + # + # Addrinfo.udp("localhost", 53) #=> # + # Addrinfo.udp("8.8.8.8", 53) #=> # + # def self.udp(host, port) Addrinfo.getaddrinfo(host, port, nil, Socket::SOCK_DGRAM, Socket::IPPROTO_UDP)[0] end + # + # call-seq: + # Addrinfo.unix(path, socktype=Socket::SOCK_STREAM) -> addrinfo + # + # Creates an Addrinfo object for Unix domain socket at the given path. + # + # Addrinfo.unix("/tmp/socket") #=> # + # Addrinfo.unix("/var/run/daemon.sock", Socket::SOCK_DGRAM) #=> # + # def self.unix(path, socktype=Socket::SOCK_STREAM) Addrinfo.new(Socket.sockaddr_un(path), Socket::AF_UNIX, socktype) end + # + # call-seq: + # addrinfo.afamily -> integer + # + # Returns the address family of the socket address. + # + # Addrinfo.tcp("localhost", 80).afamily #=> 2 (AF_INET) + # Addrinfo.unix("/tmp/sock").afamily #=> 1 (AF_UNIX) + # + # + # call-seq: + # addrinfo.afamily -> integer + # + # Returns the address family of the socket address. + # + # Addrinfo.tcp("localhost", 80).afamily #=> 2 (AF_INET) + # Addrinfo.unix("/tmp/sock").afamily #=> 1 (AF_UNIX) + # def afamily @family end @@ -58,6 +134,15 @@ class Addrinfo # Socket.getnameinfo #end + # + # call-seq: + # addrinfo.inspect -> string + # + # Returns a string representation of the Addrinfo object. + # + # Addrinfo.tcp("localhost", 80).inspect #=> "#" + # Addrinfo.unix("/tmp/sock").inspect #=> "#" + # def inspect if ipv4? or ipv6? if @protocol == Socket::IPPROTO_TCP or (@socktype == Socket::SOCK_STREAM and @protocol == 0) @@ -73,6 +158,15 @@ class Addrinfo "#" end + # + # call-seq: + # addrinfo.inspect_sockaddr -> string + # + # Returns a string representation of the socket address portion. + # + # Addrinfo.tcp("localhost", 80).inspect_sockaddr #=> "127.0.0.1:80" + # Addrinfo.unix("/tmp/sock").inspect_sockaddr #=> "/tmp/sock" + # def inspect_sockaddr if ipv4? a, p = ip_unpack @@ -87,23 +181,68 @@ class Addrinfo end end + # + # call-seq: + # addrinfo.ip? -> true or false + # + # Returns true if the address is an IP address (IPv4 or IPv6). + # + # Addrinfo.tcp("localhost", 80).ip? #=> true + # Addrinfo.unix("/tmp/sock").ip? #=> false + # def ip? ipv4? or ipv6? end + # + # call-seq: + # addrinfo.ip_address -> string + # + # Returns the IP address as a string. Raises an exception if not an IP address. + # + # Addrinfo.tcp("localhost", 80).ip_address #=> "127.0.0.1" + # Addrinfo.udp("::1", 53).ip_address #=> "::1" + # def ip_address ip_unpack[0] end + # + # call-seq: + # addrinfo.ip_port -> integer + # + # Returns the port number. Raises an exception if not an IP address. + # + # Addrinfo.tcp("localhost", 80).ip_port #=> 80 + # Addrinfo.udp("127.0.0.1", 53).ip_port #=> 53 + # def ip_port ip_unpack[1] end + # + # call-seq: + # addrinfo.ip_unpack -> [ip_address, port] + # + # Returns an array containing the IP address and port number. + # + # Addrinfo.tcp("localhost", 80).ip_unpack #=> ["127.0.0.1", 80] + # Addrinfo.udp("::1", 53).ip_unpack #=> ["::1", 53] + # def ip_unpack h, p = getnameinfo(Socket::NI_NUMERICHOST|Socket::NI_NUMERICSERV) [ h, p.to_i ] end + # + # call-seq: + # addrinfo.ipv4? -> true or false + # + # Returns true if the address is an IPv4 address. + # + # Addrinfo.tcp("127.0.0.1", 80).ipv4? #=> true + # Addrinfo.tcp("::1", 80).ipv4? #=> false + # def ipv4? @family == Socket::AF_INET end @@ -112,6 +251,15 @@ class Addrinfo #def ipv4_multicast? #def ipv4_private? + # + # call-seq: + # addrinfo.ipv6? -> true or false + # + # Returns true if the address is an IPv6 address. + # + # Addrinfo.tcp("::1", 80).ipv6? #=> true + # Addrinfo.tcp("127.0.0.1", 80).ipv6? #=> false + # def ipv6? @family == Socket::AF_INET6 end @@ -129,13 +277,48 @@ class Addrinfo #def ipv6_v4mapped? #def listen(backlog=5) + # + # call-seq: + # addrinfo.pfamily -> integer + # + # Returns the protocol family (same as afamily). + # + # Addrinfo.tcp("localhost", 80).pfamily #=> 2 (PF_INET) + # Addrinfo.unix("/tmp/sock").pfamily #=> 1 (PF_UNIX) + # def pfamily @family end + # + # call-seq: + # addrinfo.protocol -> integer + # + # Returns the protocol number. + # + # Addrinfo.tcp("localhost", 80).protocol #=> 6 (IPPROTO_TCP) + # Addrinfo.udp("localhost", 53).protocol #=> 17 (IPPROTO_UDP) + # attr_reader :protocol + + # + # call-seq: + # addrinfo.socktype -> integer + # + # Returns the socket type. + # + # Addrinfo.tcp("localhost", 80).socktype #=> 1 (SOCK_STREAM) + # Addrinfo.udp("localhost", 53).socktype #=> 2 (SOCK_DGRAM) + # attr_reader :socktype + # + # call-seq: + # addrinfo._to_array -> array + # + # Internal method that returns the address information as an array. + # Used internally by socket operations. + # def _to_array case @family when Socket::AF_INET @@ -151,12 +334,30 @@ class Addrinfo [ s, port.to_i, addr, addr ] end + # + # call-seq: + # addrinfo.to_sockaddr -> string + # + # Returns the socket address as a packed string. + # + # ai = Addrinfo.tcp("localhost", 80) + # ai.to_sockaddr #=> packed sockaddr string + # def to_sockaddr @sockaddr end alias to_s to_sockaddr + # + # call-seq: + # addrinfo.unix? -> true or false + # + # Returns true if the address is a Unix domain socket address. + # + # Addrinfo.unix("/tmp/sock").unix? #=> true + # Addrinfo.tcp("localhost", 80).unix? #=> false + # def unix? @family == Socket::AF_UNIX end @@ -165,30 +366,77 @@ end class BasicSocket < IO @@do_not_reverse_lookup = true + # + # call-seq: + # BasicSocket.do_not_reverse_lookup -> true or false + # + # Returns the current setting for reverse DNS lookups. + # + # BasicSocket.do_not_reverse_lookup #=> false + # def self.do_not_reverse_lookup @@do_not_reverse_lookup end + # + # call-seq: + # BasicSocket.do_not_reverse_lookup = boolean -> boolean + # + # Sets whether to perform reverse DNS lookups. + # + # BasicSocket.do_not_reverse_lookup = true + # def self.do_not_reverse_lookup=(val) @@do_not_reverse_lookup = val ? true : false end + # + # call-seq: + # BasicSocket.new(*args) -> basicsocket + # + # Creates a new BasicSocket object. This is typically called by subclasses. + # def initialize(*args) super(*args) self._is_socket = true @do_not_reverse_lookup = @@do_not_reverse_lookup end + # + # call-seq: + # BasicSocket.for_fd(fd) -> basicsocket + # + # Creates a BasicSocket object from an existing file descriptor. + # + # sock = BasicSocket.for_fd(3) + # def self.for_fd(fd) super(fd, "r+") end #def connect_address + # + # call-seq: + # basicsocket.local_address -> addrinfo + # + # Returns an Addrinfo object for the local address of the socket. + # + # sock.local_address #=> # + # def local_address Addrinfo.new self.getsockname end + # + # call-seq: + # basicsocket.recv_nonblock(maxlen, flags=0) -> string + # + # Receives data from the socket without blocking. May raise an exception + # if no data is available. + # + # data = sock.recv_nonblock(1024) + # def recv_nonblock(maxlen, flags=0) begin _setnonblock(true) @@ -198,6 +446,14 @@ class BasicSocket < IO end end + # + # call-seq: + # basicsocket.remote_address -> addrinfo + # + # Returns an Addrinfo object for the remote address of the socket. + # + # sock.remote_address #=> # + # def remote_address Addrinfo.new self.getpeername end @@ -206,18 +462,52 @@ class BasicSocket < IO end class IPSocket < BasicSocket + # + # call-seq: + # IPSocket.getaddress(host) -> string + # + # Returns the IP address of the given hostname as a string. + # + # IPSocket.getaddress("localhost") #=> "127.0.0.1" + # IPSocket.getaddress("www.ruby-lang.org") #=> "150.95.145.38" + # def self.getaddress(host) Addrinfo.ip(host).ip_address end + # + # call-seq: + # ipsocket.addr -> [family, port, hostname, ip_address] + # + # Returns the local address information as an array. + # + # sock.addr #=> ["AF_INET", 12345, "localhost", "127.0.0.1"] + # def addr Addrinfo.new(self.getsockname)._to_array end + # + # call-seq: + # ipsocket.peeraddr -> [family, port, hostname, ip_address] + # + # Returns the remote address information as an array. + # + # sock.peeraddr #=> ["AF_INET", 80, "example.com", "93.184.216.34"] + # def peeraddr Addrinfo.new(self.getpeername)._to_array end + # + # call-seq: + # ipsocket.recvfrom(maxlen, flags=0) -> [data, addrinfo] + # + # Receives data and sender information from the IP socket. + # + # data, addr = sock.recvfrom(1024) + # data, addr = sock.recvfrom(512, 0) + # def recvfrom(maxlen, flags=0) msg, sa = _recvfrom(maxlen, flags) [ msg, Addrinfo.new(sa)._to_array ] @@ -225,6 +515,17 @@ class IPSocket < BasicSocket end class TCPSocket < IPSocket + # + # call-seq: + # TCPSocket.new(host, service, local_host=nil, local_service=nil) -> tcpsocket + # + # Creates a new TCP socket connected to the given host and service. + # Optionally binds to local_host and local_service first. + # + # sock = TCPSocket.new("localhost", 80) + # sock = TCPSocket.new("www.example.com", "http") + # sock = TCPSocket.new("remote", 80, "127.0.0.1", 12345) + # def initialize(host, service, local_host=nil, local_service=nil) if @init_with_fd super(host, service) @@ -254,6 +555,17 @@ class TCPSocket < IPSocket end class TCPServer < TCPSocket + # + # call-seq: + # TCPServer.new(host=nil, service) -> tcpserver + # + # Creates a new TCP server socket bound to the given host and service. + # If host is nil, binds to all available interfaces. + # + # server = TCPServer.new("localhost", 8080) + # server = TCPServer.new(nil, 3000) # binds to all interfaces + # server = TCPServer.new("0.0.0.0", "http") + # def initialize(host=nil, service) ai = Addrinfo.getaddrinfo(host, service, nil, nil, nil, Socket::AI_PASSIVE)[0] @init_with_fd = true @@ -266,6 +578,15 @@ class TCPServer < TCPSocket self end + # + # call-seq: + # tcpserver.accept -> tcpsocket + # + # Accepts an incoming connection and returns a new TCPSocket. + # + # server = TCPServer.new(8080) + # client = server.accept + # def accept fd = self.sysaccept begin @@ -281,6 +602,15 @@ class TCPServer < TCPSocket end end + # + # call-seq: + # tcpserver.accept_nonblock -> unixsocket + # + # Accepts an incoming connection without blocking. May raise an exception + # if no connection is available. + # + # client = server.accept_nonblock + # def accept_nonblock begin self._setnonblock(true) @@ -290,33 +620,86 @@ class TCPServer < TCPSocket end end + # + # call-seq: + # unixserver.listen(backlog) -> 0 + # + # Sets the socket to listen for incoming connections with the given backlog. + # + # server.listen(5) + # server.listen(128) + # def listen(backlog) Socket._listen(self.fileno, backlog) 0 end + # + # call-seq: + # tcpserver.sysaccept -> integer + # + # Accepts an incoming connection and returns the file descriptor. + # + # fd = server.sysaccept + # def sysaccept Socket._accept(self.fileno) end end class UDPSocket < IPSocket + # + # call-seq: + # UDPSocket.new(af=Socket::AF_INET) -> udpsocket + # + # Creates a new UDP socket for the given address family. + # + # sock = UDPSocket.new + # sock = UDPSocket.new(Socket::AF_INET6) + # def initialize(af=Socket::AF_INET) super(Socket._socket(af, Socket::SOCK_DGRAM, 0), "r+") @af = af self end + # + # call-seq: + # ipsocket.bind(host, port) -> 0 + # + # Binds the socket to the given host and port. + # + # sock.bind("127.0.0.1", 8080) + # sock.bind("0.0.0.0", 3000) + # def bind(host, port) Socket._bind(self.fileno, _sockaddr_in(port, host)) 0 end + # + # call-seq: + # ipsocket.connect(host, port) -> 0 + # + # Connects the socket to the given host and port. + # + # sock.connect("127.0.0.1", 80) + # sock.connect("www.example.com", 443) + # def connect(host, port) Socket._connect(self.fileno, _sockaddr_in(port, host)) 0 end + # + # call-seq: + # udpsocket.recvfrom_nonblock(maxlen, flags=0) -> [data, addrinfo] + # + # Receives data and sender information without blocking. + # May raise an exception if no data is available. + # + # data, addr = sock.recvfrom_nonblock(1024) + # def recvfrom_nonblock(*args) s = self begin @@ -328,6 +711,15 @@ class UDPSocket < IPSocket end end + # + # call-seq: + # ipsocket.send(mesg, flags, host=nil, port=nil) -> integer + # + # Sends data through the socket. Returns the number of bytes sent. + # + # sock.send("Hello", 0) + # sock.send("Data", 0, "127.0.0.1", 8080) + # def send(mesg, flags, host=nil, port=nil) if port super(mesg, flags, _sockaddr_in(port, host)) @@ -338,6 +730,13 @@ class UDPSocket < IPSocket end end + # + # call-seq: + # udpsocket._sockaddr_in(port, host) -> string + # + # Internal method to create a sockaddr_in structure for the given port and host. + # Uses the socket's address family. + # def _sockaddr_in(port, host) ai = Addrinfo.getaddrinfo(host, port, @af, Socket::SOCK_DGRAM)[0] ai.to_sockaddr @@ -345,12 +744,30 @@ class UDPSocket < IPSocket end class Socket < BasicSocket + # + # call-seq: + # Socket.new(domain, type, protocol=0) -> socket + # + # Creates a new socket with the given domain, type, and protocol. + # + # sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) + # sock = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM) + # def initialize(domain, type, protocol=0) super(Socket._socket(domain, type, protocol), "r+") end #def self.accept_loop + # + # call-seq: + # Socket.getaddrinfo(nodename, servname, family=nil, socktype=nil, protocol=nil, flags=0) -> array + # + # Returns an array of Addrinfo objects for the given nodename and servname. + # + # Addrinfo.getaddrinfo("localhost", "http") + # Addrinfo.getaddrinfo("www.example.com", 80, Socket::AF_INET) + # def self.getaddrinfo(nodename, servname, family=nil, socktype=nil, protocol=nil, flags=0) Addrinfo.getaddrinfo(nodename, servname, family, socktype, protocol, flags).map { |ai| ary = ai._to_array @@ -365,10 +782,27 @@ class Socket < BasicSocket #def self.getnameinfo #def self.ip_address_list + # + # call-seq: + # Socket.open(domain, type, protocol=0) -> socket + # + # Creates a new socket. Alias for Socket.new. + # + # sock = Socket.open(Socket::AF_INET, Socket::SOCK_STREAM) + # def self.open(*args) new(args) end + # + # call-seq: + # Socket.sockaddr_in(port, host) -> string + # + # Returns a packed sockaddr_in structure for the given port and host. + # + # Socket.sockaddr_in(80, "127.0.0.1") + # Socket.sockaddr_in(443, "localhost") + # def self.sockaddr_in(port, host) ai = Addrinfo.getaddrinfo(host, port, nil, Socket::SOCK_DGRAM)[0] ai.to_sockaddr @@ -385,10 +819,26 @@ class Socket < BasicSocket #def self.unix_server_loop #def self.unix_server_socket + # + # call-seq: + # Socket.unpack_sockaddr_in(sockaddr) -> [port, ip_address] + # + # Unpacks a packed sockaddr_in structure and returns port and IP address. + # + # port, addr = Socket.unpack_sockaddr_in(sockaddr) + # def self.unpack_sockaddr_in(sa) Addrinfo.new(sa).ip_unpack.reverse end + # + # call-seq: + # Socket.unpack_sockaddr_un(sockaddr) -> path + # + # Unpacks a packed sockaddr_un structure and returns the Unix socket path. + # + # path = Socket.unpack_sockaddr_un(sockaddr) + # def self.unpack_sockaddr_un(sa) Addrinfo.new(sa).unix_path end @@ -413,18 +863,45 @@ class Socket < BasicSocket end end + # + # call-seq: + # socket.bind(sockaddr) -> 0 + # + # Binds the socket to the given socket address. + # + # sock.bind(Socket.sockaddr_in(8080, "127.0.0.1")) + # sock.bind(addrinfo) + # def bind(sockaddr) sockaddr = sockaddr.to_sockaddr if sockaddr.is_a? Addrinfo Socket._bind(self.fileno, sockaddr) 0 end + # + # call-seq: + # socket.connect(sockaddr) -> 0 + # + # Connects the socket to the given socket address. + # + # sock.connect(Socket.sockaddr_in(80, "127.0.0.1")) + # sock.connect(addrinfo) + # def connect(sockaddr) sockaddr = sockaddr.to_sockaddr if sockaddr.is_a? Addrinfo Socket._connect(self.fileno, sockaddr) 0 end + # + # call-seq: + # socket.connect_nonblock(sockaddr) -> 0 + # + # Connects the socket to the given address without blocking. + # May raise an exception if the connection cannot be completed immediately. + # + # sock.connect_nonblock(sockaddr) + # def connect_nonblock(sockaddr) begin self._setnonblock(true) @@ -462,6 +939,17 @@ class Socket < BasicSocket end class UNIXSocket < BasicSocket + # + # call-seq: + # UNIXSocket.new(path) -> unixsocket + # UNIXSocket.new(path) { |sock| block } -> obj + # + # Creates a new Unix domain socket connected to the given path. + # If a block is given, yields the socket and closes it when done. + # + # sock = UNIXSocket.new("/tmp/socket") + # UNIXSocket.new("/tmp/socket") { |s| s.write("data") } + # def initialize(path, &block) if self.is_a? UNIXServer super(path, "r") @@ -483,6 +971,15 @@ class UNIXSocket < BasicSocket end class << self + # + # call-seq: + # UNIXSocket.socketpair(type=Socket::SOCK_STREAM, protocol=0) -> [socket1, socket2] + # + # Creates a pair of connected Unix domain sockets. + # + # sock1, sock2 = UNIXSocket.socketpair + # sock1, sock2 = UNIXSocket.socketpair(Socket::SOCK_DGRAM) + # def socketpair(type=Socket::SOCK_STREAM, protocol=0) a = Socket.socketpair(Socket::AF_UNIX, type, protocol) [ UNIXSocket.for_fd(a[0]), UNIXSocket.for_fd(a[1]) ] @@ -491,14 +988,38 @@ class UNIXSocket < BasicSocket alias pair socketpair end + # + # call-seq: + # unixsocket.addr -> [family, path] + # + # Returns the local address information as an array. + # + # sock.addr #=> ["AF_UNIX", "/tmp/socket"] + # def addr [ "AF_UNIX", path ] end + # + # call-seq: + # unixsocket.path -> string + # + # Returns the path of the Unix domain socket. + # + # sock.path #=> "/tmp/socket" + # def path Addrinfo.new(self.getsockname).unix_path end + # + # call-seq: + # unixsocket.peeraddr -> [family, path] + # + # Returns the remote address information as an array. + # + # sock.peeraddr #=> ["AF_UNIX", "/tmp/peer_socket"] + # def peeraddr [ "AF_UNIX", Addrinfo.new(self.getpeername).unix_path ] end @@ -515,6 +1036,14 @@ class UNIXSocket < BasicSocket end class UNIXServer < UNIXSocket + # + # call-seq: + # UNIXServer.new(path) -> unixserver + # + # Creates a new Unix domain server socket bound to the given path. + # + # server = UNIXServer.new("/tmp/server_socket") + # def initialize(path) fd = Socket._socket(Socket::AF_UNIX, Socket::SOCK_STREAM, 0) begin @@ -535,6 +1064,15 @@ class UNIXServer < UNIXSocket end end + # + # call-seq: + # unixserver.accept -> unixsocket + # + # Accepts an incoming connection and returns a new UNIXSocket. + # + # server = UNIXServer.new("/tmp/server") + # client = server.accept + # def accept fd = self.sysaccept begin @@ -545,6 +1083,15 @@ class UNIXServer < UNIXSocket sock end + # + # call-seq: + # unixserver.accept_nonblock -> unixsocket + # + # Accepts an incoming connection without blocking. May raise an exception + # if no connection is available. + # + # client = server.accept_nonblock + # def accept_nonblock begin self._setnonblock(true) @@ -554,11 +1101,28 @@ class UNIXServer < UNIXSocket end end + # + # call-seq: + # unixserver.listen(backlog) -> 0 + # + # Sets the socket to listen for incoming connections with the given backlog. + # + # server.listen(5) + # server.listen(128) + # def listen(backlog) Socket._listen(self.fileno, backlog) 0 end + # + # call-seq: + # unixserver.sysaccept -> integer + # + # Accepts an incoming connection and returns the file descriptor. + # + # fd = server.sysaccept + # def sysaccept Socket._accept(self.fileno) end diff --git a/mrbgems/mruby-socket/src/socket.c b/mrbgems/mruby-socket/src/socket.c index d7707c58b..f3fa663d4 100644 --- a/mrbgems/mruby-socket/src/socket.c +++ b/mrbgems/mruby-socket/src/socket.c @@ -6,12 +6,9 @@ #ifdef _WIN32 #define _WIN32_WINNT 0x0501 - #include #include #include - #include - #define SHUT_RDWR SD_BOTH typedef int fsize_t; #else @@ -42,6 +39,91 @@ #include #include +#include "socket_hal.h" + +/* Address family information for compact lookup table */ +typedef struct { + int family; /* AF_INET, AF_INET6, etc. */ + const char *name; /* "AF_INET", "AF_INET6", etc. */ + int port_offset; /* Offset to port field in sockaddr structure */ + mrb_bool has_port; /* TRUE if this family has a port field */ +} af_info_t; + +/* Protocol family lookup table for socket option inspection */ +typedef struct { + int family; /* PF_INET, PF_INET6, etc. */ + const char *name; /* "INET", "INET6", etc. */ +} pf_info_t; + +/* Compact address family lookup table (memory-efficient) */ +static const af_info_t af_table[] = { + /* Internet Protocol families with port numbers */ + {AF_INET, "AF_INET", offsetof(struct sockaddr_in, sin_port), TRUE}, + {AF_INET6, "AF_INET6", offsetof(struct sockaddr_in6, sin6_port), TRUE}, + + /* Local/Unix domain sockets without port numbers */ +#ifdef AF_UNIX + {AF_UNIX, "AF_UNIX", -1, FALSE}, +#endif +#ifdef AF_LOCAL + {AF_LOCAL, "AF_LOCAL", -1, FALSE}, +#endif + + /* Additional protocol families (platform-dependent) */ +#ifdef AF_LINK + {AF_LINK, "AF_LINK", -1, FALSE}, +#endif +#ifdef AF_ROUTE + {AF_ROUTE, "AF_ROUTE", -1, FALSE}, +#endif +#ifdef AF_UNSPEC + {AF_UNSPEC, "AF_UNSPEC", -1, FALSE}, +#endif +}; + +#define AF_TABLE_SIZE (sizeof(af_table) / sizeof(af_table[0])) + +/* Get address family info for given family constant (compact linear search) */ +static inline const af_info_t *get_af_info(int family) { + for (size_t i = 0; i < AF_TABLE_SIZE; i++) { + if (af_table[i].family == family) { + return &af_table[i]; + } + } + return NULL; +} + +/* Compact protocol family lookup table (memory-efficient) */ +static const pf_info_t pf_table[] = { + {PF_INET, "INET"}, +#ifdef PF_INET6 + {PF_INET6, "INET6"}, +#endif +#ifdef PF_IPX + {PF_IPX, "IPX"}, +#endif +#ifdef PF_AX25 + {PF_AX25, "AX25"}, +#endif +#ifdef PF_APPLETALK + {PF_APPLETALK, "APPLETALK"}, +#endif +#ifdef PF_UNIX + {PF_UNIX, "UNIX"}, +#endif +}; + +#define PF_TABLE_SIZE (sizeof(pf_table) / sizeof(pf_table[0])) + +/* Get protocol family name for given family constant (compact linear search) */ +static inline const char *get_pf_name(int family) { + for (size_t i = 0; i < PF_TABLE_SIZE; i++) { + if (pf_table[i].family == family) { + return pf_table[i].name; + } + } + return NULL; +} #if !defined(HAVE_SA_LEN) #if (defined(BSD) && (BSD >= 199006)) @@ -53,56 +135,12 @@ #define E_SOCKET_ERROR mrb_class_get_id(mrb, MRB_SYM(SocketError)) -#ifdef _WIN32 -static const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) -{ - if (af == AF_INET) { - struct sockaddr_in in = {0}; - - in.sin_family = AF_INET; - memcpy(&in.sin_addr, src, sizeof(struct in_addr)); - getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in), - dst, cnt, NULL, 0, NI_NUMERICHOST); - return dst; - } - else if (af == AF_INET6) { - struct sockaddr_in6 in = {0}; - - in.sin6_family = AF_INET6; - memcpy(&in.sin6_addr, src, sizeof(struct in_addr6)); - getnameinfo((struct sockaddr*)&in, sizeof(struct sockaddr_in6), - dst, cnt, NULL, 0, NI_NUMERICHOST); - return dst; - } - return NULL; -} - -static int inet_pton(int af, const char *src, void *dst) -{ - struct addrinfo hints = {0}; - hints.ai_family = af; - - struct addrinfo *res; - if (getaddrinfo(src, NULL, &hints, &res) != 0) { - printf("Couldn't resolve host %s\n", src); - return -1; - } - - for (struct addrinfo *r = res; r; r = r->ai_next) { - memcpy(dst, r->ai_addr, r->ai_addrlen); - } - - freeaddrinfo(res); - return 0; -} - -#endif - struct gen_addrinfo_args { struct RClass *klass; struct addrinfo *addrinfo; }; +/* Helper to generate array of Addrinfo objects from addrinfo linked list */ static mrb_value gen_addrinfo(mrb_state *mrb, mrb_value args) { @@ -120,6 +158,7 @@ gen_addrinfo(mrb_state *mrb, mrb_value args) return ary; } +/* Helper to free addrinfo structure - used with mrb_ensure */ static mrb_value free_addrinfo(mrb_state *mrb, mrb_value addrinfo) { @@ -127,6 +166,15 @@ free_addrinfo(mrb_state *mrb, mrb_value addrinfo) return mrb_nil_value(); } +/* + * call-seq: + * Addrinfo.getaddrinfo(nodename, servname, family=nil, socktype=nil, protocol=nil, flags=0) -> array + * + * Returns an array of Addrinfo objects for the given nodename and servname. + * + * Addrinfo.getaddrinfo("localhost", "http") + * Addrinfo.getaddrinfo("www.example.com", 80, Socket::AF_INET) + */ static mrb_value mrb_addrinfo_getaddrinfo(mrb_state *mrb, mrb_value klass) { @@ -175,6 +223,15 @@ mrb_addrinfo_getaddrinfo(mrb_state *mrb, mrb_value klass) return mrb_ensure(mrb, gen_addrinfo, mrb_cptr_value(mrb, &args), free_addrinfo, mrb_cptr_value(mrb, addr)); } +/* + * call-seq: + * addrinfo.getnameinfo(flags=0) -> [hostname, service] + * + * Returns the hostname and service name for the address. + * + * addr.getnameinfo #=> ["localhost", "http"] + * addr.getnameinfo(Socket::NI_NUMERICHOST) #=> ["127.0.0.1", "80"] + */ static mrb_value mrb_addrinfo_getnameinfo(mrb_state *mrb, mrb_value self) { @@ -200,42 +257,42 @@ mrb_addrinfo_getnameinfo(mrb_state *mrb, mrb_value self) return ary; } -#ifndef _WIN32 +/* + * call-seq: + * addrinfo.unix_path -> string + * + * Returns the Unix domain socket path. + * + * addr.unix_path #=> "/tmp/socket" + */ static mrb_value mrb_addrinfo_unix_path(mrb_state *mrb, mrb_value self) { mrb_value sastr = mrb_iv_get(mrb, self, MRB_IVSYM(sockaddr)); - if (!mrb_string_p(sastr) || ((struct sockaddr*)RSTRING_PTR(sastr))->sa_family != AF_UNIX) - mrb_raise(mrb, E_SOCKET_ERROR, "need AF_UNIX address"); - if (RSTRING_LEN(sastr) < (mrb_int)offsetof(struct sockaddr_un, sun_path) + 1) { - return mrb_str_new(mrb, "", 0); + if (!mrb_string_p(sastr)) { + mrb_raise(mrb, E_SOCKET_ERROR, "invalid sockaddr"); } - else { - return mrb_str_new_cstr(mrb, ((struct sockaddr_un*)RSTRING_PTR(sastr))->sun_path); - } -} -#endif + return mrb_hal_socket_unix_path(mrb, RSTRING_PTR(sastr), (size_t)RSTRING_LEN(sastr)); +} + +/* Helper to convert sockaddr to address list array [family, port, host, host] */ static mrb_value sa2addrlist(mrb_state *mrb, const struct sockaddr *sa, socklen_t salen) { - const char *afstr; - unsigned short port; - - switch (sa->sa_family) { - case AF_INET: - afstr = "AF_INET"; - port = ((struct sockaddr_in*)sa)->sin_port; - break; - case AF_INET6: - afstr = "AF_INET6"; - port = ((struct sockaddr_in6*)sa)->sin6_port; - break; - default: + /* Use lookup table for O(1) address family dispatch */ + const af_info_t *af_info = get_af_info(sa->sa_family); + if (!af_info) { mrb_raise(mrb, E_ARGUMENT_ERROR, "bad af"); return mrb_nil_value(); } + + /* Extract port using table-driven offset calculation */ + unsigned short port = 0; + if (af_info->has_port) { + port = *(unsigned short*)((char*)sa + af_info->port_offset); + } 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) @@ -243,7 +300,7 @@ sa2addrlist(mrb_state *mrb, const struct sockaddr *sa, socklen_t salen) mrb_str_resize(mrb, host, strlen(RSTRING_PTR(host))); mrb_value ary = mrb_ary_new_capa(mrb, 4); - mrb_ary_push(mrb, ary, mrb_str_new_cstr(mrb, afstr)); + mrb_ary_push(mrb, ary, mrb_str_new_cstr(mrb, af_info->name)); mrb_ary_push(mrb, ary, mrb_fixnum_value(port)); mrb_ary_push(mrb, ary, host); mrb_ary_push(mrb, ary, host); @@ -252,12 +309,14 @@ sa2addrlist(mrb_state *mrb, const struct sockaddr *sa, socklen_t salen) int mrb_io_fileno(mrb_state *mrb, mrb_value io); +/* Helper to extract file descriptor from socket object */ static int socket_fd(mrb_state *mrb, mrb_value sock) { return mrb_io_fileno(mrb, sock); } +/* Helper to get address family of socket by file descriptor */ static int socket_family(int s) { @@ -269,6 +328,15 @@ socket_family(int s) return ss.ss_family; } +/* + * call-seq: + * basicsocket.getpeereid -> [euid, egid] + * + * Returns the effective user ID and group ID of the peer process. + * Only available on systems that support getpeereid(). + * + * euid, egid = sock.getpeereid + */ static mrb_value mrb_basicsocket_getpeereid(mrb_state *mrb, mrb_value self) { @@ -289,6 +357,14 @@ mrb_basicsocket_getpeereid(mrb_state *mrb, mrb_value self) #endif } +/* + * call-seq: + * basicsocket.getpeername -> string + * + * Returns the remote socket address as a packed sockaddr string. + * + * sockaddr = sock.getpeername + */ static mrb_value mrb_basicsocket_getpeername(mrb_state *mrb, mrb_value self) { @@ -301,6 +377,14 @@ mrb_basicsocket_getpeername(mrb_state *mrb, mrb_value self) return mrb_str_new(mrb, (char*)&ss, salen); } +/* + * call-seq: + * basicsocket.getsockname -> string + * + * Returns the local socket address as a packed sockaddr string. + * + * sockaddr = sock.getsockname + */ static mrb_value mrb_basicsocket_getsockname(mrb_state *mrb, mrb_value self) { @@ -313,12 +397,21 @@ mrb_basicsocket_getsockname(mrb_state *mrb, mrb_value self) return mrb_str_new(mrb, (char*)&ss, salen); } +/* Helper to get Socket::Option class reference */ static struct RClass * socket_option_class(mrb_state *mrb) { return mrb_class_get_under_id(mrb, mrb_class_get_id(mrb, MRB_SYM(Socket)), MRB_SYM(Option)); } +/* + * call-seq: + * Socket::Option.new(family, level, optname, data) -> socket_option + * + * Creates a new Socket::Option object with the given parameters. + * + * opt = Socket::Option.new(Socket::AF_INET, Socket::SOL_SOCKET, Socket::SO_REUSEADDR, [1].pack("i")) + */ static mrb_value socket_option_init(mrb_state *mrb, mrb_value self) { @@ -334,6 +427,14 @@ socket_option_init(mrb_state *mrb, mrb_value self) return self; } +/* + * call-seq: + * Socket::Option.bool(family, level, optname, bool) -> socket_option + * + * Creates a new Socket::Option object from a boolean value. + * + * opt = Socket::Option.bool(Socket::AF_INET, Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) + */ static mrb_value socket_option_s_bool(mrb_state *mrb, mrb_value klass) { @@ -347,6 +448,14 @@ socket_option_s_bool(mrb_state *mrb, mrb_value klass) return mrb_obj_new(mrb, mrb_class_ptr(klass), 4, args); } +/* + * call-seq: + * Socket::Option.int(family, level, optname, integer) -> socket_option + * + * Creates a new Socket::Option object from an integer value. + * + * opt = Socket::Option.int(Socket::AF_INET, Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1) + */ static mrb_value socket_option_s_int(mrb_state *mrb, mrb_value klass) { @@ -360,30 +469,63 @@ socket_option_s_int(mrb_state *mrb, mrb_value klass) return mrb_obj_new(mrb, mrb_class_ptr(klass), 4, args); } +/* + * call-seq: + * socket_option.family -> integer + * + * Returns the address family of the socket option. + * + * opt.family #=> Socket::AF_INET + */ static mrb_value socket_option_family(mrb_state *mrb, mrb_value self) { return mrb_iv_get(mrb, self, MRB_SYM(family)); } +/* + * call-seq: + * socket_option.level -> integer + * + * Returns the protocol level of the socket option. + * + * opt.level #=> Socket::SOL_SOCKET + */ static mrb_value socket_option_level(mrb_state *mrb, mrb_value self) { return mrb_iv_get(mrb, self, MRB_SYM(level)); } +/* + * call-seq: + * socket_option.optname -> integer + * + * Returns the option name of the socket option. + * + * opt.optname #=> Socket::SO_REUSEADDR + */ static mrb_value socket_option_optname(mrb_state *mrb, mrb_value self) { return mrb_iv_get(mrb, self, MRB_SYM(optname)); } +/* + * call-seq: + * socket_option.data -> string + * + * Returns the raw data of the socket option as a string. + * + * opt.data #=> "\x01\x00\x00\x00" + */ static mrb_value socket_option_data(mrb_state *mrb, mrb_value self) { return mrb_iv_get(mrb, self, MRB_SYM(data)); } +/* Helper to extract integer value from Socket::Option data */ static int option_int(mrb_state *mrb, mrb_value self) { @@ -398,6 +540,14 @@ option_int(mrb_state *mrb, mrb_value self) return tmp; } +/* + * call-seq: + * socket_option.int -> integer + * + * Returns the socket option data as an integer value. + * + * opt.int #=> 1 + */ static mrb_value socket_option_int(mrb_state *mrb, mrb_value self) { @@ -405,6 +555,14 @@ socket_option_int(mrb_state *mrb, mrb_value self) return mrb_int_value(mrb, (mrb_int)i); } +/* + * call-seq: + * socket_option.bool -> true or false + * + * Returns the socket option data as a boolean value. + * + * opt.bool #=> true + */ static mrb_value socket_option_bool(mrb_state *mrb, mrb_value self) { @@ -412,6 +570,7 @@ socket_option_bool(mrb_state *mrb, mrb_value self) return mrb_bool_value((mrb_bool)i); } +/* Helper to raise not implemented error for unimplemented Socket::Option methods */ static mrb_value socket_option_notimp(mrb_state *mrb, mrb_value self) { @@ -419,6 +578,14 @@ socket_option_notimp(mrb_state *mrb, mrb_value self) return mrb_nil_value(); } +/* + * call-seq: + * socket_option.inspect -> string + * + * Returns a string representation of the socket option for debugging. + * + * opt.inspect #=> "#" + */ static mrb_value socket_option_inspect(mrb_state *mrb, mrb_value self) { @@ -429,32 +596,7 @@ socket_option_inspect(mrb_state *mrb, mrb_value self) if (mrb_integer_p(family)) { mrb_int fm = mrb_integer(family); - switch (fm) { - case PF_INET: - pf = "INET"; break; -#ifdef PF_INET6 - case PF_INET6: - pf = "INET6"; break; -#endif -#ifdef PF_IPX - case PF_IPX: - pf = "IPX"; break; -#endif -#ifdef PF_AX25 - case PF_AX25: - pf = "AX25"; break; -#endif -#ifdef PF_APPLETALK - case PF_APPLETALK: - pf = "APPLETALK"; break; -#endif -#ifdef PF_UNIX - case PF_UNIX: - pf = "UNIX"; break; -#endif - default: - break; - } + pf = get_pf_name((int)fm); } if (pf) { @@ -475,6 +617,14 @@ socket_option_inspect(mrb_state *mrb, mrb_value self) return str; } +/* + * call-seq: + * basicsocket.getsockopt(level, optname) -> string + * + * Gets a socket option. Returns the option value as a string. + * + * val = sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR) + */ static mrb_value mrb_basicsocket_getsockopt(mrb_state *mrb, mrb_value self) { @@ -494,6 +644,15 @@ mrb_basicsocket_getsockopt(mrb_state *mrb, mrb_value self) return mrb_obj_new(mrb, socket_option_class(mrb), 4, args); } +/* + * call-seq: + * basicsocket.recv(maxlen, flags=0) -> string + * + * Receives data from the socket. + * + * data = sock.recv(1024) + * data = sock.recv(512, 0) + */ static mrb_value mrb_basicsocket_recv(mrb_state *mrb, mrb_value self) { @@ -509,6 +668,13 @@ mrb_basicsocket_recv(mrb_state *mrb, mrb_value self) return buf; } +/* + * call-seq: + * basicsocket._recvfrom(maxlen, flags=0) -> [data, sockaddr] + * + * Internal method to receive data and sender address from socket. + * Returns data and packed sockaddr. + */ static mrb_value mrb_basicsocket_recvfrom(mrb_state *mrb, mrb_value self) { @@ -531,6 +697,14 @@ mrb_basicsocket_recvfrom(mrb_state *mrb, mrb_value self) return ary; } +/* + * call-seq: + * basicsocket.send(mesg, flags) -> integer + * + * Sends data through the socket. Returns the number of bytes sent. + * + * bytes_sent = sock.send("Hello", 0) + */ static mrb_value mrb_basicsocket_send(mrb_state *mrb, mrb_value self) { @@ -552,34 +726,37 @@ mrb_basicsocket_send(mrb_state *mrb, mrb_value self) return mrb_fixnum_value((mrb_int)n); } +/* + * call-seq: + * basicsocket._setnonblock(flag) -> nil + * + * Internal method to set or unset non-blocking mode on the socket. + * + * sock._setnonblock(true) # enable non-blocking + * sock._setnonblock(false) # disable non-blocking + */ static mrb_value mrb_basicsocket_setnonblock(mrb_state *mrb, mrb_value self) { mrb_bool nonblocking; -#ifdef _WIN32 - u_long mode = 1; -#endif mrb_get_args(mrb, "b", &nonblocking); int fd = socket_fd(mrb, self); -#ifdef _WIN32 - int flags = ioctlsocket(fd, FIONBIO, &mode); - if (flags != NO_ERROR) - mrb_sys_fail(mrb, "ioctlsocket"); -#else - int flags = fcntl(fd, F_GETFL, 0); - if (flags == 1) - mrb_sys_fail(mrb, "fcntl"); - if (nonblocking) - flags |= O_NONBLOCK; - else - flags &= ~O_NONBLOCK; - if (fcntl(fd, F_SETFL, flags) == -1) - mrb_sys_fail(mrb, "fcntl"); -#endif + + if (mrb_hal_socket_set_nonblock(mrb, fd, nonblocking) == -1) + mrb_sys_fail(mrb, "set_nonblock"); + return mrb_nil_value(); } +/* + * call-seq: + * basicsocket.setsockopt(level, optname, optval) -> 0 + * + * Sets a socket option. Level and optname are constants, optval is the value. + * + * sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1) + */ static mrb_value mrb_basicsocket_setsockopt(mrb_state *mrb, mrb_value self) { @@ -629,6 +806,16 @@ mrb_basicsocket_setsockopt(mrb_state *mrb, mrb_value self) return mrb_fixnum_value(0); } +/* + * call-seq: + * basicsocket.shutdown(how=Socket::SHUT_RDWR) -> 0 + * + * Shuts down part of the socket connection. + * + * sock.shutdown(Socket::SHUT_RD) # shutdown reading + * sock.shutdown(Socket::SHUT_WR) # shutdown writing + * sock.shutdown(Socket::SHUT_RDWR) # shutdown both (default) + */ static mrb_value mrb_basicsocket_shutdown(mrb_state *mrb, mrb_value self) { @@ -640,6 +827,7 @@ mrb_basicsocket_shutdown(mrb_state *mrb, mrb_value self) return mrb_fixnum_value(0); } +/* Helper to set socket flag on IO object */ static mrb_value mrb_basicsocket_set_is_socket(mrb_state *mrb, mrb_value self) { @@ -654,6 +842,14 @@ mrb_basicsocket_set_is_socket(mrb_state *mrb, mrb_value self) return mrb_bool_value(b); } +/* + * call-seq: + * IPSocket.ntop(af, addr) -> string + * + * Converts a network address to a string representation. + * + * IPSocket.ntop(Socket::AF_INET, "\x7f\x00\x00\x01") #=> "127.0.0.1" + */ static mrb_value mrb_ipsocket_ntop(mrb_state *mrb, mrb_value klass) { @@ -663,11 +859,25 @@ mrb_ipsocket_ntop(mrb_state *mrb, mrb_value klass) mrb_get_args(mrb, "is", &af, &addr, &n); if ((af == AF_INET && n != 4) || (af == AF_INET6 && n != 16) || - inet_ntop((int)af, addr, buf, sizeof(buf)) == NULL) + mrb_hal_socket_inet_ntop((int)af, addr, buf, sizeof(buf)) == NULL) mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid address"); return mrb_str_new_cstr(mrb, buf); } +/* + * call-seq: + * IPSocket.pton(af, hostname) -> string + * + * Converts a string representation of an address to network format. + * + * IPSocket.pton(Socket::AF_INET, "127.0.0.1") #=> "\x7f\x00\x00\x01" + */ +static mrb_noreturn void +invalid_address_error(mrb_state *mrb) +{ + mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid address"); +} + static mrb_value mrb_ipsocket_pton(mrb_state *mrb, mrb_value klass) { @@ -676,41 +886,51 @@ mrb_ipsocket_pton(mrb_state *mrb, mrb_value klass) char buf[50]; mrb_get_args(mrb, "is", &af, &bp, &n); - if ((size_t)n > sizeof(buf) - 1) goto invalid; + if ((size_t)n > sizeof(buf) - 1) { + invalid_address_error(mrb); + } memcpy(buf, bp, n); buf[n] = '\0'; if (af == AF_INET) { struct in_addr in; - if (inet_pton(AF_INET, buf, (void*)&in.s_addr) != 1) - goto invalid; + if (mrb_hal_socket_inet_pton(AF_INET, buf, (void*)&in.s_addr) != 1) { + invalid_address_error(mrb); + } return mrb_str_new(mrb, (char*)&in.s_addr, 4); } else if (af == AF_INET6) { struct in6_addr in6; - if (inet_pton(AF_INET6, buf, (void*)&in6.s6_addr) != 1) - goto invalid; + if (mrb_hal_socket_inet_pton(AF_INET6, buf, (void*)&in6.s6_addr) != 1) { + invalid_address_error(mrb); + } return mrb_str_new(mrb, (char*)&in6.s6_addr, 16); } else { mrb_raise(mrb, E_ARGUMENT_ERROR, "unsupported address family"); } - -invalid: - mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid address"); - return mrb_nil_value(); /* dummy */ + return mrb_nil_value(); /* not reached */ } +/* + * call-seq: + * ipsocket.recvfrom(maxlen, flags=0) -> [data, [family, port, hostname, ip]] + * + * Receives data from the socket and returns sender address information. + * + * data, addr = sock.recvfrom(1024) + * # addr => ["AF_INET", 12345, "hostname", "192.168.1.1"] + */ static mrb_value mrb_ipsocket_recvfrom(mrb_state *mrb, mrb_value self) { - struct sockaddr_storage ss; mrb_int maxlen; mrb_int flags = 0; mrb_get_args(mrb, "i|i", &maxlen, &flags); mrb_value buf = mrb_str_new_capa(mrb, maxlen); + struct sockaddr_storage ss; socklen_t socklen = sizeof(ss); int fd = socket_fd(mrb, self); ssize_t n = recvfrom(fd, RSTRING_PTR(buf), (fsize_t)maxlen, (int)flags, @@ -727,6 +947,14 @@ mrb_ipsocket_recvfrom(mrb_state *mrb, mrb_value self) return pair; } +/* + * call-seq: + * Socket.gethostname -> string + * + * Returns the hostname of the current machine. + * + * Socket.gethostname #=> "localhost" + */ static mrb_value mrb_socket_gethostname(mrb_state *mrb, mrb_value cls) { @@ -743,6 +971,13 @@ mrb_socket_gethostname(mrb_state *mrb, mrb_value cls) return buf; } +/* + * call-seq: + * Socket._accept(fd) -> [new_fd, sockaddr] + * + * Internal method to accept a connection on a socket file descriptor. + * Returns the new file descriptor and remote address. + */ static mrb_value mrb_socket_accept(mrb_state *mrb, mrb_value klass) { @@ -778,6 +1013,12 @@ mrb_socket_accept2(mrb_state *mrb, mrb_value klass) return ary; } +/* + * call-seq: + * Socket._bind(fd, sockaddr) -> 0 + * + * Internal method to bind a socket file descriptor to the given address. + */ static mrb_value mrb_socket_bind(mrb_state *mrb, mrb_value klass) { @@ -791,6 +1032,12 @@ mrb_socket_bind(mrb_state *mrb, mrb_value klass) return mrb_nil_value(); } +/* + * call-seq: + * Socket._connect(fd, sockaddr) -> 0 + * + * Internal method to connect a socket file descriptor to the given address. + */ static mrb_value mrb_socket_connect(mrb_state *mrb, mrb_value klass) { @@ -804,6 +1051,12 @@ mrb_socket_connect(mrb_state *mrb, mrb_value klass) return mrb_nil_value(); } +/* + * call-seq: + * Socket._listen(fd, backlog) -> 0 + * + * Internal method to set a socket file descriptor to listen for connections. + */ static mrb_value mrb_socket_listen(mrb_state *mrb, mrb_value klass) { @@ -830,56 +1083,60 @@ mrb_socket_sockaddr_family(mrb_state *mrb, mrb_value klass) return mrb_fixnum_value(sa->sa_family); } +/* + * call-seq: + * Socket.sockaddr_un(path) -> string + * + * Returns a packed sockaddr_un structure for the given Unix socket path. + * + * Socket.sockaddr_un("/tmp/socket") + * Socket.sockaddr_un("/var/run/daemon.sock") + */ static mrb_value mrb_socket_sockaddr_un(mrb_state *mrb, mrb_value klass) { -#ifdef _WIN32 - mrb_raise(mrb, E_NOTIMP_ERROR, "sockaddr_un unsupported on Windows"); - return mrb_nil_value(); -#else mrb_value path; - struct sockaddr_un *sunp; mrb_get_args(mrb, "S", &path); - if ((size_t)RSTRING_LEN(path) > sizeof(sunp->sun_path) - 1) { - mrb_raisef(mrb, E_ARGUMENT_ERROR, "too long unix socket path (max: %d bytes)", (int)sizeof(sunp->sun_path) - 1); - } - mrb_value s = mrb_str_new_capa(mrb, sizeof(struct sockaddr_un)); - sunp = (struct sockaddr_un*)RSTRING_PTR(s); -#if HAVE_SA_LEN - sunp->sun_len = sizeof(struct sockaddr_un); -#endif - sunp->sun_family = AF_UNIX; - memcpy(sunp->sun_path, RSTRING_PTR(path), RSTRING_LEN(path)); - sunp->sun_path[RSTRING_LEN(path)] = '\0'; - mrb_str_resize(mrb, s, sizeof(struct sockaddr_un)); - return s; -#endif + return mrb_hal_socket_sockaddr_un(mrb, RSTRING_PTR(path), (size_t)RSTRING_LEN(path)); } +/* + * call-seq: + * Socket.socketpair(domain, type, protocol=0) -> [socket1, socket2] + * Socket.pair(domain, type, protocol=0) -> [socket1, socket2] + * + * Creates a pair of connected sockets. + * + * sock1, sock2 = Socket.socketpair(Socket::AF_UNIX, Socket::SOCK_STREAM) + * sock1, sock2 = Socket.pair(Socket::AF_UNIX, Socket::SOCK_DGRAM) + */ static mrb_value mrb_socket_socketpair(mrb_state *mrb, mrb_value klass) { -#ifdef _WIN32 - mrb_raise(mrb, E_NOTIMP_ERROR, "socketpair unsupported on Windows"); - return mrb_nil_value(); -#else mrb_int domain, type, protocol; int sv[2]; mrb_get_args(mrb, "iii", &domain, &type, &protocol); - mrb_value ary = mrb_ary_new_capa(mrb, 2); - if (socketpair(domain, type, protocol, sv) == -1) { + if (mrb_hal_socket_socketpair(mrb, (int)domain, (int)type, (int)protocol, sv) == -1) { mrb_sys_fail(mrb, "socketpair"); } + mrb_value ary = mrb_ary_new_capa(mrb, 2); mrb_ary_push(mrb, ary, mrb_fixnum_value(sv[0])); mrb_ary_push(mrb, ary, mrb_fixnum_value(sv[1])); return ary; -#endif } +/* + * call-seq: + * Socket._socket(domain, type, protocol) -> fd + * + * Internal method to create a new socket and return its file descriptor. + * + * fd = Socket._socket(Socket::AF_INET, Socket::SOCK_STREAM, 0) + */ static mrb_value mrb_socket_socket(mrb_state *mrb, mrb_value klass) { @@ -893,6 +1150,7 @@ mrb_socket_socket(mrb_state *mrb, mrb_value klass) return mrb_fixnum_value(s); } +/* Helper to allocate TCPSocket object */ static mrb_value mrb_tcpsocket_allocate(mrb_state *mrb, mrb_value klass) { @@ -910,6 +1168,13 @@ mrb_tcpsocket_allocate(mrb_state *mrb, mrb_value klass) * will break on socket descriptors. */ #ifdef _WIN32 +/* + * call-seq: + * basicsocket.close -> nil + * + * Windows-specific implementation to close socket using closesocket(). + * Overrides IO#close for socket objects on Windows. + */ static mrb_value mrb_win32_basicsocket_close(mrb_state *mrb, mrb_value self) { @@ -918,6 +1183,13 @@ mrb_win32_basicsocket_close(mrb_state *mrb, mrb_value self) return mrb_nil_value(); } +/* + * call-seq: + * basicsocket.sysread(maxlen, outbuf=nil) -> string + * + * Windows-specific implementation to read from socket using recv(). + * Overrides IO#sysread for socket objects on Windows. + */ static mrb_value mrb_win32_basicsocket_sysread(mrb_state *mrb, mrb_value self) { @@ -961,6 +1233,13 @@ mrb_win32_basicsocket_sysread(mrb_state *mrb, mrb_value self) return buf; } +/* + * call-seq: + * basicsocket.sysseek(offset, whence) -> integer + * + * Windows-specific implementation that raises NotImplementedError. + * Sockets don't support seeking operations. + */ static mrb_value mrb_win32_basicsocket_sysseek(mrb_state *mrb, mrb_value self) { @@ -968,6 +1247,13 @@ mrb_win32_basicsocket_sysseek(mrb_state *mrb, mrb_value self) return mrb_nil_value(); } +/* + * call-seq: + * basicsocket.syswrite(string) -> integer + * + * Windows-specific implementation to write to socket using send(). + * Overrides IO#syswrite for socket objects on Windows. + */ static mrb_value mrb_win32_basicsocket_syswrite(mrb_state *mrb, mrb_value self) { @@ -987,20 +1273,12 @@ mrb_win32_basicsocket_syswrite(mrb_state *mrb, mrb_value self) void mrb_mruby_socket_gem_init(mrb_state* mrb) { -#ifdef _WIN32 - WSADATA wsaData; - int result; - result = WSAStartup(MAKEWORD(2,2), &wsaData); - if (result != NO_ERROR) - mrb_raise(mrb, E_RUNTIME_ERROR, "WSAStartup failed"); -#endif + mrb_hal_socket_init(mrb); struct RClass *ainfo = mrb_define_class_id(mrb, MRB_SYM(Addrinfo), mrb->object_class); mrb_define_class_method_id(mrb, ainfo, MRB_SYM(getaddrinfo), mrb_addrinfo_getaddrinfo, MRB_ARGS_REQ(2)|MRB_ARGS_OPT(4)); mrb_define_method_id(mrb, ainfo, MRB_SYM(getnameinfo), mrb_addrinfo_getnameinfo, MRB_ARGS_OPT(1)); -#ifndef _WIN32 mrb_define_method_id(mrb, ainfo, MRB_SYM(unix_path), mrb_addrinfo_unix_path, MRB_ARGS_NONE()); -#endif struct RClass *io = mrb_class_get_id(mrb, MRB_SYM(IO)); @@ -1077,7 +1355,5 @@ mrb_mruby_socket_gem_init(mrb_state* mrb) void mrb_mruby_socket_gem_final(mrb_state* mrb) { -#ifdef _WIN32 - WSACleanup(); -#endif + mrb_hal_socket_final(mrb); } diff --git a/mrbgems/mruby-sprintf/mrblib/string.rb b/mrbgems/mruby-sprintf/mrblib/string.rb index d7e55536a..dbd626350 100644 --- a/mrbgems/mruby-sprintf/mrblib/string.rb +++ b/mrbgems/mruby-sprintf/mrblib/string.rb @@ -1,4 +1,19 @@ class String + # + # call-seq: + # str % arg -> new_str + # str % args -> new_str + # + # Format - Uses str as a format specification, and returns the result + # of applying it to arg. If the format specification contains more than + # one substitution, then arg must be an Array or Hash containing the values + # to be substituted. See sprintf for details of the format string. + # + # "%05d" % 123 #=> "00123" + # "%-5s: %016x" % [ "ID", self.object_id ] #=> "ID : 00002b054ec93168" + # "foo = %{foo}" % { :foo => 'bar' } #=> "foo = bar" + # "%{foo}f" % { :foo => 1 } #=> "1f" + # def %(args) if args.is_a? Array sprintf(self, *args) diff --git a/mrbgems/mruby-sprintf/src/sprintf.c b/mrbgems/mruby-sprintf/src/sprintf.c index e083503ba..622ec89ca 100644 --- a/mrbgems/mruby-sprintf/src/sprintf.c +++ b/mrbgems/mruby-sprintf/src/sprintf.c @@ -85,6 +85,94 @@ mrb_uint_to_cstr(char *buf, size_t len, mrb_int num, int base) #define FPREC 64 #define FPREC0 128 +/* Format specifier types for lookup table */ +#define FMT_INVALID 0 +#define FMT_FLAG 1 /* space, #, +, -, 0 */ +#define FMT_DIGIT 2 /* 1-9 for width */ +#define FMT_NAMED 3 /* < { for named args */ +#define FMT_WIDTH 4 /* * for width from arg */ +#define FMT_PREC 5 /* . for precision */ +#define FMT_LITERAL 6 /* % \n \0 */ +#define FMT_CHAR 7 /* c */ +#define FMT_STRING 8 /* s p */ +#define FMT_INTEGER 9 /* d i o x X b B u */ +#define FMT_FLOAT 10 /* f g G e E */ + +/* Format specifier info structure */ +typedef struct { + int type; /* FMT_* type */ + int base; /* number base for integers */ + int subtype; /* format-specific subtype */ +} fmt_spec_t; + + +/* Get format specifier info for character c */ +static inline fmt_spec_t get_format_info(unsigned char c) { + static const fmt_spec_t invalid = {FMT_INVALID, 0, 0}; + + switch (c) { + /* Control characters and whitespace */ + case '\0': case '\n': + return (fmt_spec_t){FMT_LITERAL, 0, 0}; + + /* Flags */ + case ' ': return (fmt_spec_t){FMT_FLAG, 0, FSPACE}; + case '#': return (fmt_spec_t){FMT_FLAG, 0, FSHARP}; + case '+': return (fmt_spec_t){FMT_FLAG, 0, FPLUS}; + case '-': return (fmt_spec_t){FMT_FLAG, 0, FMINUS}; + case '0': return (fmt_spec_t){FMT_FLAG, 0, FZERO}; + + /* Width digits */ + case '1': return (fmt_spec_t){FMT_DIGIT, 0, 1}; + case '2': return (fmt_spec_t){FMT_DIGIT, 0, 2}; + case '3': return (fmt_spec_t){FMT_DIGIT, 0, 3}; + case '4': return (fmt_spec_t){FMT_DIGIT, 0, 4}; + case '5': return (fmt_spec_t){FMT_DIGIT, 0, 5}; + case '6': return (fmt_spec_t){FMT_DIGIT, 0, 6}; + case '7': return (fmt_spec_t){FMT_DIGIT, 0, 7}; + case '8': return (fmt_spec_t){FMT_DIGIT, 0, 8}; + case '9': return (fmt_spec_t){FMT_DIGIT, 0, 9}; + + /* Width and precision */ + case '*': return (fmt_spec_t){FMT_WIDTH, 0, 0}; + case '.': return (fmt_spec_t){FMT_PREC, 0, 0}; + + /* Named arguments */ + case '<': return (fmt_spec_t){FMT_NAMED, 0, '<'}; + case '{': return (fmt_spec_t){FMT_NAMED, 0, '{'}; + + /* Literal percent */ + case '%': return (fmt_spec_t){FMT_LITERAL, 0, '%'}; + + /* Character formatting */ + case 'c': return (fmt_spec_t){FMT_CHAR, 0, 0}; + + /* String formatting */ + case 's': return (fmt_spec_t){FMT_STRING, 0, 0}; + case 'p': return (fmt_spec_t){FMT_STRING, 0, 1}; /* inspect format */ + + /* Integer formatting */ + case 'd': return (fmt_spec_t){FMT_INTEGER, 10, 1}; /* signed decimal */ + case 'i': return (fmt_spec_t){FMT_INTEGER, 10, 1}; /* signed decimal */ + case 'u': return (fmt_spec_t){FMT_INTEGER, 10, 1}; /* unsigned (same as signed in mruby) */ + case 'o': return (fmt_spec_t){FMT_INTEGER, 8, 0}; /* octal */ + case 'x': return (fmt_spec_t){FMT_INTEGER, 16, 0}; /* hex lowercase */ + case 'X': return (fmt_spec_t){FMT_INTEGER, 16, 1}; /* hex uppercase */ + case 'b': return (fmt_spec_t){FMT_INTEGER, 2, 0}; /* binary lowercase */ + case 'B': return (fmt_spec_t){FMT_INTEGER, 2, 1}; /* binary uppercase */ + + /* Float formatting */ + case 'f': return (fmt_spec_t){FMT_FLOAT, 0, 'f'}; + case 'e': return (fmt_spec_t){FMT_FLOAT, 0, 'e'}; + case 'E': return (fmt_spec_t){FMT_FLOAT, 0, 'E'}; + case 'g': return (fmt_spec_t){FMT_FLOAT, 0, 'g'}; + case 'G': return (fmt_spec_t){FMT_FLOAT, 0, 'G'}; + + default: + return invalid; + } +} + #ifndef MRB_NO_FLOAT static int fmt_float(char *buf, size_t buf_size, char fmt, int flags, int width, int prec, mrb_float f) @@ -247,13 +335,11 @@ get_num(mrb_state *mrb, const char *p, const char *end, int *valp) static void get_hash(mrb_state *mrb, mrb_value *hash, mrb_int argc, const mrb_value *argv) { - mrb_value tmp; - if (!mrb_undef_p(*hash)) return; if (argc != 2) { mrb_raise(mrb, E_ARGUMENT_ERROR, "one hash required"); } - tmp = mrb_check_hash_type(mrb, argv[1]); + mrb_value tmp = mrb_check_hash_type(mrb, argv[1]); if (mrb_nil_p(tmp)) { mrb_raise(mrb, E_ARGUMENT_ERROR, "one hash required"); } @@ -325,272 +411,241 @@ mrb_str_format(mrb_state *mrb, mrb_int argc, const mrb_value *argv, mrb_value fm nextvalue = mrb_undef_value(); retry: - switch (*p) { - default: - mrb_raisef(mrb, E_ARGUMENT_ERROR, "malformed format string - %%%c", *p); - break; + if (p >= end) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "malformed format string - unexpected end"); + } + { + fmt_spec_t spec = get_format_info(*p); - case ' ': - CHECK_FOR_FLAGS(flags); - flags |= FSPACE; - p++; - goto retry; + switch (spec.type) { + case FMT_INVALID: + mrb_raisef(mrb, E_ARGUMENT_ERROR, "malformed format string - %%%c", *p); + break; - case '#': - CHECK_FOR_FLAGS(flags); - flags |= FSHARP; - p++; - goto retry; + case FMT_FLAG: + CHECK_FOR_FLAGS(flags); + flags |= spec.subtype; + p++; + goto retry; - case '+': - CHECK_FOR_FLAGS(flags); - flags |= FPLUS; - p++; - goto retry; - - case '-': - CHECK_FOR_FLAGS(flags); - flags |= FMINUS; - p++; - goto retry; - - case '0': - CHECK_FOR_FLAGS(flags); - flags |= FZERO; - p++; - goto retry; - - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - GETNUM(n, width); - if (*p == '$') { - if (!mrb_undef_p(nextvalue)) { - mrb_raisef(mrb, E_ARGUMENT_ERROR, "value given twice - %i$", n); + case FMT_DIGIT: + GETNUM(n, width); + if (*p == '$') { + if (!mrb_undef_p(nextvalue)) { + mrb_raisef(mrb, E_ARGUMENT_ERROR, "value given twice - %i$", n); + } + nextvalue = GETPOSARG(n); + p++; + goto retry; } - nextvalue = GETPOSARG(n); + CHECK_FOR_WIDTH(flags); + width = n; + flags |= FWIDTH; + goto retry; + + case FMT_NAMED: { + const char *start = p; + char term = (spec.subtype == '<') ? '>' : '}'; + + for (; p < end && *p != term; ) + p++; + if (id) { + mrb_raisef(mrb, E_ARGUMENT_ERROR, "name%l after <%n>", + start, p - start + 1, id); + } + CHECKNAMEARG(start, p - start + 1); + get_hash(mrb, &hash, argc, argv); + id = mrb_intern_check(mrb, start + 1, p - start - 1); + if (id) { + nextvalue = mrb_hash_fetch(mrb, hash, mrb_symbol_value(id), mrb_undef_value()); + } + if (!id || mrb_undef_p(nextvalue)) { + mrb_raisef(mrb, E_KEY_ERROR, "key%l not found", start, p - start + 1); + } + if (term == '}') goto format_s; p++; goto retry; } - CHECK_FOR_WIDTH(flags); - width = n; - flags |= FWIDTH; - goto retry; - case '<': - case '{': { - const char *start = p; - char term = (*p == '<') ? '>' : '}'; - - for (; p < end && *p != term; ) - p++; - if (id) { - mrb_raisef(mrb, E_ARGUMENT_ERROR, "name%l after <%n>", - start, p - start + 1, id); - } - CHECKNAMEARG(start, p - start + 1); - get_hash(mrb, &hash, argc, argv); - id = mrb_intern_check(mrb, start + 1, p - start - 1); - if (id) { - nextvalue = mrb_hash_fetch(mrb, hash, mrb_symbol_value(id), mrb_undef_value()); - } - if (!id || mrb_undef_p(nextvalue)) { - mrb_raisef(mrb, E_KEY_ERROR, "key%l not found", start, p - start + 1); - } - if (term == '}') goto format_s; - p++; - goto retry; - } - - case '*': - CHECK_FOR_WIDTH(flags); - flags |= FWIDTH; - GETASTER(width); - if (width > INT16_MAX || INT16_MIN > width) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "width too big"); - } - if (width < 0) { - flags |= FMINUS; - width = -width; - } - p++; - goto retry; - - case '.': - if (flags & FPREC0) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "precision given twice"); - } - flags |= FPREC|FPREC0; - - p++; - if (*p == '*') { - GETASTER(prec); - if (prec < 0) { /* ignore negative precision */ - flags &= ~FPREC; + case FMT_WIDTH: + CHECK_FOR_WIDTH(flags); + flags |= FWIDTH; + GETASTER(width); + if (width > INT16_MAX || INT16_MIN > width) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "width too big"); + } + if (width < 0) { + flags |= FMINUS; + width = -width; } p++; goto retry; - } - GETNUM(prec, precision); - goto retry; - case '\n': - case '\0': - p--; - /* fallthrough */ - case '%': - if (flags != FNONE) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid format character - %"); - } - PUSH("%", 1); - break; - - case 'c': { - mrb_value val = GETARG(); - mrb_value tmp; - char *c; - - tmp = mrb_check_string_type(mrb, val); - if (!mrb_nil_p(tmp)) { - if (RSTRING_LEN(tmp) != 1) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "%c requires a character"); + case FMT_PREC: + if (flags & FPREC0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "precision given twice"); } - } - else if (mrb_integer_p(val)) { - mrb_int n = mrb_integer(val); + flags |= FPREC|FPREC0; + + p++; + if (*p == '*') { + GETASTER(prec); + if (prec < 0) { /* ignore negative precision */ + flags &= ~FPREC; + } + p++; + goto retry; + } + GETNUM(prec, precision); + goto retry; + + case FMT_LITERAL: + if (spec.subtype == 0) { /* \n or \0 */ + p--; + } + if (flags != FNONE) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid format character - %"); + } + PUSH("%", 1); + break; + + case FMT_CHAR: { + /* CHARACTER FORMATTING (%c) */ + mrb_value val = GETARG(); + mrb_value tmp; + char *c; + + /* Convert argument to character string */ + tmp = mrb_check_string_type(mrb, val); + if (!mrb_nil_p(tmp)) { + if (RSTRING_LEN(tmp) != 1) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "%c requires a character"); + } + } + else if (mrb_integer_p(val)) { + mrb_int n = mrb_integer(val); #ifndef MRB_UTF8_STRING - char buf[1]; - - buf[0] = (char)n&0xff; - tmp = mrb_str_new(mrb, buf, 1); -#else - if (n < 0x80) { char buf[1]; - buf[0] = (char)n; + buf[0] = (char)n&0xff; tmp = mrb_str_new(mrb, buf, 1); +#else + if (n < 0x80) { + char buf[1]; + + buf[0] = (char)n; + tmp = mrb_str_new(mrb, buf, 1); + } + else { + tmp = mrb_funcall_argv(mrb, val, MRB_SYM(chr), 0, NULL); + mrb_check_type(mrb, tmp, MRB_TT_STRING); + } +#endif } else { - tmp = mrb_funcall_argv(mrb, val, MRB_SYM(chr), 0, NULL); - mrb_check_type(mrb, tmp, MRB_TT_STRING); + mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid character"); } -#endif + /* Format and output the character with width/alignment */ + c = RSTRING_PTR(tmp); + n = (int)RSTRING_LEN(tmp); + if (!(flags & FWIDTH)) { + PUSH(c, n); + } + else if ((flags & FMINUS)) { + PUSH(c, n); + if (width>0) FILL(' ', width-1); + } + else { + if (width>0) FILL(' ', width-1); + PUSH(c, n); + } + mrb_gc_arena_restore(mrb, ai); } - else { - mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid character"); - } - c = RSTRING_PTR(tmp); - n = (int)RSTRING_LEN(tmp); - if (!(flags & FWIDTH)) { - PUSH(c, n); - } - else if ((flags & FMINUS)) { - PUSH(c, n); - if (width>0) FILL(' ', width-1); - } - else { - if (width>0) FILL(' ', width-1); - PUSH(c, n); - } - mrb_gc_arena_restore(mrb, ai); - } - break; + break; - case 's': - case 'p': + case FMT_STRING: format_s: - { - mrb_value arg = GETARG(); - mrb_int len; - mrb_int slen; + { + /* STRING FORMATTING (%s, %p) */ + mrb_value arg = GETARG(); + mrb_int len; + mrb_int slen; - if (*p == 'p') arg = mrb_inspect(mrb, arg); - str = mrb_obj_as_string(mrb, arg); - len = RSTRING_LEN(str); - if (RSTRING(result)->flags & MRB_STR_EMBED) { - mrb_int tmp_n = len; - RSTRING(result)->flags &= ~MRB_STR_EMBED_LEN_MASK; - RSTRING(result)->flags |= tmp_n << MRB_STR_EMBED_LEN_SHIFT; - } - else { - RSTRING(result)->as.heap.len = blen; - } - if (flags&(FPREC|FWIDTH)) { - slen = RSTRING_LEN(str); - if (slen < 0) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid mbstring sequence"); + /* Convert to string (with inspect for %p) */ + if (spec.subtype == 1) arg = mrb_inspect(mrb, arg); /* 'p' format */ + str = mrb_obj_as_string(mrb, arg); + len = RSTRING_LEN(str); + + /* Update result string length for embedded strings */ + if (RSTRING(result)->flags & MRB_STR_EMBED) { + mrb_int tmp_n = len; + RSTRING(result)->flags &= ~MRB_STR_EMBED_LEN_MASK; + RSTRING(result)->flags |= tmp_n << MRB_STR_EMBED_LEN_SHIFT; } - if ((flags&FPREC) && (prec < slen)) { - char *p = RSTRING_PTR(str) + prec; - slen = prec; - len = (mrb_int)(p - RSTRING_PTR(str)); + else { + RSTRING(result)->as.heap.len = blen; } - /* need to adjust multi-byte string pos */ - if ((flags&FWIDTH) && (width > slen)) { - width -= (int)slen; - if (!(flags&FMINUS)) { - FILL(' ', width); + + /* Handle precision and width formatting */ + if (flags&(FPREC|FWIDTH)) { + slen = RSTRING_LEN(str); + if (slen < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid mbstring sequence"); } - PUSH(RSTRING_PTR(str), len); - if (flags&FMINUS) { - FILL(' ', width); + if ((flags&FPREC) && (prec < slen)) { + char *p = RSTRING_PTR(str) + prec; + slen = prec; + len = (mrb_int)(p - RSTRING_PTR(str)); + } + /* Apply width formatting with padding */ + if ((flags&FWIDTH) && (width > slen)) { + width -= (int)slen; + if (!(flags&FMINUS)) { + FILL(' ', width); + } + PUSH(RSTRING_PTR(str), len); + if (flags&FMINUS) { + FILL(' ', width); + } + break; } - break; } + PUSH(RSTRING_PTR(str), len); + mrb_gc_arena_restore(mrb, ai); } - PUSH(RSTRING_PTR(str), len); - mrb_gc_arena_restore(mrb, ai); - } - break; + break; - case 'd': - case 'i': - case 'o': - case 'x': - case 'X': - case 'b': - case 'B': - case 'u': { - mrb_value val = GETARG(); - char nbuf[69], *s; - const char *prefix = NULL; - int sign = 0, dots = 0; - char sc = 0; - char fc = 0; - mrb_int v = 0; - int base; - int len; + case FMT_INTEGER: { + /* INTEGER FORMATTING (%d, %i, %o, %x, %X, %b, %B, %u) */ + mrb_value val = GETARG(); + char nbuf[69], *s; + const char *prefix = NULL; + int sign = 0, dots = 0; + char sc = 0; + char fc = 0; + mrb_int v = 0; + int base; + int len; - if (flags & FSHARP) { - switch (*p) { - case 'o': prefix = "0"; break; - case 'x': prefix = "0x"; break; - case 'X': prefix = "0X"; break; - case 'b': prefix = "0b"; break; - case 'B': prefix = "0B"; break; - default: break; - } - } - - switch (*p) { - case 'o': - base = 8; break; - case 'x': - case 'X': - base = 16; break; - case 'b': - case 'B': - base = 2; break; - case 'u': - case 'd': - case 'i': + /* Determine base and signedness from lookup table */ + base = spec.base; + if (spec.subtype == 1) { /* signed formats: d, i, u */ sign = 1; - /* fall through */ - default: - base = 10; break; - } + } + /* Set prefix for alternative format (#) */ + if (flags & FSHARP) { + switch (base) { + case 8: prefix = "0"; break; + case 16: prefix = (spec.subtype == 1) ? "0X" : "0x"; break; + case 2: prefix = (spec.subtype == 1) ? "0B" : "0b"; break; + default: break; + } + } + + /* Convert value to integer and format as string */ bin_retry: - switch (mrb_type(val)) { + switch (mrb_type(val)) { #ifndef MRB_NO_FLOAT case MRB_TT_FLOAT: val = mrb_float_to_integer(mrb, val); @@ -650,8 +705,7 @@ retry: } { - size_t size; - size = strlen(s); + size_t size = strlen(s); /* PARANOID: assert(size <= MRB_INT_MAX) */ len = (int)size; } @@ -676,17 +730,18 @@ retry: s++; len--; } } - if (*p == 'X') { - char *pp = s; - int c; - while ((c = (int)(unsigned char)*pp) != 0) { - *pp = toupper(c); - pp++; + /* Convert to uppercase for X, B formats */ + if (spec.subtype == 1) { /* uppercase formats: X, B */ + char *pp = s; + int c; + while ((c = (int)(unsigned char)*pp) != 0) { + *pp = toupper(c); + pp++; + } + if (base == 16) { + fc = 'F'; + } } - if (base == 16) { - fc = 'F'; - } - } if (prefix && !prefix[1]) { /* octal */ if (dots) { @@ -705,8 +760,7 @@ retry: } if (prefix) { - size_t size; - size = strlen(prefix); + size_t size = strlen(prefix); /* PARANOID: assert(size <= MRB_INT_MAX). * this check is absolutely paranoid. */ width -= (int)size; @@ -755,22 +809,19 @@ retry: FILL(fc, prec - len); } } - PUSH(s, len); - if (width > 0) { - FILL(' ', width); + PUSH(s, len); + if (width > 0) { + FILL(' ', width); + } } - } - break; + break; - case 'f': - case 'g': - case 'G': - case 'e': - case 'E': { + case FMT_FLOAT: { + /* FLOAT FORMATTING (%f, %g, %G, %e, %E) */ #ifdef MRB_NO_FLOAT - mrb_raisef(mrb, E_ARGUMENT_ERROR, "%%%c not supported with MRB_NO_FLOAT defined", *p); + mrb_raisef(mrb, E_ARGUMENT_ERROR, "%%%c not supported with MRB_NO_FLOAT defined", spec.subtype); #else - mrb_value val = GETARG(); + mrb_value val = GETARG(); double fval; mrb_int need = 6; @@ -834,14 +885,15 @@ retry: need += 20; CHECK(need); - n = fmt_float(&buf[blen], need, *p, flags, width, prec, fval); + n = fmt_float(&buf[blen], need, spec.subtype, flags, width, prec, fval); if (n < 0 || n >= need) { mrb_raise(mrb, E_RUNTIME_ERROR, "formatting error"); } - blen += n; + blen += n; #endif + } + break; } - break; } } @@ -856,7 +908,7 @@ retry: * format(format_string [, arguments...] ) -> string * sprintf(format_string [, arguments...] ) -> string * - * Returns the string resulting from applying format_string to + * Returns the string resulting from applying *format_string* to * any additional arguments. Within the format string, any characters * other than format sequences are copied to the result. * @@ -868,7 +920,7 @@ retry: * sequence consists of a percent sign, followed by optional flags, * width, and precision indicators, then terminated with a field type * character. The field type controls how the corresponding - * sprintf argument is to be interpreted, while the flags + * `sprintf` argument is to be interpreted, while the flags * modify that interpretation. * * The field type characters are: @@ -1043,7 +1095,7 @@ retry: * numeric fields, the precision controls the number of decimal places * displayed. For string fields, the precision determines the maximum * number of characters to be copied from the string. (Thus, the format - * sequence %10.10s will always contribute exactly ten + * sequence `%10.10s` will always contribute exactly ten * characters to the result.) * * Examples of precisions: @@ -1129,8 +1181,8 @@ void mrb_mruby_sprintf_gem_init(mrb_state *mrb) { struct RClass *krn = mrb->kernel_module; - mrb_define_module_function(mrb, krn, "sprintf", mrb_f_sprintf, MRB_ARGS_ANY()); - mrb_define_module_function(mrb, krn, "format", mrb_f_sprintf, MRB_ARGS_ANY()); + mrb_define_module_function_id(mrb, krn, MRB_SYM(sprintf), mrb_f_sprintf, MRB_ARGS_ANY()); + mrb_define_module_function_id(mrb, krn, MRB_SYM(format), mrb_f_sprintf, MRB_ARGS_ANY()); } void diff --git a/mrbgems/mruby-strftime/README.md b/mrbgems/mruby-strftime/README.md new file mode 100644 index 000000000..78e6c5530 --- /dev/null +++ b/mrbgems/mruby-strftime/README.md @@ -0,0 +1,140 @@ +# mruby-strftime + +`Time#strftime` implementation for mruby. + +## Overview + +This gem provides the `strftime` method for `Time` objects in mruby, enabling +formatted time string output using standard format directives. + +## Usage + +```ruby +require 'mruby-strftime' + +t = Time.new(2023, 12, 25, 10, 30, 45) + +t.strftime("%Y-%m-%d") #=> "2023-12-25" +t.strftime("%H:%M:%S") #=> "10:30:45" +t.strftime("%Y-%m-%d %H:%M:%S") #=> "2023-12-25 10:30:45" +t.strftime("%A, %B %d, %Y") #=> "Monday, December 25, 2023" +``` + +## Format Specifiers + +The `strftime` method supports standard format directives. The exact set of +available directives depends on your system's `strftime(3)` implementation. +Common directives include: + +### Date Components + +- `%Y` - Year with century (e.g., 2023) +- `%y` - Year without century (00-99) +- `%m` - Month of the year (01-12) +- `%B` - Full month name (e.g., "December") +- `%b` - Abbreviated month name (e.g., "Dec") +- `%d` - Day of the month (01-31) +- `%j` - Day of the year (001-366) + +### Time Components + +- `%H` - Hour of the day, 24-hour clock (00-23) +- `%I` - Hour of the day, 12-hour clock (01-12) +- `%M` - Minute of the hour (00-59) +- `%S` - Second of the minute (00-60) +- `%p` - AM/PM indicator +- `%Z` - Timezone name or abbreviation + +### Weekday + +- `%A` - Full weekday name (e.g., "Monday") +- `%a` - Abbreviated weekday name (e.g., "Mon") +- `%w` - Day of the week (0-6, Sunday is 0) +- `%u` - Day of the week (1-7, Monday is 1) + +### Combined Formats + +- `%c` - Preferred date and time representation +- `%x` - Preferred date representation +- `%X` - Preferred time representation +- `%F` - ISO 8601 date format (equivalent to `%Y-%m-%d`) +- `%T` - ISO 8601 time format (equivalent to `%H:%M:%S`) + +### Special Characters + +- `%%` - Literal `%` character +- `%n` - Newline character +- `%t` - Tab character + +## Features + +### Timezone Support + +The method respects the timezone of the `Time` object: + +```ruby +t_utc = Time.utc(2023, 12, 25, 12, 0, 0) +t_local = Time.local(2023, 12, 25, 12, 0, 0) + +t_utc.strftime("%Y-%m-%d %H:%M:%S %Z") +#=> "2023-12-25 12:00:00 UTC" + +t_local.strftime("%Y-%m-%d %H:%M:%S %z") +#=> "2023-12-25 12:00:00 +0900" (example for JST) +``` + +### NUL Byte Handling + +Unlike some implementations, mruby-strftime correctly handles NUL bytes (`\0`) +embedded in format strings, preserving them in the output: + +```ruby +t = Time.gm(2023, 12, 25) +result = t.strftime("year\0%Y") +result.length #=> 9 (includes the NUL byte) +``` + +This behavior maintains Ruby's string semantics where strings are length-based +rather than null-terminated. + +## Implementation Details + +### Buffer Management + +The implementation uses dynamic buffer allocation: + +- Initial buffer size: 64 bytes +- Maximum buffer size: 4096 bytes +- Automatically grows when needed for longer formatted strings + +### Platform Compatibility + +This gem uses the system's `strftime(3)` function for formatting. The exact +behavior of format directives may vary slightly between platforms, particularly +for: + +- Locale-dependent formats (day/month names, date/time preferences) +- Timezone representations +- Platform-specific extensions + +## Dependencies + +- `mruby-time` - Required for `Time` class support + +## Installation + +Add to your `build_config.rb`: + +```ruby +conf.gem :core => 'mruby-strftime' +``` + +Or include via gembox: + +```ruby +conf.gembox 'stdlib-ext' # Includes mruby-strftime +``` + +## License + +MIT License - See mruby's main license file for details. diff --git a/mrbgems/mruby-strftime/mrbgem.rake b/mrbgems/mruby-strftime/mrbgem.rake new file mode 100644 index 000000000..89d37304b --- /dev/null +++ b/mrbgems/mruby-strftime/mrbgem.rake @@ -0,0 +1,7 @@ +MRuby::Gem::Specification.new('mruby-strftime') do |spec| + spec.license = 'MIT' + spec.author = 'mruby developers' + spec.summary = 'Time#strftime implementation' + + spec.add_dependency 'mruby-time' +end diff --git a/mrbgems/mruby-strftime/src/strftime.c b/mrbgems/mruby-strftime/src/strftime.c new file mode 100644 index 000000000..e328d30f7 --- /dev/null +++ b/mrbgems/mruby-strftime/src/strftime.c @@ -0,0 +1,140 @@ +/* +** strftime.c - Time#strftime +** +** See Copyright Notice in mruby.h +*/ + +#include +#include +#include +#include +#include +#include +#include + +#define INITIAL_BUFFER_SIZE 64 +#define MAX_BUFFER_SIZE 4096 + +/* + * call-seq: + * time.strftime(format) -> string + * + * Formats time according to the directives in the given format string. + * + * The format string may contain NUL bytes, which will be preserved in + * the output string. + * + * Common format directives: + * %Y - Year with century (e.g., 2023) + * %m - Month of the year (01-12) + * %d - Day of the month (01-31) + * %H - Hour of the day, 24-hour clock (00-23) + * %M - Minute of the hour (00-59) + * %S - Second of the minute (00-60) + * %% - Literal % character + * + * See your system's strftime(3) documentation for a complete list. + * + * t = Time.new(2023, 12, 25, 10, 30, 45) + * t.strftime("%Y-%m-%d %H:%M:%S") #=> "2023-12-25 10:30:45" + * t.strftime("%A, %B %d, %Y") #=> "Monday, December 25, 2023" + */ +static mrb_value +mrb_time_strftime(mrb_state *mrb, mrb_value self) +{ + const char *format; + mrb_int format_len; + + mrb_get_args(mrb, "s", &format, &format_len); + struct tm *tm = mrb_time_get_tm(mrb, self); + + mrb_value result = mrb_str_new(mrb, NULL, 0); + const char *fmt_ptr = format; + mrb_int remaining = format_len; + + /* Process format string in segments, handling NUL bytes */ + while (remaining > 0) { + const char *nul_pos = (const char *)memchr(fmt_ptr, '\0', (size_t)remaining); + mrb_int segment_len = nul_pos ? (nul_pos - fmt_ptr) : remaining; + + /* Process this segment (up to NUL or end of string) */ + if (segment_len > 0) { + char *segment; + size_t buf_size; + char *buf; + size_t n; + + /* Create null-terminated copy of this segment */ + segment = (char *)mrb_malloc(mrb, (size_t)segment_len + 1); + memcpy(segment, fmt_ptr, (size_t)segment_len); + segment[segment_len] = '\0'; + +#ifdef _MSC_VER + /* Check for GNU extension %-flag which crashes on MSVC */ + /* Scan for %- patterns in the format string */ + for (const char *p = segment; *p != '\0'; p++) { + if (p[0] == '%' && p[1] == '-') { + mrb_free(mrb, segment); + mrb_raisef(mrb, E_ARGUMENT_ERROR, + "strftime format flag '%-' not supported on this platform (use '%%#' on Windows)"); + } + } +#endif + + /* Allocate buffer for formatted output */ + buf_size = INITIAL_BUFFER_SIZE; + buf = (char *)mrb_malloc(mrb, buf_size); + + /* Try formatting; grow buffer if needed */ + while (1) { + n = strftime(buf, buf_size, segment, tm); + + /* + * strftime returns 0 if: + * 1. Buffer is too small (retry with larger buffer) + * 2. Format produces empty result (stop retrying) + * We distinguish by checking buffer size limit. + */ + if (n > 0 || buf_size >= MAX_BUFFER_SIZE) { + break; + } + + /* Double buffer size and retry */ + buf_size *= 2; + buf = (char *)mrb_realloc(mrb, buf, buf_size); + } + + /* Append formatted output to result */ + mrb_str_cat(mrb, result, buf, n); + + mrb_free(mrb, buf); + mrb_free(mrb, segment); + } + + /* If there was a NUL, append it to result and advance past it */ + if (nul_pos) { + mrb_str_cat(mrb, result, "\0", 1); + fmt_ptr = nul_pos + 1; + remaining -= segment_len + 1; + } + else { + break; + } + } + + return result; +} + +void +mrb_mruby_strftime_gem_init(mrb_state *mrb) +{ + struct RClass *time_class; + + time_class = mrb_class_get_id(mrb, MRB_SYM(Time)); + mrb_define_method_id(mrb, time_class, MRB_SYM(strftime), mrb_time_strftime, MRB_ARGS_REQ(1)); +} + +void +mrb_mruby_strftime_gem_final(mrb_state *mrb) +{ +} diff --git a/mrbgems/mruby-strftime/test/strftime.rb b/mrbgems/mruby-strftime/test/strftime.rb new file mode 100644 index 000000000..6dade4ed9 --- /dev/null +++ b/mrbgems/mruby-strftime/test/strftime.rb @@ -0,0 +1,152 @@ +assert('Time#strftime') do + t = Time.now + assert_true t.respond_to?(:strftime) +end + +assert('Time#strftime with basic formats') do + t = Time.gm(2023, 12, 25, 10, 30, 45) + + assert_equal '2023', t.strftime('%Y') + assert_equal '23', t.strftime('%y') + assert_equal '12', t.strftime('%m') + assert_equal '25', t.strftime('%d') + assert_equal '10', t.strftime('%H') + assert_equal '30', t.strftime('%M') + assert_equal '45', t.strftime('%S') +end + +assert('Time#strftime with combined formats') do + t = Time.gm(2023, 12, 25, 10, 30, 45) + + assert_equal '2023-12-25', t.strftime('%Y-%m-%d') + assert_equal '10:30:45', t.strftime('%H:%M:%S') + assert_equal '2023-12-25 10:30:45', t.strftime('%Y-%m-%d %H:%M:%S') +end + +assert('Time#strftime with weekday formats') do + # 2023-12-25 is Monday + t = Time.gm(2023, 12, 25) + + result = t.strftime('%A') + assert_true result.include?('Mon') || result == 'Monday' + + result = t.strftime('%a') + assert_true result.length >= 2 + + assert_equal '1', t.strftime('%w') # Monday is day 1 +end + +assert('Time#strftime with month formats') do + t = Time.gm(2023, 12, 25) + + result = t.strftime('%B') + assert_true result.include?('Dec') || result == 'December' + + result = t.strftime('%b') + assert_true result.length >= 2 +end + +assert('Time#strftime with literal percent') do + t = Time.gm(2023, 12, 25) + + assert_equal '%', t.strftime('%%') + assert_equal '100%', t.strftime('100%%') + assert_equal '2023%12', t.strftime('%Y%%%m') +end + +assert('Time#strftime with empty format') do + t = Time.now + + assert_equal '', t.strftime('') +end + +assert('Time#strftime with no format specifiers') do + t = Time.now + + assert_equal 'hello', t.strftime('hello') + assert_equal 'test123', t.strftime('test123') +end + +assert('Time#strftime with NUL byte') do + t = Time.gm(2023, 12, 25) + + result = t.strftime("foo\0bar") + assert_equal 7, result.length + assert_equal 'f', result[0] + assert_equal 'o', result[1] + assert_equal 'o', result[2] + assert_equal "\0", result[3] + assert_equal 'b', result[4] + assert_equal 'a', result[5] + assert_equal 'r', result[6] +end + +assert('Time#strftime with NUL and format specifiers') do + t = Time.gm(2023, 12, 25) + + result = t.strftime("year\0%Y") + assert_true result.length >= 9 # "year\0" (5) + "2023" (4) + assert_true result.include?("\0") + # Check last 4 characters are "2023" + assert_equal '2023', result[-4, 4] +end + +assert('Time#strftime with multiple NULs') do + t = Time.now + + result = t.strftime("\0\0") + assert_equal 2, result.length + assert_equal "\0\0", result +end + +assert('Time#strftime with NUL at beginning') do + t = Time.gm(2023, 1, 1) + + result = t.strftime("\0%Y") + assert_equal 5, result.length + assert_equal "\0", result[0] + assert_equal '2023', result[1, 4] +end + +assert('Time#strftime with NUL at end') do + t = Time.gm(2023, 1, 1) + + result = t.strftime("%Y\0") + assert_equal 5, result.length + assert_equal '2023', result[0, 4] + assert_equal "\0", result[4] +end + +assert('Time#strftime preserves timezone') do + t_utc = Time.utc(2023, 1, 1, 12, 0, 0) + t_local = Time.local(2023, 1, 1, 12, 0, 0) + + # Both should format their time correctly + assert_equal '12', t_utc.strftime('%H') + assert_equal '12', t_local.strftime('%H') +end + +assert('Time#strftime with various time components') do + t = Time.gm(2023, 6, 15, 14, 23, 7) + + # Use portable format specifiers that work on all platforms + assert_equal '06', t.strftime('%m') # Month with leading zero (portable) + assert_equal '15', t.strftime('%d') + assert_equal '14', t.strftime('%H') + assert_equal '23', t.strftime('%M') + assert_equal '07', t.strftime('%S') +end + +assert('Time#strftime argument type error') do + t = Time.now + + assert_raise(TypeError) { t.strftime(123) } + assert_raise(TypeError) { t.strftime(nil) } +end + +assert('Time#strftime argument count error') do + t = Time.now + + assert_raise(ArgumentError) { t.strftime } + assert_raise(ArgumentError) { t.strftime('%Y', '%m') } +end diff --git a/mrbgems/mruby-string-ext/mrblib/string.rb b/mrbgems/mruby-string-ext/mrblib/string.rb index b75944825..ff812f3dd 100644 --- a/mrbgems/mruby-string-ext/mrblib/string.rb +++ b/mrbgems/mruby-string-ext/mrblib/string.rb @@ -2,7 +2,7 @@ class String ## # Call the given block for each character of - # +self+. + # `self`. def each_char(&block) return to_enum :each_char unless block pos = 0 @@ -56,7 +56,7 @@ class String # a = "abc\ndef" # a.lines #=> ["abc\n", "def"] # - # If a block is given, it works the same as each_line. + # If a block is given, it works the same as `each_line`. def lines(&blk) lines = self.__lines if blk @@ -74,9 +74,9 @@ class String # str.upto(other_str, exclusive=false) {|s| block } -> str # str.upto(other_str, exclusive=false) -> an_enumerator # - # Iterates through successive values, starting at str and - # ending at other_str inclusive, passing each value in turn to - # the block. The String#succ method is used to generate + # Iterates through successive values, starting at *str* and + # ending at *other_str* inclusive, passing each value in turn to + # the block. The `String#succ` method is used to generate # each value. If optional second argument exclusive is omitted or is false, # the last value will be included; otherwise it will be excluded. # @@ -92,7 +92,7 @@ class String # a8 a9 b0 b1 b2 b3 b4 b5 b6 # a8 a9 b0 b1 b2 b3 b4 b5 b6 # - # If str and other_str contains only ascii numeric characters, + # If *str* and *other_str* contains only ascii numeric characters, # both are recognized as decimal numbers. In addition, the width of # string (e.g. leading zeros) is handled appropriately. # diff --git a/mrbgems/mruby-string-ext/src/string.c b/mrbgems/mruby-string-ext/src/string.c index 00d04c560..4e9c95d21 100644 --- a/mrbgems/mruby-string-ext/src/string.c +++ b/mrbgems/mruby-string-ext/src/string.c @@ -89,8 +89,8 @@ int_chr_utf8(mrb_state *mrb, mrb_value num) * call-seq: * str.swapcase! -> str or nil * - * Equivalent to String#swapcase, but modifies the receiver in - * place, returning str, or nil if no changes were made. + * Equivalent to `String#swapcase`, but modifies the receiver in + * place, returning *str*, or `nil` if no changes were made. * Note: case conversion is effective only in ASCII region. */ static mrb_value @@ -122,7 +122,7 @@ str_swapcase_bang(mrb_state *mrb, mrb_value str) * call-seq: * str.swapcase -> new_str * - * Returns a copy of str with uppercase alphabetic characters converted + * Returns a copy of *str* with uppercase alphabetic characters converted * to lowercase and lowercase characters converted to uppercase. * Note: case conversion is effective only in ASCII region. * @@ -184,7 +184,7 @@ str_concat0(mrb_state *mrb, mrb_value self, mrb_bool binary) * s.concat('bar', 'baz') # => "foobarbaz" * s # => "foobarbaz" * - * For each given object +object+ that is an \Integer, + * For each given object `object` that is an \Integer, * the value is considered a codepoint and converted to a character before concatenation: * * s = 'foo' @@ -215,7 +215,7 @@ str_append_as_bytes(mrb_state *mrb, mrb_value self) * call-seq: * str.start_with?([prefixes]+) -> true or false * - * Returns true if +str+ starts with one of the +prefixes+ given. + * Returns true if `str` starts with one of the `prefixes` given. * * "hello".start_with?("hell") #=> true * @@ -251,7 +251,7 @@ str_start_with(mrb_state *mrb, mrb_value self) * call-seq: * str.end_with?([suffixes]+) -> true or false * - * Returns true if +str+ ends with one of the +suffixes+ given. + * Returns true if `str` ends with one of the `suffixes` given. */ static mrb_value str_end_with(mrb_state *mrb, mrb_value self) @@ -521,14 +521,17 @@ str_tr(mrb_state *mrb, mrb_value str, mrb_value p1, mrb_value p2, mrb_bool squee char *s = RSTRING_PTR(str); mrb_int len = RSTRING_LEN(str); + /* Hoist pointer retrieval outside loop to avoid repeated conditionals */ + const char *p1_ptr = RSTRING_PTR(p1); + const char *p2_ptr = RSTRING_PTR(p2); mrb_int i, j; for (i=j=0; ij) s[j] = s[i]; if (n >= 0) { flag_changed = TRUE; - mrb_int c = tr_get_character(&rep, RSTRING_PTR(p2), n); + mrb_int c = tr_get_character(&rep, p2_ptr, n); if (c < 0 || (squeeze && c == lastch)) { j--; @@ -873,9 +876,9 @@ str_chr(mrb_state *mrb, mrb_value self) * call-seq: * int.chr([encoding]) -> string * - * Returns a string containing the character represented by the +int+'s value - * according to +encoding+. +"ASCII-8BIT"+ (+"BINARY"+) and +"UTF-8"+ (only - * with +MRB_UTF8_STRING+) can be specified as +encoding+ (default is + * Returns a string containing the character represented by the `int`'s value + * according to `encoding`. +"ASCII-8BIT"+ (+"BINARY"+) and +"UTF-8"+ (only + * with `MRB_UTF8_STRING`) can be specified as `encoding` (default is * +"ASCII-8BIT"+). * * 65.chr #=> "A" @@ -1099,12 +1102,20 @@ str_codepoints(mrb_state *mrb, mrb_value self) } #endif +static mrb_bool +str_prefix_p(mrb_state *mrb, mrb_value str, const char *prefix_ptr, mrb_int prefix_len) +{ + mrb_int str_len = RSTRING_LEN(str); + if (prefix_len > str_len) return FALSE; + return memcmp(RSTRING_PTR(str), prefix_ptr, prefix_len) == 0; +} + /* * call-seq: * str.delete_prefix!(prefix) -> self or nil * - * Deletes leading prefix from str, returning - * nil if no change was made. + * Deletes leading `prefix` from *str*, returning + * `nil` if no change was made. * * "hello".delete_prefix!("hel") #=> "lo" * "hello".delete_prefix!("llo") #=> nil @@ -1114,13 +1125,13 @@ str_del_prefix_bang(mrb_state *mrb, mrb_value self) { mrb_int plen; const char *ptr; - struct RString *str = RSTRING(self); mrb_get_args(mrb, "s", &ptr, &plen); + struct RString *str = RSTRING(self); mrb_int slen = RSTR_LEN(str); if (plen > slen) return mrb_nil_value(); char *s = RSTR_PTR(str); - if (memcmp(s, ptr, plen) != 0) return mrb_nil_value(); + if (!str_prefix_p(mrb, self, ptr, plen)) return mrb_nil_value(); if (!mrb_frozen_p(str) && (RSTR_SHARED_P(str) || RSTR_FSHARED_P(str))) { str->as.heap.ptr += plen; } @@ -1137,7 +1148,7 @@ str_del_prefix_bang(mrb_state *mrb, mrb_value self) * call-seq: * str.delete_prefix(prefix) -> new_str * - * Returns a copy of str with leading prefix deleted. + * Returns a copy of *str* with leading `prefix` deleted. * * "hello".delete_prefix("hel") #=> "lo" * "hello".delete_prefix("llo") #=> "hello" @@ -1151,17 +1162,25 @@ str_del_prefix(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "s", &ptr, &plen); mrb_int slen = RSTRING_LEN(self); if (plen > slen) return mrb_str_dup(mrb, self); - if (memcmp(RSTRING_PTR(self), ptr, plen) != 0) + if (!str_prefix_p(mrb, self, ptr, plen)) return mrb_str_dup(mrb, self); return mrb_str_substr(mrb, self, plen, slen-plen); } +static mrb_bool +str_suffix_p(mrb_state *mrb, mrb_value str, const char *suffix_ptr, mrb_int suffix_len) +{ + mrb_int str_len = RSTRING_LEN(str); + if (suffix_len > str_len) return FALSE; + return memcmp(RSTRING_PTR(str) + (str_len - suffix_len), suffix_ptr, suffix_len) == 0; +} + /* * call-seq: * str.delete_suffix!(suffix) -> self or nil * - * Deletes trailing suffix from str, returning - * nil if no change was made. + * Deletes trailing `suffix` from *str*, returning + * `nil` if no change was made. * * "hello".delete_suffix!("llo") #=> "he" * "hello".delete_suffix!("hel") #=> nil @@ -1171,19 +1190,13 @@ str_del_suffix_bang(mrb_state *mrb, mrb_value self) { mrb_int plen; const char *ptr; - struct RString *str = RSTRING(self); mrb_get_args(mrb, "s", &ptr, &plen); + struct RString *str = RSTRING(self); + mrb_check_frozen(mrb, str); mrb_int slen = RSTR_LEN(str); if (plen > slen) return mrb_nil_value(); - char *s = RSTR_PTR(str); - if (memcmp(s+slen-plen, ptr, plen) != 0) return mrb_nil_value(); - if (!mrb_frozen_p(str) && (RSTR_SHARED_P(str) || RSTR_FSHARED_P(str))) { - /* no need to modify string */ - } - else { - mrb_str_modify(mrb, str); - } + if (!str_suffix_p(mrb, self, ptr, plen)) return mrb_nil_value(); RSTR_SET_LEN(str, slen-plen); return self; } @@ -1192,7 +1205,7 @@ str_del_suffix_bang(mrb_state *mrb, mrb_value self) * call-seq: * str.delete_suffix(suffix) -> new_str * - * Returns a copy of str with leading suffix deleted. + * Returns a copy of *str* with leading `suffix` deleted. * * "hello".delete_suffix("hel") #=> "lo" * "hello".delete_suffix("llo") #=> "hello" @@ -1206,7 +1219,7 @@ str_del_suffix(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "s", &ptr, &plen); mrb_int slen = RSTRING_LEN(self); if (plen > slen) return mrb_str_dup(mrb, self); - if (memcmp(RSTRING_PTR(self)+slen-plen, ptr, plen) != 0) + if (!str_suffix_p(mrb, self, ptr, plen)) return mrb_str_dup(mrb, self); return mrb_str_substr(mrb, self, 0, slen-plen); } @@ -1217,7 +1230,7 @@ str_del_suffix(mrb_state *mrb, mrb_value self) * call-seq: * str.casecmp(other_str) -> -1, 0, +1 or nil * - * Case-insensitive version of String#<=>. + * Case-insensitive version of `String#<=>`. * * "abcdef".casecmp("abcde") #=> 1 * "aBcDeF".casecmp("abcdef") #=> 0 @@ -1272,20 +1285,18 @@ str_casecmp_p(mrb_state *mrb, mrb_value self) static mrb_value str_lines(mrb_state *mrb, mrb_value self) { - mrb_value result; - mrb_int len; char *b = RSTRING_PTR(self); char *p = b, *t; char *e = b + RSTRING_LEN(self); mrb->c->ci->mid = 0; - result = mrb_ary_new(mrb); + mrb_value result = mrb_ary_new(mrb); int ai = mrb_gc_arena_save(mrb); while (p < e) { t = p; while (p < e && *p != '\n') p++; if (*p == '\n') p++; - len = (mrb_int) (p - t); + mrb_int len = (mrb_int) (p - t); mrb_ary_push(mrb, result, mrb_str_new(mrb, t, len)); mrb_gc_arena_restore(mrb, ai); } @@ -1296,9 +1307,9 @@ str_lines(mrb_state *mrb, mrb_value self) * call-seq: * +string -> new_string or self * - * Returns +self+ if +self+ is not frozen. + * Returns `self` if `self` is not frozen. * - * Otherwise returns self.dup, which is not frozen. + * Otherwise returns `self.dup`, which is not frozen. */ static mrb_value str_uplus(mrb_state *mrb, mrb_value str) @@ -1503,9 +1514,12 @@ str_lstrip_bang(mrb_state *mrb, mrb_value self) if (start < len) { memmove(ptr, ptr + start, len - start); RSTR_SET_LEN(s, len - start); - } else { + ptr[len - start] = '\0'; + } + else { /* All whitespace - make empty */ RSTR_SET_LEN(s, 0); + ptr[0] = '\0'; } return self; @@ -1543,6 +1557,7 @@ str_rstrip_bang(mrb_state *mrb, mrb_value self) /* Truncate string */ RSTR_SET_LEN(s, end); + ptr[end] = '\0'; return self; } @@ -1597,6 +1612,7 @@ str_strip_bang(mrb_state *mrb, mrb_value self) /* Set new length */ RSTR_SET_LEN(s, end - start); + ptr[end - start] = '\0'; return self; } @@ -1628,14 +1644,13 @@ str_chars_ary(mrb_state *mrb, mrb_value self) struct RString *s = mrb_str_ptr(self); const unsigned char *p = (unsigned char*)RSTR_PTR(s); const unsigned char *e = p + RSTR_LEN(s); - mrb_value result; /* Estimate character count for array pre-allocation */ mrb_int estimated_chars = RSTR_LEN(s); if (!RSTR_SINGLE_BYTE_P(s) && !RSTR_BINARY_P(s)) { estimated_chars = estimated_chars / 2; /* rough estimate for UTF-8 */ } - result = mrb_ary_new_capa(mrb, estimated_chars); + mrb_value result = mrb_ary_new_capa(mrb, estimated_chars); if (RSTR_SINGLE_BYTE_P(s) || RSTR_BINARY_P(s)) { /* ASCII/Binary: each byte is a character */ @@ -1644,7 +1659,8 @@ str_chars_ary(mrb_state *mrb, mrb_value self) mrb_ary_push(mrb, result, char_str); p++; } - } else { + } + else { #ifdef MRB_UTF8_STRING /* UTF-8: handle multi-byte characters */ while (p < e) { @@ -1652,7 +1668,8 @@ str_chars_ary(mrb_state *mrb, mrb_value self) if (char_len == 0 || char_len > 4 || p + char_len > e) { /* Invalid UTF-8, treat as single byte */ char_len = 1; - } else { + } + else { /* Validate UTF-8 sequence */ mrb_bool valid = TRUE; if (char_len > 1) { @@ -1697,7 +1714,6 @@ str_ljust_core(mrb_state *mrb, mrb_value self) { mrb_int width; mrb_value padstr = mrb_str_new_lit(mrb, " "); - mrb_int char_len, pad_char_len, padsize; mrb_get_args(mrb, "i|S", &width, &padstr); @@ -1705,13 +1721,13 @@ str_ljust_core(mrb_state *mrb, mrb_value self) mrb_raise(mrb, E_ARGUMENT_ERROR, "zero width padding"); } - char_len = str_char_count(self); + mrb_int char_len = str_char_count(self); if (width <= char_len) { return mrb_str_dup(mrb, self); } - padsize = width - char_len; - pad_char_len = str_char_count(padstr); + mrb_int padsize = width - char_len; + mrb_int pad_char_len = str_char_count(padstr); if (pad_char_len == 0) { mrb_raise(mrb, E_ARGUMENT_ERROR, "zero width padding"); } @@ -1723,7 +1739,8 @@ str_ljust_core(mrb_state *mrb, mrb_value self) if (chars_needed >= pad_char_len) { mrb_str_cat_str(mrb, padding, padstr); chars_needed -= pad_char_len; - } else { + } + else { /* Need partial padding - use substr to get exact characters */ mrb_value partial = mrb_str_substr(mrb, padstr, 0, chars_needed); mrb_str_cat_str(mrb, padding, partial); @@ -1747,7 +1764,6 @@ str_rjust_core(mrb_state *mrb, mrb_value self) { mrb_int width; mrb_value padstr = mrb_str_new_lit(mrb, " "); - mrb_int char_len, pad_char_len, padsize; mrb_get_args(mrb, "i|S", &width, &padstr); @@ -1755,13 +1771,13 @@ str_rjust_core(mrb_state *mrb, mrb_value self) mrb_raise(mrb, E_ARGUMENT_ERROR, "zero width padding"); } - char_len = str_char_count(self); + mrb_int char_len = str_char_count(self); if (width <= char_len) { return mrb_str_dup(mrb, self); } - padsize = width - char_len; - pad_char_len = str_char_count(padstr); + mrb_int padsize = width - char_len; + mrb_int pad_char_len = str_char_count(padstr); if (pad_char_len == 0) { mrb_raise(mrb, E_ARGUMENT_ERROR, "zero width padding"); } @@ -1773,7 +1789,8 @@ str_rjust_core(mrb_state *mrb, mrb_value self) if (chars_needed >= pad_char_len) { mrb_str_cat_str(mrb, padding, padstr); chars_needed -= pad_char_len; - } else { + } + else { /* Need partial padding - use substr to get exact characters */ mrb_value partial = mrb_str_substr(mrb, padstr, 0, chars_needed); mrb_str_cat_str(mrb, padding, partial); @@ -1797,7 +1814,6 @@ str_center_core(mrb_state *mrb, mrb_value self) { mrb_int width; mrb_value padstr = mrb_str_new_lit(mrb, " "); - mrb_int char_len, pad_char_len, total_pad, left_pad, right_pad; mrb_get_args(mrb, "i|S", &width, &padstr); @@ -1805,16 +1821,16 @@ str_center_core(mrb_state *mrb, mrb_value self) mrb_raise(mrb, E_ARGUMENT_ERROR, "zero width padding"); } - char_len = str_char_count(self); + mrb_int char_len = str_char_count(self); if (width <= char_len) { return mrb_str_dup(mrb, self); } - total_pad = width - char_len; - left_pad = total_pad / 2; - right_pad = total_pad - left_pad; + mrb_int total_pad = width - char_len; + mrb_int left_pad = total_pad / 2; + mrb_int right_pad = total_pad - left_pad; - pad_char_len = str_char_count(padstr); + mrb_int pad_char_len = str_char_count(padstr); if (pad_char_len == 0) { mrb_raise(mrb, E_ARGUMENT_ERROR, "zero width padding"); } @@ -1826,7 +1842,8 @@ str_center_core(mrb_state *mrb, mrb_value self) if (chars_needed >= pad_char_len) { mrb_str_cat_str(mrb, left_padding, padstr); chars_needed -= pad_char_len; - } else { + } + else { mrb_value partial = mrb_str_substr(mrb, padstr, 0, chars_needed); mrb_str_cat_str(mrb, left_padding, partial); chars_needed = 0; @@ -1840,7 +1857,8 @@ str_center_core(mrb_state *mrb, mrb_value self) if (chars_needed >= pad_char_len) { mrb_str_cat_str(mrb, right_padding, padstr); chars_needed -= pad_char_len; - } else { + } + else { mrb_value partial = mrb_str_substr(mrb, padstr, 0, chars_needed); mrb_str_cat_str(mrb, right_padding, partial); chars_needed = 0; @@ -1921,13 +1939,12 @@ mrb_str_slice_bang(mrb_state *mrb, mrb_value self) mrb_int argc = mrb_get_args(mrb, "o|o", &arg1, &arg2); struct RString *str = mrb_str_ptr(self); - mrb_int str_len; const char *ptr = RSTRING_PTR(self); #ifdef MRB_UTF8_STRING - str_len = str_char_count(self); + mrb_int str_len = str_char_count(self); #else - str_len = RSTRING_LEN(self); + mrb_int str_len = RSTRING_LEN(self); #endif mrb_int beg, len; @@ -2014,11 +2031,11 @@ str_clear(mrb_state *mrb, mrb_value self) * call-seq: * str.partition(sep) -> [head, sep, tail] * - * Searches for the first occurrence of +sep+ in +str+. If +sep+ is found, - * returns a 3-element array containing the part of +str+ before +sep+, - * +sep+ itself, and the part of +str+ after +sep+. + * Searches for the first occurrence of `sep` in `str`. If `sep` is found, + * returns a 3-element array containing the part of `str` before `sep`, + * `sep` itself, and the part of `str` after `sep`. * - * If +sep+ is not found, returns a 3-element array containing +str+, + * If `sep` is not found, returns a 3-element array containing `str`, * an empty string, and an empty string. * * "hello world".partition(" ") #=> ["hello", " ", "world"] @@ -2046,8 +2063,7 @@ str_partition(mrb_state *mrb, mrb_value self) } const char *found_ptr = NULL; - mrb_int i; - for (i = 0; i <= self_len - sep_len; ++i) { + for (mrb_int i = 0; i <= self_len - sep_len; ++i) { if (memcmp(self_ptr + i, sep_ptr, sep_len) == 0) { found_ptr = self_ptr + i; break; @@ -2075,12 +2091,12 @@ str_partition(mrb_state *mrb, mrb_value self) * call-seq: * str.rpartition(sep) -> [head, sep, tail] * - * Searches for the last occurrence of +sep+ in +str+. If +sep+ is found, - * returns a 3-element array containing the part of +str+ before +sep+, - * +sep+ itself, and the part of +str+ after +sep+. + * Searches for the last occurrence of `sep` in `str`. If `sep` is found, + * returns a 3-element array containing the part of `str` before `sep`, + * `sep` itself, and the part of `str` after `sep`. * - * If +sep+ is not found, returns a 3-element array containing an empty string, - * an empty string, and +str+. + * If `sep` is not found, returns a 3-element array containing an empty string, + * an empty string, and `str`. * * "hello world".rpartition(" ") #=> ["hello", " ", "world"] * "hello world".rpartition("o") #=> ["hello w", "o", "rld"] @@ -2107,8 +2123,7 @@ str_rpartition(mrb_state *mrb, mrb_value self) } const char *found_ptr = NULL; - mrb_int i; - for (i = self_len - sep_len; i >= 0; --i) { + for (mrb_int i = self_len - sep_len; i >= 0; --i) { if (memcmp(self_ptr + i, sep_ptr, sep_len) == 0) { found_ptr = self_ptr + i; break; @@ -2136,11 +2151,11 @@ str_rpartition(mrb_state *mrb, mrb_value self) * call-seq: * str.insert(index, other_str) -> str * - * Inserts other_str before the character at the given - * index, modifying str. Negative indices count from the + * Inserts *other_str* before the character at the given + * *index*, modifying *str*. Negative indices count from the * end of the string, and insert after the given character. - * The intent is insert aString so that it starts at the given - * index. + * The intent is insert *aString* so that it starts at the given + * *index*. * * "abcd".insert(0, 'X') #=> "Xabcd" * "abcd".insert(3, 'X') #=> "abcXd" @@ -2156,9 +2171,8 @@ str_insert(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "iS", &idx, &str_to_insert); struct RString *s = mrb_str_ptr(self); - mrb_int self_len = RSTRING_LEN(self); + mrb_int self_len = RSTR_LEN(s); mrb_int insert_len = RSTRING_LEN(str_to_insert); - const char *insert_ptr = RSTRING_PTR(str_to_insert); mrb_check_frozen(mrb, s); @@ -2175,7 +2189,7 @@ str_insert(mrb_state *mrb, mrb_value self) char *p = RSTRING_PTR(self); memmove(p + idx + insert_len, p + idx, self_len - idx); - memcpy(p + idx, insert_ptr, insert_len); + memcpy(p + idx, RSTRING_PTR(str_to_insert), insert_len); return self; } @@ -2184,7 +2198,7 @@ str_insert(mrb_state *mrb, mrb_value self) * call-seq: * str.prepend(*other_str) -> str * - * Prepend---Prepend the given strings to str. + * Prepend---Prepend the given strings to *str*. * * a = "world" * a.prepend("hello ") #=> "hello world" diff --git a/mrbgems/mruby-struct/README.md b/mrbgems/mruby-struct/README.md index 29452daad..366610ae5 100644 --- a/mrbgems/mruby-struct/README.md +++ b/mrbgems/mruby-struct/README.md @@ -5,7 +5,8 @@ This mrbgem provides the `Struct` class, a convenient way to bundle a number of ## Functionality - Define new Struct classes with a specific set of members. -- Create instances of these Structs. +- Create instances of these Structs with positional or keyword arguments. +- Support for keyword initialization mode (`keyword_init` option). - Access and assign to struct members using accessor methods or by index/symbol. - Iterate over members and values. - Convert structs to Arrays or Hashes. @@ -37,6 +38,56 @@ puts joe.name # Output: Joe Smith puts joe.address # Output: 123 Maple, Anytown NC ``` +## Keyword Initialization + +As of mruby 3.4, Struct supports keyword initialization through the `keyword_init` option: + +```ruby +# Create a Struct with keyword initialization enabled +Person = Struct.new(:name, :age, keyword_init: true) + +# Must use keyword arguments when keyword_init: true +person = Person.new(name: "Alice", age: 30) +puts person.name # Output: Alice +puts person.age # Output: 30 + +# Can create with partial keywords (missing values are nil) +person2 = Person.new(name: "Bob") +puts person2.name # Output: Bob +puts person2.age # Output: nil + +# Empty initialization is allowed +person3 = Person.new +puts person3.name # Output: nil + +# Positional arguments will raise an error when keyword_init: true +# Person.new("Charlie", 25) # ArgumentError: wrong arguments, expected keyword arguments +``` + +### Keyword Initialization Modes + +The `keyword_init` option supports three modes: + +1. **`keyword_init: true`** - Only keyword arguments are accepted +2. **`keyword_init: false`** - Only positional arguments are accepted (hashes are treated as values) +3. **`keyword_init: nil` (default)** - Flexible mode: accepts both positional arguments and keyword arguments (single hash) + +```ruby +# Flexible mode (default behavior) +FlexPoint = Struct.new(:x, :y) +p1 = FlexPoint.new(1, 2) # Positional arguments +p2 = FlexPoint.new(x: 3, y: 4) # Keyword arguments (single hash) + +# Keyword-only mode +KeywordPoint = Struct.new(:x, :y, keyword_init: true) +p3 = KeywordPoint.new(x: 5, y: 6) # Only keyword arguments allowed + +# Positional-only mode +PositionalPoint = Struct.new(:x, :y, keyword_init: false) +p4 = PositionalPoint.new(7, 8) # Only positional arguments +p5 = PositionalPoint.new({x: 9, y: 10}) # Hash is treated as first value +``` + ## Available Methods Instances of classes created with `Struct.new` have several useful methods, including: diff --git a/mrbgems/mruby-struct/mrblib/struct.rb b/mrbgems/mruby-struct/mrblib/struct.rb index 20d9bde86..7599be970 100644 --- a/mrbgems/mruby-struct/mrblib/struct.rb +++ b/mrbgems/mruby-struct/mrblib/struct.rb @@ -6,7 +6,7 @@ class Struct include Enumerable ## - # Calls the given block for each element of +self+ + # Calls the given block for each element of `self` # and pass the respective element. # # ISO 15.2.18.4.4 @@ -18,7 +18,7 @@ class Struct end ## - # Calls the given block for each element of +self+ + # Calls the given block for each element of `self` # and pass the name and value of the respective # element. # @@ -31,7 +31,7 @@ class Struct end ## - # Calls the given block for each element of +self+ + # Calls the given block for each element of `self` # and returns an array with all elements of which # block is not false. # @@ -54,9 +54,9 @@ class Struct # call-seq: # hsh.dig(key,...) -> object # - # Extracts the nested value specified by the sequence of key - # objects by calling +dig+ at each step, returning +nil+ if any - # intermediate step is +nil+. + # Extracts the nested value specified by the sequence of *key* + # objects by calling `dig` at each step, returning `nil` if any + # intermediate step is `nil`. # def dig(idx,*args) n = self[idx] diff --git a/mrbgems/mruby-struct/src/struct.c b/mrbgems/mruby-struct/src/struct.c index 3f7c90799..c4af92c2f 100644 --- a/mrbgems/mruby-struct/src/struct.c +++ b/mrbgems/mruby-struct/src/struct.c @@ -220,25 +220,32 @@ make_struct(mrb_state *mrb, mrb_value name, mrb_value members, struct RClass *kl /* 15.2.18.3.1 */ /* * call-seq: - * Struct.new( [aString] [, aSym]+> ) -> StructClass - * StructClass.new(arg, ...) -> obj - * StructClass[arg, ...] -> obj + * Struct.new([aString] [, aSym]+, keyword_init: false) -> StructClass + * StructClass.new(arg, ...) -> obj + * StructClass[arg, ...] -> obj * - * Creates a new class, named by aString, containing accessor - * methods for the given symbols. If the name aString is + * Creates a new class, named by *aString*, containing accessor + * methods for the given symbols. If the name *aString* is * omitted, an anonymous structure class will be created. Otherwise, * the name of this struct will appear as a constant in class - * Struct, so it must be unique for all - * Structs in the system and should start with a capital + * `Struct`, so it must be unique for all + * `Struct`s in the system and should start with a capital * letter. Assigning a structure class to a constant effectively gives * the class the name of the constant. * - * Struct::new returns a new Class object, + * `Struct::new` returns a new `Class` object, * which can then be used to create specific instances of the new * structure. The number of actual parameters must be * less than or equal to the number of attributes defined for this - * class; unset parameters default to nil. Passing too many - * parameters will raise an ArgumentError. + * class; unset parameters default to `nil`. Passing too many + * parameters will raise an `ArgumentError`. + * + * If `keyword_init` is true, the struct will accept keyword + * arguments for initialization instead of positional arguments: + * + * Person = Struct.new(:name, :age, keyword_init: true) + * Person.new(name: "Alice", age: 30) + * #=> # * * The remaining methods listed in this section (class and instance) * are defined for this generated class. @@ -250,6 +257,11 @@ make_struct(mrb_state *mrb, mrb_value name, mrb_value members, struct RClass *kl * # Create a structure named by its constant * Customer = Struct.new(:name, :address) #=> Customer * Customer.new("Dave", "123 Main") #=> # + * + * # Create a structure with keyword initialization + * User = Struct.new(:id, :email, keyword_init: true) + * User.new(id: 1, email: "user@example.com") + * #=> # */ static mrb_value mrb_struct_s_def(mrb_state *mrb, mrb_value klass) @@ -268,10 +280,10 @@ mrb_struct_s_def(mrb_state *mrb, mrb_value klass) /* Check for keyword_init option in arguments */ if (argc > 0 && mrb_hash_p(argv[argc-1])) { mrb_value options = argv[argc-1]; - mrb_sym keyword_init_sym = mrb_intern_lit(mrb, "keyword_init"); + mrb_value keyword_init_sym = mrb_symbol_value(MRB_SYM(keyword_init)); - if (mrb_hash_key_p(mrb, options, mrb_symbol_value(keyword_init_sym))) { - keyword_init_val = mrb_hash_get(mrb, options, mrb_symbol_value(keyword_init_sym)); + if (mrb_hash_key_p(mrb, options, keyword_init_sym)) { + keyword_init_val = mrb_hash_get(mrb, options, keyword_init_sym); argc--; /* Don't treat the options hash as a member name */ } } @@ -304,7 +316,7 @@ mrb_struct_s_def(mrb_state *mrb, mrb_value klass) mrb_value st = make_struct(mrb, name, members, mrb_class_ptr(klass)); /* Set keyword_init value on the struct class */ - mrb_iv_set(mrb, st, mrb_intern_lit(mrb, "@__keyword_init__"), keyword_init_val); + mrb_iv_set(mrb, st, MRB_IVSYM(__keyword_init__), keyword_init_val); if (!mrb_nil_p(b)) { mrb_yield_with_class(mrb, b, 1, &st, st, mrb_class_ptr(st)); @@ -316,7 +328,7 @@ mrb_struct_s_def(mrb_state *mrb, mrb_value klass) /* */ static mrb_value -mrb_struct_initialize_withArg(mrb_state *mrb, mrb_int argc, const mrb_value *argv, mrb_value self) +mrb_struct_init_with_args(mrb_state *mrb, mrb_int argc, const mrb_value *argv, mrb_value self) { mrb_int n = num_members(mrb, self); @@ -334,7 +346,7 @@ mrb_struct_initialize_withArg(mrb_state *mrb, mrb_int argc, const mrb_value *arg } static mrb_value -mrb_struct_initialize_withKw(mrb_state *mrb, mrb_value hash, mrb_value self) +mrb_struct_init_with_keywords(mrb_state *mrb, mrb_value hash, mrb_value self) { mrb_value members = struct_members(mrb, self); mrb_int member_count = num_members(mrb, self); @@ -369,7 +381,7 @@ mrb_struct_initialize_withKw(mrb_state *mrb, mrb_value hash, mrb_value self) /* If there are any invalid keys, raise an error with all of them */ if (RARRAY_LEN(invalid_keys) > 0) { - mrb_value keys_str = mrb_funcall(mrb, invalid_keys, "join", 1, mrb_str_new_lit(mrb, ", ")); + mrb_value keys_str = mrb_ary_join(mrb, invalid_keys, mrb_str_new_lit(mrb, ", ")); mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown keywords: %S", keys_str); } @@ -381,29 +393,28 @@ mrb_struct_initialize(mrb_state *mrb, mrb_value self) { const mrb_value *argv; mrb_int argc; - mrb_value klass, keyword_init; mrb_get_args(mrb, "*", &argv, &argc); - klass = mrb_obj_value(mrb_obj_class(mrb, self)); - keyword_init = mrb_iv_get(mrb, klass, mrb_intern_lit(mrb, "@__keyword_init__")); + mrb_value klass = mrb_obj_value(mrb_obj_class(mrb, self)); + mrb_value keyword_init = mrb_iv_get(mrb, klass, MRB_IVSYM(__keyword_init__)); if (mrb_test(keyword_init)) { /* keyword_init: true or other truthy value */ if (argc > 1 || (argc == 1 && !mrb_hash_p(argv[0]))) { mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong arguments, expected keyword arguments"); } mrb_value hash = (argc == 1) ? argv[0] : mrb_hash_new(mrb); - return mrb_struct_initialize_withKw(mrb, hash, self); + return mrb_struct_init_with_keywords(mrb, hash, self); } else if (mrb_equal(mrb, keyword_init, mrb_false_value())) { /* keyword_init: false */ - return mrb_struct_initialize_withArg(mrb, argc, argv, self); + return mrb_struct_init_with_args(mrb, argc, argv, self); } else { /* keyword_init: nil (default) */ if (argc == 1 && mrb_hash_p(argv[0])) { - return mrb_struct_initialize_withKw(mrb, argv[0], self); + return mrb_struct_init_with_keywords(mrb, argv[0], self); } else { - return mrb_struct_initialize_withArg(mrb, argc, argv, self); + return mrb_struct_init_with_args(mrb, argc, argv, self); } } } @@ -474,9 +485,9 @@ struct_aref_int(mrb_state *mrb, mrb_value s, mrb_int i) * struct[fixnum] -> anObject * * Attribute Reference---Returns the value of the instance variable - * named by symbol, or indexed (0..length-1) by - * fixnum. Will raise NameError if the named - * variable does not exist, or IndexError if the index is + * named by *symbol*, or indexed (0..length-1) by + * *fixnum*. Will raise `NameError` if the named + * variable does not exist, or `IndexError` if the index is * out of range. * * Customer = Struct.new(:name, :address, :zip) @@ -524,9 +535,9 @@ mrb_struct_aset_sym(mrb_state *mrb, mrb_value s, mrb_sym id, mrb_value val) * struct[fixnum] = obj -> obj * * Attribute Assignment---Assigns to the instance variable named by - * symbol or fixnum the value obj and - * returns it. Will raise a NameError if the named - * variable does not exist, or an IndexError if the index + * *symbol* or *fixnum* the value *obj* and + * returns it. Will raise a `NameError` if the named + * variable does not exist, or an `IndexError` if the index * is out of range. * * Customer = Struct.new(:name, :address, :zip) @@ -565,10 +576,10 @@ mrb_struct_aset(mrb_state *mrb, mrb_value s) * call-seq: * struct == other_struct -> true or false * - * Equality---Returns true if other_struct is + * Equality---Returns `true` if *other_struct* is * equal to this one: they must be of the same class as generated by - * Struct::new, and the values of all instance variables - * must be equal (according to Object#==). + * `Struct::new`, and the values of all instance variables + * must be equal (according to `Object#==`). * * Customer = Struct.new(:name, :address, :zip) * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) @@ -594,7 +605,7 @@ mrb_struct_equal(mrb_state *mrb, mrb_value s) } /* Check for recursion */ - if (MRB_RECURSIVE_BINARY_P(mrb, MRB_OPSYM(eq), s, s2)) { + if (MRB_RECURSIVE_BINARY_FUNC_P(mrb, MRB_OPSYM(eq), s, s2)) { return mrb_false_value(); } @@ -618,7 +629,7 @@ mrb_struct_equal(mrb_state *mrb, mrb_value s) * struct.eql?(other) -> true or false * * Two structures are equal if they are the same object, or if all their - * fields are equal (using eql?). + * fields are equal (using `eql?`). */ static mrb_value mrb_struct_eql(mrb_state *mrb, mrb_value s) @@ -638,7 +649,7 @@ mrb_struct_eql(mrb_state *mrb, mrb_value s) } /* Check for recursion */ - if (MRB_RECURSIVE_BINARY_P(mrb, MRB_SYM_Q(eql), s, s2)) { + if (MRB_RECURSIVE_BINARY_FUNC_P(mrb, MRB_SYM_Q(eql), s, s2)) { return mrb_false_value(); } @@ -750,19 +761,19 @@ mrb_struct_to_s(mrb_state *mrb, mrb_value self) } /* - * A Struct is a convenient way to bundle a number of + * A `Struct` is a convenient way to bundle a number of * attributes together, using accessor methods, without having to write * an explicit class. * - * The Struct class is a generator of specific classes, + * The `Struct` class is a generator of specific classes, * each one of which is defined to hold a set of variables and their * accessors. In these examples, we'll call the generated class - * "CustomerClass," and we'll show an example instance of that - * class as "CustomerInst." + * "*Customer*Class," and we'll show an example instance of that + * class as "*Customer*Inst." * - * In the descriptions that follow, the parameter symbol refers + * In the descriptions that follow, the parameter *symbol* refers * to a symbol, which is either a quoted string or a - * Symbol (such as :name). + * `Symbol` (such as `:name`). */ void mrb_mruby_struct_gem_init(mrb_state* mrb) diff --git a/mrbgems/mruby-symbol-ext/mrblib/symbol.rb b/mrbgems/mruby-symbol-ext/mrblib/symbol.rb index 99fa275d5..c2166a9b9 100644 --- a/mrbgems/mruby-symbol-ext/mrblib/symbol.rb +++ b/mrbgems/mruby-symbol-ext/mrblib/symbol.rb @@ -7,7 +7,7 @@ class Symbol # call-seq: # sym.capitalize -> symbol # - # Same as sym.to_s.capitalize.intern. + # Same as `sym.to_s.capitalize.intern`. def capitalize (self.to_s.capitalize! || self).to_sym @@ -17,7 +17,7 @@ class Symbol # call-seq: # sym.downcase -> symbol # - # Same as sym.to_s.downcase.intern. + # Same as `sym.to_s.downcase.intern`. def downcase (self.to_s.downcase! || self).to_sym @@ -27,7 +27,7 @@ class Symbol # call-seq: # sym.upcase -> symbol # - # Same as sym.to_s.upcase.intern. + # Same as `sym.to_s.upcase.intern`. def upcase (self.to_s.upcase! || self).to_sym @@ -37,7 +37,7 @@ class Symbol # call-seq: # sym.casecmp(other) -> -1, 0, +1 or nil # - # Case-insensitive version of Symbol#<=>. + # Case-insensitive version of `Symbol#<=>`. def casecmp(other) return nil unless other.kind_of?(Symbol) diff --git a/mrbgems/mruby-symbol-ext/src/symbol.c b/mrbgems/mruby-symbol-ext/src/symbol.c index 41aee9c7a..d0c481ad2 100644 --- a/mrbgems/mruby-symbol-ext/src/symbol.c +++ b/mrbgems/mruby-symbol-ext/src/symbol.c @@ -41,7 +41,7 @@ mrb_sym_all_symbols(mrb_state *mrb, mrb_value self) * call-seq: * sym.length -> integer * - * Same as sym.to_s.length. + * Same as `sym.to_s.length`. */ static mrb_value mrb_sym_length(mrb_state *mrb, mrb_value self) diff --git a/mrbgems/mruby-task/README.md b/mrbgems/mruby-task/README.md new file mode 100644 index 000000000..df5b34d88 --- /dev/null +++ b/mrbgems/mruby-task/README.md @@ -0,0 +1,623 @@ +# mruby-task + +mruby-task is an mrbgem that provides cooperative multitasking with preemptive +scheduling for mruby. It enables concurrent execution of multiple tasks within +a single mruby VM instance using a priority-based scheduler with tick-based +time slicing. + +## Purpose + +The primary purpose of `mruby-task` is to enable mruby applications to: + +- Execute multiple tasks concurrently within a single VM. +- 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. +- Suspend and resume tasks programmatically. + +## Architecture + +### Task Scheduler + +The scheduler uses four priority-sorted queues: + +- **DORMANT**: Tasks that have not started or have finished execution. +- **READY**: Tasks ready to run, ordered by priority. +- **WAITING**: Tasks waiting for sleep timeout or join completion. +- **SUSPENDED**: Tasks manually suspended via `#suspend`. + +### Tick-Based Preemption + +A platform-specific timer generates periodic ticks (default: 4ms). Each task +receives a timeslice (default: 3 ticks = 12ms) before being preempted. The +scheduler automatically switches to the next ready task when: + +- A task's timeslice expires. +- A task calls `sleep`, `Task.pass`, or `join`. +- A task finishes execution. + +## Functionality + +### Creating Tasks + +Tasks are created with `Task.new` and begin execution immediately: + +```ruby +# Create a task with default priority (128) +task = Task.new do + puts "Hello from task!" + sleep 1 + puts "Task resumed" +end + +# Create a named task with custom priority +task = Task.new(name: "worker", priority: 64) do + loop do + process_data + Task.pass # Yield to other tasks + end +end + +# Start the scheduler (blocks until all tasks complete or idle) +Task.run +``` + +### Task Class Methods + +- **`Task.new(name: nil, priority: 128) { block }`**: Creates and starts a new task. Lower priority values run first (0 is highest priority). The `name` parameter must be a String if provided. The `priority` must be an Integer between 0-255. + + ```ruby + task = Task.new(name: "background", priority: 200) do + # Task code here + end + ``` + +- **`Task.current`**: Returns the currently executing task. + + ```ruby + current = Task.current + puts "Running: #{current.name}" + ``` + +- **`Task.list`**: Returns an array of all tasks (including dormant tasks). + + ```ruby + Task.list.each do |task| + puts "#{task.name}: #{task.status}" + end + ``` + +- **`Task.pass`**: Cooperatively yields execution to other ready tasks. + + ```ruby + loop do + do_work + Task.pass # Let other tasks run + end + ``` + +- **`Task.get(name)`**: Finds a task by name. Returns `nil` if not found. + + ```ruby + worker = Task.get("worker") + worker.suspend if worker + ``` + +- **`Task.stat`**: Returns a hash containing scheduler statistics: + - `:tick` (Integer): Current tick count + - `:wakeup_tick` (Integer): Next scheduled wakeup tick + - `:dormant`, `:ready`, `:waiting`, `:suspended`: Each is a hash with: + - `:count` (Integer): Number of tasks in this queue + - `:tasks` (Array): Array of task objects in this queue + + ```ruby + stats = Task.stat + puts "Tick: #{stats[:tick]}" + puts "Ready tasks: #{stats[:ready][:count]}" + stats[:ready][:tasks].each { |t| puts t.name } + ``` + +- **`Task.run`**: Starts the scheduler main loop. Blocks until no tasks remain ready or waiting. + + ```ruby + Task.new { do_async_work } + Task.run # Run scheduler until tasks complete + ``` + +### Task Instance Methods + +- **`#status`**: Returns the task status as a symbol (`:DORMANT`, `:READY`, `:RUNNING`, `:WAITING`, `:SUSPENDED`). + + ```ruby + puts task.status # => :READY + ``` + +- **`#name`** / **`#name=`**: Get or set the task name. Returns `"(noname)"` for unnamed tasks. Note: `name=` accepts any value, but `Task.new` requires a String. + + ```ruby + task.name = "worker-1" + puts task.name # => "worker-1" + + task = Task.new { } + puts task.name # => "(noname)" + ``` + +- **`#priority`** / **`#priority=`**: Get or set the task priority (0-255). Changing priority requeues the task. + + ```ruby + task.priority = 100 # Lower priority + ``` + +- **`#suspend`**: Suspends the task, moving it to the SUSPENDED queue. The task will not run until `#resume` is called. + + ```ruby + task.suspend + # Later... + task.resume + ``` + +- **`#resume`**: Resumes a suspended task, moving it to the READY queue. + + ```ruby + task.resume + ``` + +- **`#terminate`**: Terminates the task immediately, moving it to DORMANT state. + + ```ruby + task.terminate + ``` + +- **`#join`**: Blocks the current task until the target task completes. + + ```ruby + worker = Task.new { do_long_operation } + worker.join # Wait for completion + puts "Worker finished" + ``` + +### Kernel Methods (Sleep) + +The task scheduler provides task-aware sleep methods that cooperatively yield +to other tasks: + +- **`sleep(seconds)`**: Sleeps for the specified duration. Accepts integers or floats (when `MRB_NO_FLOAT` is not defined). + + ```ruby + sleep 1 # Sleep for 1 second + sleep 0.5 # Sleep for 500ms (with float support) + sleep # Sleep indefinitely (no arguments) + ``` + +- **`usleep(microseconds)`**: Sleeps for the specified number of microseconds. + + ```ruby + usleep 500000 # Sleep for 500ms + usleep 1000 # Sleep for 1ms + ``` + +- **`sleep_ms(milliseconds)`**: Sleeps for the specified number of milliseconds. + + ```ruby + sleep_ms 100 # Sleep for 100ms + ``` + +**Note**: These methods override `mruby-sleep` when both gems are present. They +provide task-aware cooperative sleep when called from within a task, or +blocking sleep when called outside task context. + +## Configuration + +### Build Configuration + +Enable the task scheduler by including the gem in your build config: + +```ruby +MRuby::Build.new do |conf| + # ... other configuration ... + + conf.gem :core => 'mruby-task' + + # ... other gems ... +end +``` + +This automatically defines `MRB_USE_TASK_SCHEDULER`. + +### Timing Configuration + +Timing parameters can be configured via C defines: + +```c +#define MRB_TICK_UNIT 4 // Tick period in milliseconds (default: 4ms) +#define MRB_TIMESLICE_TICK_COUNT 3 // Ticks per timeslice (default: 3) +``` + +Default timeslice: `MRB_TICK_UNIT * MRB_TIMESLICE_TICK_COUNT = 12ms` + +### Stack Configuration + +Task stack and call info sizes: + +```c +#define TASK_STACK_INIT_SIZE 64 // Initial stack entries (default: 64) +#define TASK_CI_INIT_SIZE 8 // Initial callinfo entries (default: 8) +``` + +These grow automatically as needed, similar to Fiber. + +## Platform Requirements + +### 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. + +#### Built-in HAL Gems + +**hal-posix-task** - For POSIX systems (Linux, macOS, BSD, Unix) + +- Uses `SIGALRM` and `setitimer()` for timer +- Uses `sigprocmask()` for interrupt protection +- Uses `SA_RESTART` to prevent `EINTR` on system calls +- Supports multiple VMs per process + +**hal-win-task** - For Windows + +- Uses multimedia timer API (`timeSetEvent`/`timeKillEvent`) +- Uses `CRITICAL_SECTION` for interrupt protection +- Supports multiple VMs per process + +#### HAL 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: + +```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.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 + +**Multi-VM support:** + +- Both HAL implementations 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 + +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`: + +```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 + */ +void mrb_task_hal_init(mrb_state *mrb); + +/** + * Cleanup timer and unregister VM + * Called during gem finalization + */ +void mrb_task_hal_final(mrb_state *mrb); + +/** + * 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 + */ +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 + */ +void mrb_task_hal_idle_cpu(mrb_state *mrb); +``` + +**Example custom HAL gem structure:** + +``` +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; +} +``` + +See `hal-posix-task` and `hal-win-task` source code for complete reference implementations. + +## Examples + +### Basic Multitasking + +```ruby +Task.new(name: "task1") do + 3.times do |i| + puts "Task 1: #{i}" + sleep 0.1 + end +end + +Task.new(name: "task2") do + 3.times do |i| + puts "Task 2: #{i}" + sleep 0.1 + end +end + +Task.run # Run until both tasks complete +``` + +### Priority Scheduling + +```ruby +# High priority task (runs first) +Task.new(priority: 0) do + puts "High priority" + sleep 0.1 +end + +# Low priority task (runs after high priority yields) +Task.new(priority: 255) do + puts "Low priority" +end + +Task.run +``` + +### Cooperative Yielding + +```ruby +Task.new(name: "cooperative") do + loop do + do_some_work + Task.pass # Yield to other tasks + break if done? + end +end + +Task.new(name: "other") do + do_other_work +end + +Task.run +``` + +### Task Synchronization + +```ruby +worker = Task.new(name: "worker") do + puts "Working..." + sleep 1 + puts "Work done" + 42 # Return value +end + +Task.new(name: "main") do + puts "Waiting for worker..." + worker.join + puts "Worker completed!" +end + +Task.run +``` + +### Task Control + +```ruby +task = Task.new do + loop do + puts "Running..." + sleep 0.5 + end +end + +# From another task or after Task.run returns: +task.suspend # Pause execution +sleep 1 +task.resume # Resume execution +sleep 1 +task.terminate # Stop permanently +``` + +## Limitations and Compatibility + +### Relationship with Fiber + +Tasks and Fibers both use `mrb_context` but are **not compatible**: + +- Tasks are scheduled automatically by the preemptive scheduler. +- Fibers require explicit `Fiber.yield` and `resume` calls. +- Do not mix Tasks and Fibers in the same application. + +### Relationship with mruby-sleep + +When `mruby-task` is enabled: + +- The `sleep`, `usleep`, and `sleep_ms` methods are task-aware. +- Inside a task, they cooperatively yield to the scheduler. +- Outside a task (or when scheduler is idle), they block. +- `mruby-sleep` should be excluded from your build when using `mruby-task`. + +### Thread Safety + +The task scheduler is **not thread-safe**. All tasks run in a single OS thread. +For multi-core concurrency, use OS threads with separate mruby VMs per thread. + +### Exceptions + +Uncaught exceptions in a task will terminate that task but not affect other +tasks. The exception is not propagated to the scheduler. + +### GC Integration + +Task contexts are registered with the garbage collector. Tasks and their +stacks/callinfo are properly marked and freed. + +## Testing + +The gem includes tests that verify: + +- Task creation and execution +- Priority scheduling +- Sleep and wakeup +- Join synchronization +- Suspend and resume +- Task.pass cooperative yielding + +Run tests with: + +```bash +rake CONFIG=host-debug test:lib +``` + +## Implementation Details + +### Task States + +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) +- `SUSPENDED (0x08)`: Manually suspended + +### Wait Reasons + +When a task is in WAITING state, the reason indicates why: + +- `NONE (0x00)`: No specific reason +- `SLEEP (0x01)`: Sleeping for time +- `MUTEX (0x02)`: Waiting for mutex (reserved, not yet implemented) +- `JOIN (0x04)`: Waiting for another task + +### Scheduler Algorithm + +1. Get the highest-priority task from the READY queue +2. Set task status to RUNNING and switch context (`mrb->c = &task->c`) +3. Execute task via `mrb_vm_exec()` until: + - Task yields (sleep, pass, join) + - Timeslice expires (preemption) + - Task completes or terminates +4. Handle completion (wake joined tasks, move to DORMANT) +5. Run incremental GC if needed +6. Requeue task to READY (if still running) or appropriate queue +7. Repeat from step 1 + +## Future Enhancements + +Planned features not yet implemented: + +- **Mutex support**: Thread-safe synchronization primitives +- **Task.raise**: Throw exceptions to other tasks +- **Task#value**: Retrieve task return value (like Thread#value) +- **Per-task timeslice configuration** + +## License + +MIT License (same as mruby) + +## See Also + +- `mruby-fiber`: Cooperative fibers with manual control +- `mruby-sleep`: Blocking sleep (superseded by mruby-task) diff --git a/mrbgems/mruby-task/examples/inspection.rb b/mrbgems/mruby-task/examples/inspection.rb new file mode 100644 index 000000000..08068f535 --- /dev/null +++ b/mrbgems/mruby-task/examples/inspection.rb @@ -0,0 +1,65 @@ +# Task Inspection Example +# Demonstrates task status and inspect methods + +puts "=== Task Inspection Demo ===" +puts + +# Create some tasks +worker1 = Task.new(name: "worker-1") do + puts "Worker 1: Starting" + puts "Worker 1: My status is #{Task.current.status}" + puts "Worker 1: My inspect is #{Task.current.inspect}" + sleep 0.5 + puts "Worker 1: Finished" +end + +worker2 = Task.new(name: "worker-2") do + sleep 1 + puts "Worker 2: Finished" +end + +# Inspect before running +puts "Initial task states:" +puts " worker1: #{worker1.inspect}" +puts " status=#{worker1.status}" +puts " worker2: #{worker2.inspect}" +puts " status=#{worker2.status}" +puts + +# Create an observer task +observer = Task.new(name: "observer") do + sleep 0.2 + + puts "\nObserver checking task states:" + puts " worker1: #{worker1.inspect}" + puts " status=#{worker1.status}" + puts " worker2: #{worker2.inspect}" + puts " status=#{worker2.status}" + + sleep 0.5 + + # Suspend worker2 + worker2.suspend + puts "\nObserver suspended worker2:" + puts " worker2: #{worker2.inspect}" + puts " status=#{worker2.status}" + + sleep 0.3 + + # Resume and terminate + worker2.resume + sleep 0.1 + worker2.terminate + + puts "\nObserver terminated worker2:" + puts " worker2: #{worker2.inspect}" + puts " status=#{worker2.status}" +end + +# Run the scheduler +Task.run + +puts "\n=== Final states ===" +puts " worker1: #{worker1.inspect} status=#{worker1.status}" +puts " worker2: #{worker2.inspect} status=#{worker2.status}" +puts " observer: #{observer.inspect} status=#{observer.status}" diff --git a/mrbgems/mruby-task/examples/priority.rb b/mrbgems/mruby-task/examples/priority.rb new file mode 100644 index 000000000..ee3ec97ed --- /dev/null +++ b/mrbgems/mruby-task/examples/priority.rb @@ -0,0 +1,41 @@ +# Priority Scheduling Example +# Lower priority values = higher priority (0 is highest) + +puts "=== Priority Scheduling Demo ===" +puts + +# Create tasks with different priorities +low_priority = Task.new(name: "low-priority", priority: 200) do + 5.times do |i| + puts " [Low Priority] iteration #{i}" + sleep 0.1 + end +end + +high_priority = Task.new(name: "high-priority", priority: 50) do + 5.times do |i| + puts "[High Priority] iteration #{i}" + sleep 0.1 + end +end + +medium_priority = Task.new(name: "medium-priority", priority: 128) do + 5.times do |i| + puts " [Medium Priority] iteration #{i}" + sleep 0.1 + end +end + +puts "Created 3 tasks with different priorities:" +puts " High: priority=50" +puts " Medium: priority=128" +puts " Low: priority=200" +puts +puts "Tasks will run in priority order (highest first)" +puts + +# Run the scheduler +Task.run + +puts +puts "=== All tasks completed ===" diff --git a/mrbgems/mruby-task/examples/producer_consumer.rb b/mrbgems/mruby-task/examples/producer_consumer.rb new file mode 100644 index 000000000..4a1427f8b --- /dev/null +++ b/mrbgems/mruby-task/examples/producer_consumer.rb @@ -0,0 +1,58 @@ +# Producer/Consumer Example +# Demonstrates task coordination using shared state + +puts "=== Producer/Consumer Demo ===" +puts + +# Shared buffer +$buffer = [] +$max_items = 10 +$produced = 0 + +# Producer task +producer = Task.new(name: "producer") do + $max_items.times do |i| + # Produce an item + item = "item-#{i}" + $buffer << item + $produced += 1 + puts "Producer: created #{item} (buffer size: #{$buffer.size})" + + sleep 0.3 + end + puts "Producer: finished producing #{$max_items} items" +end + +# Consumer task +consumer = Task.new(name: "consumer") do + consumed = 0 + + while consumed < $max_items + if $buffer.empty? + puts "Consumer: buffer empty, waiting..." + sleep 0.2 + else + item = $buffer.shift + consumed += 1 + puts "Consumer: consumed #{item} (#{consumed}/#{$max_items})" + sleep 0.5 + end + end + + puts "Consumer: finished consuming #{consumed} items" +end + +# Monitor task +monitor = Task.new(name: "monitor") do + loop do + sleep 1 + puts "Monitor: produced=#{$produced}, buffer=#{$buffer.size}, remaining=#{$max_items - $produced}" + break if $produced >= $max_items && $buffer.empty? + end +end + +# Run the scheduler +Task.run + +puts "\n=== All tasks completed ===" +puts "Final state: produced=#{$produced}, buffer=#{$buffer.size}" diff --git a/mrbgems/mruby-task/examples/simple.rb b/mrbgems/mruby-task/examples/simple.rb new file mode 100644 index 000000000..52a8de571 --- /dev/null +++ b/mrbgems/mruby-task/examples/simple.rb @@ -0,0 +1,27 @@ +# Simple Task Example +# Basic usage of mruby-task + +puts "=== Simple Task Demo ===" +puts + +# Create a simple task +Task.new(name: "hello") do + 3.times do |i| + puts "Hello from task, iteration #{i}" + sleep 0.5 + end +end + +# Create another task +Task.new(name: "world") do + 3.times do |i| + puts "World from task, iteration #{i}" + sleep 0.7 + end +end + +# Run the scheduler - blocks until all tasks complete +puts "Starting tasks..." +Task.run + +puts "All tasks completed!" diff --git a/mrbgems/mruby-task/examples/statistics.rb b/mrbgems/mruby-task/examples/statistics.rb new file mode 100644 index 000000000..3fd4fa98a --- /dev/null +++ b/mrbgems/mruby-task/examples/statistics.rb @@ -0,0 +1,59 @@ +# Task Statistics Example +# Demonstrates Task.stat for monitoring scheduler state + +def print_stats(label) + stat = Task.stat + puts "\n#{label}" + puts " Tick: #{stat[:tick]}, Next wakeup: #{stat[:wakeup_tick]}" + puts " Ready: #{stat[:ready][:count]} tasks" + puts " Waiting: #{stat[:waiting][:count]} tasks" + puts " Suspended: #{stat[:suspended][:count]} tasks" + puts " Dormant: #{stat[:dormant][:count]} tasks" +end + +puts "=== Task Statistics Demo ===" + +print_stats("Initial state:") + +# Create several tasks +workers = [] +5.times do |i| + workers << Task.new(name: "worker-#{i}") do + sleep 0.5 + puts "Worker #{i} completed" + end +end + +print_stats("After creating 5 tasks:") + +# Create a monitor task +monitor = Task.new(name: "monitor") do + sleep 0.1 + print_stats("Monitor: After 0.1s:") + + # Suspend a couple tasks + workers[2].suspend + workers[3].suspend + + print_stats("Monitor: After suspending 2 tasks:") + + sleep 0.5 + + print_stats("Monitor: After 0.5s more:") + + # Resume suspended tasks + workers[2].resume + workers[3].resume + + print_stats("Monitor: After resuming tasks:") + + # Wait for all workers + workers.each(&:join) +end + +# Run the scheduler +Task.run + +print_stats("Final state:") + +puts "\n=== Demo complete ===" diff --git a/mrbgems/mruby-task/examples/suspend_resume.rb b/mrbgems/mruby-task/examples/suspend_resume.rb new file mode 100644 index 000000000..c90832ecc --- /dev/null +++ b/mrbgems/mruby-task/examples/suspend_resume.rb @@ -0,0 +1,47 @@ +# Suspend and Resume Example +# Demonstrates manual task control + +puts "=== Suspend/Resume Demo ===" +puts + +$counter = 0 + +# Create a worker task +worker = Task.new(name: "worker") do + 10.times do |i| + puts "Worker: iteration #{i}, counter=#{$counter}" + $counter += 1 + sleep 0.2 + end + puts "Worker: finished!" +end + +# Create a controller task +controller = Task.new(name: "controller") do + sleep 0.5 + + puts "\nController: Suspending worker..." + worker.suspend + puts "Controller: Worker status = #{worker.status}" + + sleep 1 + + puts "\nController: Incrementing counter while worker is suspended..." + 5.times do + $counter += 10 + puts "Controller: counter=#{$counter}" + sleep 0.2 + end + + puts "\nController: Resuming worker..." + worker.resume + puts "Controller: Worker status = #{worker.status}" + + # Wait for worker to finish + worker.join +end + +# Run the scheduler +Task.run + +puts "\n=== Final counter value: #{$counter} ===" diff --git a/mrbgems/mruby-task/examples/task_pass.rb b/mrbgems/mruby-task/examples/task_pass.rb new file mode 100644 index 000000000..3a629f1d6 --- /dev/null +++ b/mrbgems/mruby-task/examples/task_pass.rb @@ -0,0 +1,21 @@ +$global = 0 +task = Task.new do + 10.times do |i| + puts "Task #{i} is running" + $global += 1 + sleep 1 + end +end + +while true + sleep 0.5 + if $global < 6 + puts "Global variable is less than 6, waiting..." + Task.pass + else + break + end +end + +puts "Global variable reached 6, exiting loop" +Task.run diff --git a/mrbgems/mruby-task/include/task.h b/mrbgems/mruby-task/include/task.h new file mode 100644 index 000000000..28e866165 --- /dev/null +++ b/mrbgems/mruby-task/include/task.h @@ -0,0 +1,110 @@ +/* +** task.h - Task scheduler +** +** See Copyright Notice in mruby.h +*/ + +#ifndef MRUBY_TASK_H +#define MRUBY_TASK_H + +#include + +/* + * Task status values (bit-mapped) + */ +enum { + MRB_TASK_STATUS_DORMANT = 0x00, /* Not started or finished */ + MRB_TASK_STATUS_READY = 0x02, /* Ready to run */ + MRB_TASK_STATUS_RUNNING = 0x03, /* Currently executing */ + MRB_TASK_STATUS_WAITING = 0x04, /* Waiting for condition */ + MRB_TASK_STATUS_SUSPENDED = 0x08, /* Manually suspended */ +}; + +/* + * Task wait reason + */ +enum { + MRB_TASK_REASON_NONE = 0x00, /* No specific reason */ + 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 */ +}; + +/* + * Task structure - represents a single task in the scheduler + * + * Memory-optimized layout: + * - Removed priority_preemption (always equals priority): 1 byte + * - 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 { + struct mrb_task *next; /* Linked list pointer */ + uint8_t priority; /* Priority (0-255, 0=highest) */ + uint8_t status; /* Current status (TASKSTATUS enum) */ + uint8_t reason; /* Wait reason (TASKREASON enum) */ + mrb_value name; /* Optional task name */ + + /* Wait-specific data - mutually exclusive based on reason field */ + union { + 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) */ + } 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; + + struct mrb_context c; /* Execution context (stack, callinfo, etc) */ +} mrb_task; + +/* + * Task queue configuration + * (mrb_task_state is defined in mruby.h) + */ +#define MRB_NUM_TASK_QUEUE 4 + +/* Queue indices */ +#define MRB_TASK_QUEUE_DORMANT 0 +#define MRB_TASK_QUEUE_READY 1 +#define MRB_TASK_QUEUE_WAITING 2 +#define MRB_TASK_QUEUE_SUSPENDED 3 + +/* Configuration */ +#ifndef MRB_TICK_UNIT +#define MRB_TICK_UNIT 4 /* Tick period in milliseconds */ +#endif + +#ifndef MRB_TIMESLICE_TICK_COUNT +#define MRB_TIMESLICE_TICK_COUNT 3 /* Number of ticks per timeslice */ +#endif + +#define TASK_STACK_INIT_SIZE 64 /* Initial task stack size */ +#define TASK_CI_INIT_SIZE 4 /* Initial task callinfo size */ + +/* + * HAL (Hardware Abstraction Layer) functions + * Platform-specific implementations must provide these + */ +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); + +/* + * Core task scheduler API + */ +void mrb_tick(mrb_state *mrb); +mrb_value mrb_task_run(mrb_state *mrb); +void mrb_task_mark_all(mrb_state *mrb); + +#endif /* MRUBY_TASK_H */ diff --git a/mrbgems/mruby-task/include/task_hal.h b/mrbgems/mruby-task/include/task_hal.h new file mode 100644 index 000000000..907320596 --- /dev/null +++ b/mrbgems/mruby-task/include/task_hal.h @@ -0,0 +1,162 @@ +/* +** task_hal.h - Task scheduler Hardware Abstraction Layer (HAL) +** +** See Copyright Notice in mruby.h +** +** This header defines the HAL interface that platform-specific implementations +** must provide. The HAL separates platform-specific timer and interrupt handling +** from the core task scheduler logic. +*/ + +#ifndef MRUBY_TASK_HAL_H +#define MRUBY_TASK_HAL_H + +#include + +/* + * Configuration - can be overridden in platform-specific build configs + */ + +/* Tick period in milliseconds - how often the timer fires */ +#ifndef MRB_TICK_UNIT +#define MRB_TICK_UNIT 4 +#endif + +/* Number of timer ticks per task timeslice */ +#ifndef MRB_TIMESLICE_TICK_COUNT +#define MRB_TIMESLICE_TICK_COUNT 3 +#endif + +/* Maximum number of concurrent mrb_state instances with task scheduler */ +#ifndef MRB_TASK_MAX_VMS +#define MRB_TASK_MAX_VMS 8 +#endif + +/* + * HAL Interface Functions + * + * Platform-specific implementations (hal-posix-task, hal-win-task, etc.) + * must provide these functions. + */ + +/** + * Initialize hardware timer and interrupt system + * + * Called once during mruby-task gem initialization. Should set up a periodic + * timer that calls mrb_tick(mrb) every MRB_TICK_UNIT milliseconds. + * + * Requirements: + * - Initialize platform-specific timer hardware/APIs + * - Set up timer to fire every MRB_TICK_UNIT milliseconds + * - Register mrb_state for multi-VM support if needed + * - Initialize interrupt protection mechanisms (mutexes, signal masks, etc.) + * - Timer should call mrb_tick() on each tick for all registered VMs + * + * @param mrb The mruby state to associate with the timer + */ +void mrb_hal_task_init(mrb_state *mrb); + +/** + * Cleanup timer and interrupt resources + * + * Called during mruby-task gem finalization. Should clean up all resources + * allocated by mrb_hal_task_init(). + * + * Requirements: + * - Stop and cleanup platform timer + * - Unregister mrb_state from multi-VM support + * - Free any allocated HAL resources + * - If last VM, cleanup global HAL state + * + * @param mrb The mruby state to disassociate from the timer + */ +void mrb_hal_task_final(mrb_state *mrb); + +/** + * Enable timer interrupts (exit critical section) + * + * Called by the task scheduler when it's safe to allow timer interrupts. + * Should enable timer interrupts/callbacks that were disabled by + * mrb_task_disable_irq(). + * + * Requirements: + * - Must be reentrant (can be called multiple times) + * - Should use nesting counter or equivalent for nested critical sections + * - On POSIX: unmask signals + * - On Windows: leave critical section + * - On embedded: enable timer interrupts + */ +void mrb_task_enable_irq(void); + +/** + * Disable timer interrupts (enter critical section) + * + * Called by the task scheduler before modifying shared task state. + * Should disable timer interrupts/callbacks to prevent concurrent access. + * + * Requirements: + * - Must be reentrant (can be called multiple times) + * - Should use nesting counter or equivalent for nested critical sections + * - On POSIX: block signals + * - On Windows: enter critical section + * - On embedded: disable timer interrupts + */ +void mrb_task_disable_irq(void); + +/** + * Put CPU in low-power/idle mode + * + * Called by the scheduler when no tasks are ready to run but some tasks + * are waiting or suspended. Should briefly idle the CPU or sleep for + * approximately MRB_TICK_UNIT milliseconds to allow timer to fire. + * + * Requirements: + * - Should return when timer fires or after ~MRB_TICK_UNIT milliseconds + * - Must allow timer interrupts to occur during idle + * - On POSIX: usleep() or nanosleep() + * - On Windows: Sleep() + * - On embedded: platform-specific sleep/wait-for-interrupt instruction + * + * @param mrb The mruby state (for context, may be unused) + */ +void mrb_hal_task_idle_cpu(mrb_state *mrb); + +/** + * Sleep for specified microseconds in wall-clock time + * + * Called by sleep functions when in root context (not in a task). + * Should sleep for the specified number of microseconds in real wall-clock + * time, allowing timer interrupts to occur during the sleep. + * + * Requirements: + * - Sleep for approximately usec microseconds in wall-clock time + * - Must allow timer interrupts/callbacks during sleep + * - Should handle interruptions gracefully and complete full sleep duration + * - On POSIX: use clock_gettime + nanosleep loop for accuracy + * - On Windows: use Sleep() with millisecond conversion + * - On embedded: platform-specific delay with interrupt support + * + * @param mrb The mruby state (for context, may be unused) + * @param usec Number of microseconds to sleep + */ +void mrb_hal_task_sleep_us(mrb_state *mrb, mrb_int usec); + +/* + * Core scheduler function (implemented in task.c, called by HAL) + */ + +/** + * Tick handler - advances scheduler time and wakes sleeping tasks + * + * HAL timer callback must call this function every MRB_TICK_UNIT milliseconds + * for each registered mrb_state. This function: + * - Increments the global tick counter + * - Decrements running task's timeslice + * - Wakes tasks whose sleep time has expired + * - Triggers context switches when needed + * + * @param mrb The mruby state to tick + */ +void mrb_tick(mrb_state *mrb); + +#endif /* MRUBY_TASK_HAL_H */ diff --git a/mrbgems/mruby-task/mrbgem.rake b/mrbgems/mruby-task/mrbgem.rake new file mode 100644 index 000000000..7edc62081 --- /dev/null +++ b/mrbgems/mruby-task/mrbgem.rake @@ -0,0 +1,36 @@ +MRuby::Gem::Specification.new('mruby-task') do |spec| + spec.license = 'MIT' + spec.authors = 'mruby developers' + spec.summary = 'Cooperative multitasking with preemptive scheduling' + + # 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 + 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" + end + end +end diff --git a/mrbgems/mruby-task/src/task.c b/mrbgems/mruby-task/src/task.c new file mode 100644 index 000000000..fb550633d --- /dev/null +++ b/mrbgems/mruby-task/src/task.c @@ -0,0 +1,1173 @@ +/* +** task.c - Task scheduler +** +** See Copyright Notice in mruby.h +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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) \ + do { \ + (var) = (mrb_task*)mrb_data_get_ptr(mrb, (self), &mrb_task_type); \ + if (!(var)) { \ + mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid task"); \ + } \ + } while (0) + +/* Convert microseconds to tick count */ +#define USEC_TO_TICKS(usec) (((usec) / 1000) / MRB_TICK_UNIT) + +/* + * Task data type for GC + */ +static void +mrb_task_free(mrb_state *mrb, void *ptr) +{ + mrb_task *t = (mrb_task*)ptr; + if (t) { + /* Unregister from GC protection (unless it's the main task during shutdown) */ + if (t != mrb->task.main_task) { + mrb_gc_unregister(mrb, t->self); + } + + /* Free context resources - always free if allocated */ + /* Main task never has allocated context (stbase/cibase are NULL) */ + if (t->c.stbase) { + mrb_free(mrb, t->c.stbase); + } + if (t->c.cibase) { + mrb_free(mrb, t->c.cibase); + } + + /* Free the task structure itself */ + mrb_free(mrb, t); + } +} + +static const struct mrb_data_type mrb_task_type = { + "Task", mrb_task_free, +}; + +/* + * GC marking function for all tasks + * Called from gc.c during root_scan_phase + */ +void +mrb_task_mark_all(mrb_state *mrb) +{ + int qi; + for (qi = 0; qi < 4; qi++) { + mrb_task *t = mrb->task.queues[qi]; + while (t) { + struct mrb_context *c = &t->c; + mrb_callinfo *ci; + size_t i, e; + + /* Mark task's stack */ + if (c->stbase) { + if (c->ci) { + e = (c->ci->stack ? c->ci->stack - c->stbase : 0); + e += mrb_ci_nregs(c->ci); + } + else { + e = 0; + } + if (c->stbase + e > c->stend) e = c->stend - c->stbase; + for (i = 0; i < e; i++) { + mrb_gc_mark_value(mrb, c->stbase[i]); + } + } + + /* Mark call stack */ + if (c->cibase && c->ci) { + for (ci = c->cibase; ci <= c->ci; ci++) { + if (ci->proc) { + mrb_gc_mark(mrb, (struct RBasic*)ci->proc); + } + if (ci->u.target_class) { + mrb_gc_mark(mrb, (struct RBasic*)ci->u.target_class); + } + } + } + + /* Mark fiber */ + mrb_gc_mark(mrb, (struct RBasic*)c->fib); + + /* 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->name); + + t = t->next; + } + } +} + +/* + * Queue operations + */ + +/* Get queue head pointer based on task status */ +static mrb_task** +q_get_queue(mrb_state *mrb, mrb_task *t) +{ + switch (t->status) { + case MRB_TASK_STATUS_DORMANT: + return &q_dormant_; + case MRB_TASK_STATUS_READY: + case MRB_TASK_STATUS_RUNNING: + return &q_ready_; + case MRB_TASK_STATUS_WAITING: + return &q_waiting_; + case MRB_TASK_STATUS_SUSPENDED: + return &q_suspended_; + default: + return &q_dormant_; + } +} + +/* 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) +{ + mrb_task **q = q_get_queue(mrb, t); + mrb_task *curr = *q; + mrb_task *prev = NULL; + + /* Find insertion point - insert before first task with lower priority */ + while (curr != NULL && curr->priority <= t->priority) { + prev = curr; + curr = curr->next; + } + + /* Insert task */ + t->next = curr; + if (prev == NULL) { + *q = t; /* Insert at head */ + } + else { + prev->next = t; /* Insert after prev */ + } +} + +/* Delete task from its current queue */ +static void +q_delete_task(mrb_state *mrb, mrb_task *t) +{ + mrb_task **q = q_get_queue(mrb, t); + mrb_task *curr = *q; + mrb_task *prev = NULL; + + /* Find and remove task */ + while (curr != NULL) { + if (curr == t) { + if (prev == NULL) { + *q = curr->next; /* Remove from head */ + } + else { + prev->next = curr->next; /* Remove from middle/end */ + } + t->next = NULL; + return; + } + prev = curr; + curr = curr->next; + } +} + +/* + * Task lifecycle + */ + +/* Allocate new task */ +static mrb_task* +task_alloc(mrb_state *mrb) +{ + mrb_task *t = (mrb_task*)mrb_malloc(mrb, sizeof(mrb_task)); + memset(t, 0, sizeof(mrb_task)); + return t; +} + +/* Initialize task context (stack and callinfo) - similar to Fiber */ +static void +task_init_context(mrb_state *mrb, mrb_task *t, const struct RProc *proc) +{ + static const struct mrb_context mrb_context_zero = { 0 }; + struct mrb_context *c = &t->c; + + *c = mrb_context_zero; + + /* Initialize VM stack */ + size_t slen = TASK_STACK_INIT_SIZE; + if (proc->body.irep->nregs > slen) { + slen += proc->body.irep->nregs; + } + c->stbase = (mrb_value*)mrb_malloc(mrb, slen * sizeof(mrb_value)); + c->stend = c->stbase + slen; + + /* Initialize stack values to nil */ + { + mrb_value *s = c->stbase + 1; + mrb_value *send = c->stend; + while (s < send) { + SET_NIL_VALUE(*s); + s++; + } + } + + /* Copy receiver from current context */ + c->stbase[0] = mrb->c->ci->stack[0]; + + /* Initialize callinfo stack */ + static const mrb_callinfo ci_zero = { 0 }; + c->cibase = (mrb_callinfo*)mrb_malloc(mrb, TASK_CI_INIT_SIZE * sizeof(mrb_callinfo)); + c->ciend = c->cibase + TASK_CI_INIT_SIZE; + c->ci = c->cibase; + c->cibase[0] = ci_zero; + + /* Setup callinfo */ + mrb_callinfo *ci = c->ci; + mrb_vm_ci_target_class_set(ci, MRB_PROC_TARGET_CLASS(proc)); + mrb_vm_ci_proc_set(ci, proc); + ci->stack = c->stbase; + ci->pc = proc->body.irep->iseq; /* Initialize PC to start of bytecode */ + ci[1] = ci[0]; + c->ci++; /* Push dummy callinfo */ + + c->status = MRB_TASK_CREATED; +} + +/* + * Scheduler core + */ + +/* Wake up tasks waiting on join for a completed task */ +static void +wake_up_join_waiters(mrb_state *mrb, mrb_task *completed_task) +{ + 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); + 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(); + } + curr = next; + } +} + +/* Change task state with IRQ protection and queue management */ +static void +task_change_state(mrb_state *mrb, mrb_task *t, uint8_t new_status) +{ + mrb_task_disable_irq(); + q_delete_task(mrb, t); + t->status = new_status; + q_insert_task(mrb, t); + mrb_task_enable_irq(); +} + +/* Execute a single task - core task execution logic */ +static void +execute_task(mrb_state *mrb, mrb_task *t) +{ + struct mrb_context *prev_c; + mrb_callinfo *prev_ci; + uint8_t prev_cci; + + /* Set task as running */ + t->status = MRB_TASK_STATUS_RUNNING; + t->state.timeslice = MRB_TIMESLICE_TICK_COUNT; + + /* Switch to task context */ + prev_c = mrb->c; + prev_ci = prev_c->ci; + prev_cci = prev_c->ci->cci; + t->c.prev = mrb->c; + mrb->c = &t->c; + + /* If task hasn't started yet, pop dummy callinfo */ + if (t->c.status == MRB_FIBER_CREATED) { + t->c.ci--; + } + + /* Clear switching flag */ + switching_ = FALSE; + + /* Set status to RUNNING so VM can transition it properly */ + t->c.status = MRB_FIBER_RUNNING; + + /* Save proc and PC to locals before calling mrb_vm_exec */ + const struct RProc *proc = t->c.ci->proc; + const mrb_code *pc = t->c.ci->pc; + + /* With C function boundary checks, proc should never be NULL on resume */ + if (!proc) { + mrb_raise(mrb, E_RUNTIME_ERROR, "task context corrupted: no proc on resume"); + } + + /* 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); + + /* Clear vmexec flag */ + t->c.vmexec = FALSE; + + /* Clear switching flag */ + switching_ = FALSE; + + /* Restore context */ + mrb->c = prev_c; + t->c.prev = NULL; + prev_c->ci = prev_ci; + prev_ci->cci = prev_cci; + + /* Handle task termination */ + if (t->c.status == MRB_FIBER_TERMINATED) { + switching_ = FALSE; + mrb_task_disable_irq(); + q_delete_task(mrb, t); + t->status = MRB_TASK_STATUS_DORMANT; + q_insert_task(mrb, t); + mrb_task_enable_irq(); + + /* Wake up tasks waiting on join */ + wake_up_join_waiters(mrb, t); + } + else if (t->status == MRB_TASK_STATUS_RUNNING) { + /* Task yielded but still running - move to ready queue */ + t->status = MRB_TASK_STATUS_READY; + } +} + +/* Tick handler - called by timer interrupt */ +void +mrb_tick(mrb_state *mrb) +{ + mrb_task *t; + + /* Increment global tick counter */ + tick_++; + + /* 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) { + switching_ = TRUE; /* Trigger context switch */ + } + } + + /* Wake up sleeping tasks whose wakeup time has passed */ + if ((int32_t)(wakeup_tick_ - tick_) <= 0) { + mrb_task *curr = q_waiting_; + mrb_task *next; + uint32_t next_wakeup = UINT32_MAX; + + while (curr != NULL) { + next = curr->next; + + 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); + curr->status = MRB_TASK_STATUS_READY; + curr->reason = MRB_TASK_REASON_NONE; + q_insert_task(mrb, curr); + switching_ = TRUE; + } + else if (curr->wait.wakeup_tick < next_wakeup) { + next_wakeup = curr->wait.wakeup_tick; + } + } + + curr = next; + } + + wakeup_tick_ = next_wakeup; + } +} + +/* Main scheduler loop */ +mrb_value +mrb_task_run(mrb_state *mrb) +{ + mrb_task *t; + + while (1) { + t = q_ready_; + + /* No task ready - check if all tasks are done */ + if (!t) { + /* If there are tasks waiting or suspended, idle */ + if (q_waiting_ || q_suspended_) { + mrb_hal_task_idle_cpu(mrb); + continue; + } + /* All tasks are dormant - scheduler done */ + break; + } + + /* Safety check - don't execute terminated tasks */ + if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_FIBER_TERMINATED) { + /* Task is terminated but still in queue - remove it */ + mrb_task_disable_irq(); + q_delete_task(mrb, t); + if (t->status != MRB_TASK_STATUS_DORMANT) { + t->status = MRB_TASK_STATUS_DORMANT; + q_insert_task(mrb, t); + } + mrb_task_enable_irq(); + continue; + } + + /* Execute task using core logic */ + execute_task(mrb, t); + + /* Move to end of ready queue if still running (round-robin) */ + if (t->status == MRB_TASK_STATUS_READY) { + task_change_state(mrb, t, MRB_TASK_STATUS_READY); + } + + /* Run incremental GC if active */ + if (mrb->gc.state != MRB_GC_STATE_ROOT) { + mrb_incremental_gc(mrb); + } + } + + return mrb_nil_value(); +} + +/* + * Sleep operations + */ + +static void +sleep_us_impl(mrb_state *mrb, uint32_t usec) +{ + mrb_task *t; + + /* Check if we're in a task context */ + if (mrb->c == mrb->root_c) { + /* Not in task context - sleep in real wall-clock time using HAL */ + mrb_hal_task_sleep_us(mrb, usec); + /* Clear switching flag - we're in root context, not switching to a task */ + switching_ = FALSE; + return; + } + + /* Check for C function boundary - cannot do cooperative context switch */ + mrb_callinfo *ci; + for (ci = mrb->c->ci; ci >= mrb->c->cibase; ci--) { + if (ci->cci > 0) { + /* Inside C function - fall back to blocking sleep without context switch */ + mrb_hal_task_sleep_us(mrb, usec); + switching_ = FALSE; + return; + } + } + + /* In task context - get current running task */ + t = MRB2TASK(mrb); + + mrb_task_disable_irq(); + + /* Remove from ready queue */ + q_delete_task(mrb, t); + + /* Move to waiting queue */ + t->status = MRB_TASK_STATUS_WAITING; + t->reason = MRB_TASK_REASON_SLEEP; + /* 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) { + wakeup_tick_ = t->wait.wakeup_tick; + } + + q_insert_task(mrb, t); + + mrb_task_enable_irq(); + + /* Trigger context switch */ + switching_ = TRUE; +} + +static void +sleep_ms_impl(mrb_state *mrb, uint32_t ms) +{ + sleep_us_impl(mrb, ms * 1000); +} + +static mrb_value +mrb_f_sleep(mrb_state *mrb, mrb_value self) +{ + mrb_float sec = 0; + mrb_int n = mrb_get_args(mrb, "|f", &sec); + + if (n == 0) { + /* No argument - suspend indefinitely */ + mrb_task *t = q_ready_; + if (t) { + mrb_task_disable_irq(); + q_delete_task(mrb, t); + t->status = MRB_TASK_STATUS_SUSPENDED; + q_insert_task(mrb, t); + mrb_task_enable_irq(); + switching_ = TRUE; + } + return mrb_nil_value(); + } + + if (sec < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "time interval must be positive"); + } + + mrb_int ms = (mrb_int)(sec * 1000); + sleep_ms_impl(mrb, (uint32_t)ms); + + return mrb_fixnum_value((mrb_int)sec); +} + +static mrb_value +mrb_f_sleep_ms(mrb_state *mrb, mrb_value self) +{ + mrb_int ms; + + mrb_get_args(mrb, "i", &ms); + + if (ms < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "time interval must be positive"); + } + + sleep_ms_impl(mrb, (uint32_t)ms); + + return mrb_nil_value(); +} + +static mrb_value +mrb_f_usleep(mrb_state *mrb, mrb_value self) +{ + mrb_int usec; + + mrb_get_args(mrb, "i", &usec); + + if (usec < 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "time interval must be positive"); + } + + sleep_us_impl(mrb, (uint32_t)usec); + + return mrb_fixnum_value(usec); +} + +/* + * Task class methods + */ + +static mrb_value +mrb_task_s_new(mrb_state *mrb, mrb_value self) +{ + mrb_value blk; + mrb_value name_val = mrb_nil_value(); + mrb_int priority = 128; /* Default middle priority */ + mrb_value kw_values[2] = {mrb_undef_value(), mrb_undef_value()}; + mrb_sym kw_names[2] = {mrb_intern_lit(mrb, "name"), mrb_intern_lit(mrb, "priority")}; + const mrb_kwargs kwargs = { + 2, 0, kw_names, kw_values, NULL + }; + + /* Get block and optional keyword arguments */ + mrb_get_args(mrb, "&:", &blk, &kwargs); + + if (mrb_nil_p(blk)) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "tried to create task without a block"); + } + + const struct RProc *proc = mrb_proc_ptr(blk); + + /* 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"); + } + priority = mrb_integer(kw_values[1]); + if (priority < 0 || priority > 255) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "priority must be 0-255"); + } + } + + /* 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(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); + + /* 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; +} + +static mrb_value +mrb_task_s_current(mrb_state *mrb, mrb_value self) +{ + /* Check if we're in root context */ + if (mrb->c == mrb->root_c) { + /* Return main task wrapper (lazy-allocate if needed) */ + if (!mrb->task.main_task) { + struct RClass *task_class = mrb_class_ptr(self); + struct RData *data = mrb_data_object_alloc(mrb, task_class, NULL, &mrb_task_type); + mrb_task *t = (mrb_task*)mrb_calloc(mrb, 1, sizeof(mrb_task)); + + /* Initialize as main task - special status that's never scheduled */ + t->priority = 0; + t->status = MRB_TASK_STATUS_RUNNING; /* Always running */ + t->name = mrb_str_new_cstr(mrb, "main"); + t->self = mrb_obj_value(data); + data->data = t; + data->type = &mrb_task_type; + + /* Register for GC protection */ + mrb_gc_register(mrb, t->self); + + /* Note: t->c is not used - root context is in mrb->root_c */ + mrb->task.main_task = t; + } + return mrb->task.main_task->self; + } + + /* Use pointer arithmetic to get task from context - O(1) */ + mrb_task *t = MRB2TASK(mrb); + return t->self; +} + +static mrb_value +mrb_task_s_list(mrb_state *mrb, mrb_value self) +{ + mrb_value ary = mrb_ary_new(mrb); + + /* Iterate all queues and collect tasks */ + for (int i = 0; i < MRB_NUM_TASK_QUEUE; i++) { + mrb_task *t = mrb->task.queues[i]; + while (t != NULL) { + mrb_ary_push(mrb, ary, t->self); + t = t->next; + } + } + + return ary; +} + +/* + * Run one task iteration - helper for Task.pass from root context + * Waits for ready tasks if needed (cooperative yielding) + */ +static void +task_run_one_iteration(mrb_state *mrb) +{ + mrb_task *t = q_ready_; + + /* No ready task - just return (sleep from root provides delays) */ + if (!t) { + return; + } + + /* Skip terminated tasks */ + if (t->status == MRB_TASK_STATUS_DORMANT || t->c.status == MRB_FIBER_TERMINATED) { + mrb_task_disable_irq(); + q_delete_task(mrb, t); + if (t->status != MRB_TASK_STATUS_DORMANT) { + t->status = MRB_TASK_STATUS_DORMANT; + q_insert_task(mrb, t); + } + mrb_task_enable_irq(); + return; + } + + /* Execute ready task */ + execute_task(mrb, t); +} + +static mrb_value +mrb_task_s_pass(mrb_state *mrb, mrb_value self) +{ + if (mrb->c == mrb->root_c) { + /* Called from root context - run one task iteration */ + task_run_one_iteration(mrb); + } + else { + /* Check for C function boundary - cannot yield from C function */ + mrb_callinfo *ci; + for (ci = mrb->c->ci; ci >= mrb->c->cibase; ci--) { + if (ci->cci > 0) { + mrb_raise(mrb, E_RUNTIME_ERROR, "can't pass across C function boundary"); + } + } + + /* In task context - trigger context switch */ + switching_ = TRUE; + } + + return mrb_nil_value(); +} + +/* Helper to build statistics for a task queue */ +static mrb_value +mrb_stat_sub(mrb_state *mrb, mrb_task *queue) +{ + mrb_value stat = mrb_hash_new(mrb); + mrb_value tasks = mrb_ary_new(mrb); + mrb_task *curr = queue; + int count = 0; + + /* Walk the queue and collect task objects */ + while (curr) { + count++; + mrb_ary_push(mrb, tasks, curr->self); + curr = curr->next; + } + + /* Build statistics hash */ + mrb_hash_set(mrb, stat, mrb_symbol_value(MRB_SYM(count)), mrb_fixnum_value(count)); + mrb_hash_set(mrb, stat, mrb_symbol_value(MRB_SYM(tasks)), tasks); + + return stat; +} + +static mrb_value +mrb_task_s_stat(mrb_state *mrb, mrb_value self) +{ + mrb_value data = mrb_hash_new(mrb); + + mrb_task_disable_irq(); + + /* Add global scheduler state */ + mrb_hash_set(mrb, data, mrb_symbol_value(MRB_SYM(tick)), mrb_fixnum_value(tick_)); + mrb_hash_set(mrb, data, mrb_symbol_value(MRB_SYM(wakeup_tick)), mrb_fixnum_value(wakeup_tick_)); + + /* Add statistics for each queue */ + mrb_hash_set(mrb, data, mrb_symbol_value(MRB_SYM(dormant)), mrb_stat_sub(mrb, q_dormant_)); + mrb_hash_set(mrb, data, mrb_symbol_value(MRB_SYM(ready)), mrb_stat_sub(mrb, q_ready_)); + mrb_hash_set(mrb, data, mrb_symbol_value(MRB_SYM(waiting)), mrb_stat_sub(mrb, q_waiting_)); + mrb_hash_set(mrb, data, mrb_symbol_value(MRB_SYM(suspended)), mrb_stat_sub(mrb, q_suspended_)); + + mrb_task_enable_irq(); + + return data; +} + +static mrb_value +mrb_task_s_run(mrb_state *mrb, mrb_value self) +{ + return mrb_task_run(mrb); +} + +static mrb_value +mrb_task_s_get(mrb_state *mrb, mrb_value self) +{ + mrb_value name; + + mrb_get_args(mrb, "o", &name); + + /* Search all queues for task with matching name */ + for (int i = 0; i < MRB_NUM_TASK_QUEUE; i++) { + mrb_task *t = mrb->task.queues[i]; + while (t != NULL) { + if (mrb_equal(mrb, t->name, name)) { + return t->self; + } + t = t->next; + } + } + + return mrb_nil_value(); +} + +/* + * Task instance methods + */ + +static mrb_value +mrb_task_status(mrb_state *mrb, mrb_value self) +{ + mrb_task *t; + + TASK_GET_PTR_OR_RAISE(t, self); + + /* Return status as symbol matching original implementation */ + return mrb_symbol_value( + (t->status == MRB_TASK_STATUS_RUNNING) ? MRB_SYM(RUNNING) : + (t->status == MRB_TASK_STATUS_READY) ? MRB_SYM(READY) : + (t->status == MRB_TASK_STATUS_WAITING) ? MRB_SYM(WAITING) : + (t->status == MRB_TASK_STATUS_SUSPENDED) ? MRB_SYM(SUSPENDED) : + (t->status == MRB_TASK_STATUS_DORMANT) ? MRB_SYM(DORMANT) : + MRB_SYM(UNKNOWN)); +} + +static mrb_value +mrb_task_inspect(mrb_state *mrb, mrb_value self) +{ + mrb_task *t; + char buf[256]; + const char *name_str; + const char *status_str; + + TASK_GET_PTR_OR_RAISE(t, self); + + /* Get status string directly from task status field */ + switch (t->status) { + case MRB_TASK_STATUS_RUNNING: + status_str = "RUNNING"; + break; + case MRB_TASK_STATUS_READY: + status_str = "READY"; + break; + case MRB_TASK_STATUS_WAITING: + status_str = "WAITING"; + break; + case MRB_TASK_STATUS_SUSPENDED: + status_str = "SUSPENDED"; + break; + case MRB_TASK_STATUS_DORMANT: + status_str = "DORMANT"; + break; + default: + status_str = "UNKNOWN"; + break; + } + + /* Get name as C string - avoid mrb_funcall to prevent VM state issues */ + if (mrb_string_p(t->name)) { + name_str = RSTRING_PTR(t->name); + } + else if (mrb_symbol_p(t->name)) { + name_str = mrb_sym_name(mrb, mrb_symbol(t->name)); + } + else { + /* Treat nil, undef, or any other type as unnamed */ + name_str = "(unnamed)"; + } + + /* Format: # */ + snprintf(buf, sizeof(buf), "#", + (void *)t, + name_str, + status_str); + + return mrb_str_new_cstr(mrb, buf); +} + +static mrb_value +mrb_task_name(mrb_state *mrb, mrb_value self) +{ + mrb_task *t; + + TASK_GET_PTR_OR_RAISE(t, self); + + /* Return "(noname)" if name is not set */ + if (mrb_nil_p(t->name)) { + return mrb_str_new_lit(mrb, "(noname)"); + } + + return t->name; +} + +static mrb_value +mrb_task_set_name(mrb_state *mrb, mrb_value self) +{ + mrb_task *t; + mrb_value name; + + TASK_GET_PTR_OR_RAISE(t, self); + + mrb_get_args(mrb, "o", &name); + t->name = name; + + return name; +} + +static mrb_value +mrb_task_priority(mrb_state *mrb, mrb_value self) +{ + mrb_task *t; + + TASK_GET_PTR_OR_RAISE(t, self); + + return mrb_fixnum_value(t->priority); +} + +static mrb_value +mrb_task_set_priority(mrb_state *mrb, mrb_value self) +{ + mrb_task *t; + mrb_int priority; + + TASK_GET_PTR_OR_RAISE(t, self); + + mrb_get_args(mrb, "i", &priority); + + if (priority < 0 || priority > 255) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "priority must be 0-255"); + } + + mrb_task_disable_irq(); + t->priority = (uint8_t)priority; + + /* 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_enable_irq(); + + return mrb_fixnum_value(priority); +} + +static mrb_value +mrb_task_suspend(mrb_state *mrb, mrb_value self) +{ + mrb_task *t; + + TASK_GET_PTR_OR_RAISE(t, self); + + /* Can only suspend ready or running tasks */ + if (t->status != MRB_TASK_STATUS_READY && t->status != MRB_TASK_STATUS_RUNNING) { + return self; + } + + task_change_state(mrb, t, MRB_TASK_STATUS_SUSPENDED); + + /* If suspending self, trigger context switch */ + if (t == q_ready_ || t->status == MRB_TASK_STATUS_RUNNING) { + switching_ = TRUE; + } + + return self; +} + +static mrb_value +mrb_task_resume(mrb_state *mrb, mrb_value self) +{ + mrb_task *t; + + TASK_GET_PTR_OR_RAISE(t, self); + + /* Can only resume suspended tasks */ + if (t->status != MRB_TASK_STATUS_SUSPENDED) { + return self; + } + + task_change_state(mrb, t, MRB_TASK_STATUS_READY); + + /* Trigger context switch if resumed task has higher priority */ + if (q_ready_ && q_ready_->status == MRB_TASK_STATUS_RUNNING) { + if (t->priority < q_ready_->priority) { + switching_ = TRUE; + } + } + + return self; +} + +static mrb_value +mrb_task_terminate(mrb_state *mrb, mrb_value self) +{ + mrb_task *t; + + TASK_GET_PTR_OR_RAISE(t, self); + + /* Don't terminate already dormant tasks */ + if (t->status == MRB_TASK_STATUS_DORMANT) { + return self; + } + + mrb_task_disable_irq(); + + /* Move to dormant queue */ + q_delete_task(mrb, t); + t->status = MRB_TASK_STATUS_DORMANT; + t->c.status = MRB_TASK_STOPPED; + q_insert_task(mrb, t); + + mrb_task_enable_irq(); + + /* Wake up tasks waiting on join */ + wake_up_join_waiters(mrb, t); + + /* If terminating self, trigger context switch */ + if (t == q_ready_) { + switching_ = TRUE; + } + + return self; +} + +static mrb_value +mrb_task_join(mrb_state *mrb, mrb_value self) +{ + mrb_task *t, *current; + + TASK_GET_PTR_OR_RAISE(t, self); + + /* Get current task using pointer arithmetic */ + if (mrb->c == mrb->root_c) { + mrb_raise(mrb, E_RUNTIME_ERROR, "join can only be called from running task"); + } + current = MRB2TASK(mrb); + + /* Can't join self */ + if (t == current) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "can't join self"); + } + + /* If task is already dormant, return immediately */ + if (t->status == MRB_TASK_STATUS_DORMANT) { + return t->state.result; + } + + /* Wait for task to complete */ + mrb_task_disable_irq(); + q_delete_task(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_enable_irq(); + + /* Trigger context switch */ + switching_ = TRUE; + + return t->state.result; +} + +/* + * Initialization + */ + +void +mrb_mruby_task_gem_init(mrb_state *mrb) +{ + struct RClass *task_class; + + /* Initialize HAL (timer and interrupts) */ + mrb_hal_task_init(mrb); + + /* Initialize main task to NULL */ + mrb->task.main_task = NULL; + + task_class = mrb_define_class(mrb, "Task", mrb->object_class); + MRB_SET_INSTANCE_TT(task_class, MRB_TT_DATA); + + /* Class methods */ + mrb_define_class_method_id(mrb, task_class, MRB_SYM(new), mrb_task_s_new, MRB_ARGS_BLOCK()); + mrb_define_class_method_id(mrb, task_class, MRB_SYM(current), mrb_task_s_current, MRB_ARGS_NONE()); + mrb_define_class_method_id(mrb, task_class, MRB_SYM(list), mrb_task_s_list, MRB_ARGS_NONE()); + mrb_define_class_method_id(mrb, task_class, MRB_SYM(pass), mrb_task_s_pass, MRB_ARGS_NONE()); + mrb_define_class_method_id(mrb, task_class, MRB_SYM(stat), mrb_task_s_stat, MRB_ARGS_NONE()); + mrb_define_class_method_id(mrb, task_class, MRB_SYM(get), mrb_task_s_get, MRB_ARGS_REQ(1)); + mrb_define_class_method_id(mrb, task_class, MRB_SYM(run), mrb_task_s_run, MRB_ARGS_NONE()); + + /* Instance methods */ + mrb_define_method_id(mrb, task_class, MRB_SYM(status), mrb_task_status, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, task_class, MRB_SYM(inspect), mrb_task_inspect, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, task_class, MRB_SYM(name), mrb_task_name, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, task_class, MRB_SYM_E(name), mrb_task_set_name, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, task_class, MRB_SYM(priority), mrb_task_priority, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, task_class, MRB_SYM_E(priority), mrb_task_set_priority, MRB_ARGS_REQ(1)); + mrb_define_method_id(mrb, task_class, MRB_SYM(suspend), mrb_task_suspend, MRB_ARGS_NONE()); + 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()); + + /* Kernel methods (module functions like CRuby) + * Note: sleep and usleep override mruby-sleep's implementation to be task-aware + * (cooperative sleep within tasks, blocking sleep otherwise) + */ + mrb_define_module_function_id(mrb, mrb->kernel_module, MRB_SYM(sleep), mrb_f_sleep, MRB_ARGS_OPT(1)); + mrb_define_module_function_id(mrb, mrb->kernel_module, MRB_SYM(usleep), mrb_f_usleep, MRB_ARGS_REQ(1)); + mrb_define_module_function_id(mrb, mrb->kernel_module, MRB_SYM(sleep_ms), mrb_f_sleep_ms, MRB_ARGS_REQ(1)); +} + +void +mrb_mruby_task_gem_final(mrb_state *mrb) +{ + /* Clear main task pointer - GC will handle freeing the object */ + if (mrb->task.main_task) { + mrb_gc_unregister(mrb, mrb->task.main_task->self); + mrb->task.main_task = NULL; + } + + mrb_hal_task_final(mrb); +} diff --git a/mrbgems/mruby-task/test/task.rb b/mrbgems/mruby-task/test/task.rb new file mode 100644 index 000000000..e93c525af --- /dev/null +++ b/mrbgems/mruby-task/test/task.rb @@ -0,0 +1,185 @@ +# Sleep/usleep tests (from mruby-sleep) +# Note: Use minimal sleep times to avoid test slowdown + +assert("sleep accepts non-negative values") do + assert_nothing_raised { sleep(0) } +end + +assert("sleep accepts non-negative float values") do + skip unless Object.const_defined?(:Float) + assert_nothing_raised { sleep(0.0) } + assert_nothing_raised { sleep(-0.0) } +end + +assert("sleep raises ArgumentError for negative integer") do + assert_raise(ArgumentError) { sleep(-1) } +end + +assert("sleep raises ArgumentError for negative float") do + skip unless Object.const_defined?(:Float) + assert_raise(ArgumentError) { sleep(-0.1) } +end + +assert("usleep accepts non-negative values") do + assert_nothing_raised { usleep(0) } +end + +assert("usleep raises ArgumentError for negative value") do + assert_raise(ArgumentError) { usleep(-100) } +end + +# Task creation tests + +assert("Task.new creates a task") do + task = Task.new { } + assert_kind_of Task, task +end + +assert("Task.new accepts name") do + task = Task.new(name: "test") { } + assert_equal "test", task.name +end + +assert("Task.new accepts priority") do + task = Task.new(priority: 100) { } + assert_equal 100, task.priority +end + +assert("Task.new raises without block") do + assert_raise(ArgumentError) { Task.new } +end + +# Task state tests + +assert("Task#status returns symbol") do + task = Task.new { } + status = task.status + assert_true [:READY, :RUNNING, :WAITING, :SUSPENDED, :DORMANT, :UNKNOWN].include?(status) +end + +assert("new task has READY status") do + task = Task.new { } + assert_equal :READY, task.status +end + +assert("Task#inspect returns formatted string") do + task = Task.new(name: "test") { } + inspect_str = task.inspect + assert_kind_of String, inspect_str + assert_true inspect_str.include?("Task") + assert_true inspect_str.include?("test") +end + +assert("Task#inspect shows status") do + task = Task.new { } + inspect_str = task.inspect + assert_true inspect_str.include?("READY") || inspect_str.include?("DORMANT") +end + +# Task control methods + +assert("Task#suspend doesn't raise") do + task = Task.new { } + assert_nothing_raised { task.suspend } +end + +assert("Task#resume doesn't raise") do + task = Task.new { } + assert_nothing_raised { task.resume } +end + +assert("Task#terminate doesn't raise") do + task = Task.new { } + assert_nothing_raised { task.terminate } +end + +# Task.current tests + +assert("Task.current in root context") do + # In root context, Task.current might be nil or a special value + current = Task.current + assert_true current.nil? || current.kind_of?(Task) +end + +# Task.pass tests + +assert("Task.pass yields control") do + assert_nothing_raised { Task.pass } +end + +# Task.stat tests + +assert("Task.stat returns hash") do + stat = Task.stat + assert_kind_of Hash, stat +end + +assert("Task.stat includes tick") do + stat = Task.stat + assert_true stat.has_key?(:tick) + assert_kind_of Integer, stat[:tick] +end + +assert("Task.stat includes wakeup_tick") do + stat = Task.stat + assert_true stat.has_key?(:wakeup_tick) + assert_kind_of Integer, stat[:wakeup_tick] +end + +assert("Task.stat includes queue counts") do + stat = Task.stat + [:ready, :waiting, :suspended, :dormant].each do |queue| + assert_true stat.has_key?(queue), "Missing queue: #{queue}" + assert_kind_of Hash, stat[queue] + assert_true stat[queue].has_key?(:count) + assert_kind_of Integer, stat[queue][:count] + assert_true stat[queue].has_key?(:tasks) + assert_kind_of Array, stat[queue][:tasks] + end +end + +assert("Task.stat tracks task counts") do + stat_before = Task.stat + ready_before = stat_before[:ready][:count] + + task1 = Task.new { sleep 0 } + task2 = Task.new { sleep 0 } + + stat_after = Task.stat + ready_after = stat_after[:ready][:count] + + assert_equal ready_before + 2, ready_after +end + +# Priority tests + +assert("Task.new accepts different priorities") do + low = Task.new(priority: 200) { } + high = Task.new(priority: 50) { } + med = Task.new(priority: 128) { } + + assert_equal 200, low.priority + assert_equal 50, high.priority + assert_equal 128, med.priority +end + +# Name handling + +assert("Task with string name") do + task = Task.new(name: "string_name") { } + assert_equal "string_name", task.name +end + +assert("Task without name returns (noname)") do + task = Task.new { } + assert_equal "(noname)", task.name +end + +# Edge cases + +assert("Task.new with block doesn't execute immediately") do + executed = false + task = Task.new { executed = true } + # Block should not execute until scheduler runs + assert_false executed +end diff --git a/mrbgems/mruby-test/driver.c b/mrbgems/mruby-test/driver.c index e62bc0147..fb7f10cf8 100644 --- a/mrbgems/mruby-test/driver.c +++ b/mrbgems/mruby-test/driver.c @@ -290,8 +290,9 @@ main(int argc, char **argv) /* new interpreter instance */ mrb = mrb_open(); - if (mrb == NULL) { - fputs("Invalid mrb_state, exiting test driver", stderr); + if (MRB_OPEN_FAILURE(mrb)) { + mrb_print_error(mrb); /* handles NULL */ + mrb_close(mrb); /* handles NULL */ return EXIT_FAILURE; } diff --git a/mrbgems/mruby-time/include/mruby/time.h b/mrbgems/mruby-time/include/mruby/time.h index 9149e0af2..349fc4d04 100644 --- a/mrbgems/mruby-time/include/mruby/time.h +++ b/mrbgems/mruby-time/include/mruby/time.h @@ -20,6 +20,7 @@ typedef enum mrb_timezone { } mrb_timezone; MRB_API mrb_value mrb_time_at(mrb_state *mrb, time_t sec, time_t usec, mrb_timezone timezone); +MRB_API struct tm* mrb_time_get_tm(mrb_state *mrb, mrb_value time); MRB_END_DECL diff --git a/mrbgems/mruby-time/src/time.c b/mrbgems/mruby-time/src/time.c index 6056fbd24..f6f9353fb 100644 --- a/mrbgems/mruby-time/src/time.c +++ b/mrbgems/mruby-time/src/time.c @@ -66,6 +66,11 @@ double round(double x) { /** Time class configuration */ +/* Platform detection for Windows variants */ +#if defined(_MSC_VER) && _MSC_VER < 1900 || defined(__MINGW64__) || defined(__MINGW32__) +#define MRB_TIME_WINDOWS_NO_STRFTIME_Z +#endif + /* gettimeofday(2) */ /* C99 does not have gettimeofday that is required to retrieve microseconds */ /* uncomment following macro on platforms without gettimeofday(2) */ @@ -110,26 +115,26 @@ double round(double x) { # define USE_CLOCK_GETTIME #endif -#if !defined(NO_GETTIMEOFDAY) -# if defined(_WIN32) && !defined(USE_CLOCK_GETTIME) -# define WIN32_LEAN_AND_MEAN /* don't include winsock.h */ -# include -# define gettimeofday my_gettimeofday +#if !defined(NO_GETTIMEOFDAY) && defined(_WIN32) && !defined(USE_CLOCK_GETTIME) +/* Windows gettimeofday polyfill */ +#define WIN32_LEAN_AND_MEAN /* don't include winsock.h */ +#include +#define gettimeofday my_gettimeofday -# ifdef _MSC_VER -# define UI64(x) x##ui64 -# else -# define UI64(x) x##ull -# endif +#ifdef _MSC_VER +# define UI64(x) x##ui64 +#else +# define UI64(x) x##ull +#endif typedef long suseconds_t; -# if (!defined __MINGW64__) && (!defined __MINGW32__) +#if (!defined __MINGW64__) && (!defined __MINGW32__) struct timeval { time_t tv_sec; suseconds_t tv_usec; }; -# endif +#endif /* * Polyfill for gettimeofday on Windows platforms that may not have it (e.g., older MSVC). @@ -156,9 +161,10 @@ gettimeofday(struct timeval *tv, void *tz) } return 0; } -# else -# include -# endif + +#elif !defined(NO_GETTIMEOFDAY) +/* Non-Windows platforms use standard sys/time.h */ +#include #endif #ifdef NO_GMTIME_R #define gmtime_r(t,r) gmtime(t) @@ -232,9 +238,9 @@ static const char wday_names[7][4] = { /* Consider defining DAYS_PER_WEEK = 7 if struct mrb_time { time_t sec; /* Seconds since the Epoch */ - time_t usec; /* Microsecond fraction of the second */ + time_t nsec; /* Nanosecond fraction of the second (0-999999999) */ enum mrb_timezone timezone; /* Timezone setting (MRB_TIMEZONE_UTC or MRB_TIMEZONE_LOCAL) */ - struct tm datetime; /* Cache for broken-down time based on sec, usec, and timezone. Updated by time_update_datetime. */ + struct tm datetime; /* Cache for broken-down time based on sec, nsec, and timezone. Updated by time_update_datetime. */ }; static const struct mrb_data_type time_type = { "Time", mrb_free }; /* mrb_free is the standard C free() */ @@ -269,9 +275,16 @@ fixable_time_t_p(time_t v) static void time_out_of_range(mrb_state *mrb, mrb_value obj) { - mrb_raisef(mrb, E_ARGUMENT_ERROR, "%v out of Time range", obj); + mrb_raisef(mrb, E_RANGE_ERROR, "%v out of Time range", obj); } +static mrb_noreturn void +time_uninitialized(mrb_state *mrb) +{ + mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized Time"); +} + + #ifndef MRB_NO_FLOAT static time_t mrb_time_t_from_float(mrb_state *mrb, mrb_value obj, time_t *usec) @@ -383,7 +396,7 @@ time_value_from_time_t(mrb_state *mrb, time_t t) #elif !defined(MRB_NO_FLOAT) return mrb_float_value(mrb, (mrb_float)t); #else - mrb_raisef(mrb, E_ARGUMENT_ERROR, "Time too big"); + mrb_raise(mrb, E_RANGE_ERROR, "Time out of range"); #endif } return mrb_int_value(mrb, (mrb_int)t); @@ -431,30 +444,29 @@ time_wrap(mrb_state *mrb, struct RClass *tc, struct mrb_time *tm) /* Allocates a mrb_time object and initializes it. */ static struct mrb_time* -time_alloc_time(mrb_state *mrb, time_t sec, time_t usec, enum mrb_timezone timezone) +time_alloc_time(mrb_state *mrb, time_t sec, time_t nsec, enum mrb_timezone timezone) { struct mrb_time *time_obj = (struct mrb_time*)mrb_malloc(mrb, sizeof(struct mrb_time)); time_obj->sec = sec; - time_obj->usec = usec; + time_obj->nsec = nsec; - /* Normalize seconds and microseconds. */ - /* This is only necessary if time_t is signed and usec is negative. */ - if (!MRB_TIME_T_UINT && time_obj->usec < 0) { + /* Normalize seconds and nanoseconds. */ + /* This is only necessary if time_t is signed and nsec is negative. */ + if (!MRB_TIME_T_UINT && time_obj->nsec < 0) { /* - * If usec is negative, adjust seconds downwards. + * If nsec is negative, adjust seconds downwards. * NDIV calculates division rounded towards negative infinity. - * For example, NDIV(-1, USECS_PER_SEC) is -1, so 1 second is subtracted. - * NDIV(-1000001, USECS_PER_SEC) is -2, so 2 seconds are subtracted. + * For example, NDIV(-1, 1000000000) is -1, so 1 second is subtracted. */ - long sec_adjustment = (long)NDIV(time_obj->usec, USECS_PER_SEC); - time_obj->usec -= sec_adjustment * USECS_PER_SEC; /* Becomes positive or zero */ + long sec_adjustment = (long)NDIV(time_obj->nsec, 1000000000L); + time_obj->nsec -= sec_adjustment * 1000000000L; /* Becomes positive or zero */ time_obj->sec += sec_adjustment; } - /* Handle positive microsecond overflow. */ - else if (time_obj->usec >= USECS_PER_SEC) { - /* If usec is USECS_PER_SEC or more, adjust seconds upwards. */ - long sec_adjustment = (long)(time_obj->usec / USECS_PER_SEC); - time_obj->usec -= sec_adjustment * USECS_PER_SEC; /* Reduce to < USECS_PER_SEC */ + /* Handle positive nanosecond overflow. */ + else if (time_obj->nsec >= 1000000000L) { + /* If nsec is 1000000000 or more, adjust seconds upwards. */ + long sec_adjustment = (long)(time_obj->nsec / 1000000000L); + time_obj->nsec -= sec_adjustment * 1000000000L; /* Reduce to < 1000000000 */ time_obj->sec += sec_adjustment; } time_obj->timezone = timezone; @@ -477,7 +489,7 @@ time_alloc(mrb_state *mrb, mrb_value sec, mrb_value usec, enum mrb_timezone time tsec = mrb_to_time_t(mrb, sec, &tusec); tusec += mrb_to_time_t(mrb, usec, NULL); - return time_alloc_time(mrb, tsec, tusec, timezone); + return time_alloc_time(mrb, tsec, tusec * NSECS_PER_USEC, timezone); } /* @@ -513,21 +525,21 @@ static struct mrb_time* current_mrb_time(mrb_state *mrb) { struct mrb_time tmzero = {0}; /* Used to initialize the new mrb_time struct */ - time_t sec, usec; + time_t sec, nsec; #if defined(TIME_UTC) && !defined(__ANDROID__) { struct timespec ts; timespec_get(&ts, TIME_UTC); sec = ts.tv_sec; - usec = ts.tv_nsec / NSECS_PER_USEC; + nsec = ts.tv_nsec; /* Full nanosecond precision preserved */ } #elif defined(USE_CLOCK_GETTIME) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); sec = ts.tv_sec; - usec = ts.tv_nsec / NSECS_PER_USEC; + nsec = ts.tv_nsec; /* Full nanosecond precision preserved */ } #elif defined(NO_GETTIMEOFDAY) { @@ -545,7 +557,7 @@ current_mrb_time(mrb_state *mrb) */ last_usec += 1; } - usec = last_usec; + nsec = last_usec * NSECS_PER_USEC; /* Convert fake microseconds to nanoseconds */ } #else { @@ -553,13 +565,13 @@ current_mrb_time(mrb_state *mrb) gettimeofday(&tv, NULL); sec = tv.tv_sec; - usec = tv.tv_usec; + nsec = tv.tv_usec * NSECS_PER_USEC; /* Convert microseconds to nanoseconds */ } #endif struct mrb_time *tm = (struct mrb_time*)mrb_malloc(mrb, sizeof(*tm)); *tm = tmzero; - tm->sec = sec; tm->usec = usec; + tm->sec = sec; tm->nsec = nsec; tm->timezone = MRB_TIMEZONE_LOCAL; time_update_datetime(mrb, tm, TRUE); @@ -584,7 +596,7 @@ time_now(mrb_state *mrb, mrb_value self) MRB_API mrb_value mrb_time_at(mrb_state *mrb, time_t sec, time_t usec, enum mrb_timezone zone) { - return time_make_time(mrb, mrb_class_get_id(mrb, MRB_SYM(Time)), sec, usec, zone); + return time_make_time(mrb, mrb_class_get_id(mrb, MRB_SYM(Time)), sec, usec * NSECS_PER_USEC, zone); } /* @@ -660,23 +672,25 @@ time_mktime(mrb_state *mrb, mrb_int ayear, mrb_int amonth, mrb_int aday, time_t nowsecs = (*mk)(&nowtime); /* - * Handle mktime/timegm failure: - * If mk() returns -1, it usually indicates an error or an out-of-range date. - * A special case is when the time is exactly one second before the epoch (Epoch-1). - * Some mktime implementations might return -1 for this valid time. - * The code tries to detect this by adding one second to tm_sec and calling mk() again. - * If the result is 0 (Epoch), then the original time was indeed Epoch-1. + * Handle mktime/timegm failure (returns -1): + * This could mean either: + * 1. Invalid date/time arguments, OR + * 2. Valid time exactly one second before Unix epoch (1969-12-31 23:59:59) + * + * To distinguish: increment seconds and test again. + * If result is 0 (epoch), original was valid epoch-1. + * Otherwise, original arguments were invalid. */ if (nowsecs == (time_t)-1) { - nowtime.tm_sec += 1; /* Check if it was Epoch-1 by trying Epoch */ - nowsecs = (*mk)(&nowtime); /* Call mktime/timegm again */ - if (nowsecs != 0) { /* If it's not Epoch, then the original time was invalid */ - mrb_raise(mrb, E_ARGUMENT_ERROR, "Not a valid time"); + struct tm test_tm = nowtime; + test_tm.tm_sec += 1; + if ((*mk)(&test_tm) != 0) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid time"); } - nowsecs = (time_t)-1; /* Reset to Epoch-1, which is a valid time_t */ + /* Original time was valid epoch-1, keep nowsecs = -1 */ } - return time_alloc_time(mrb, nowsecs, ausec, timezone); + return time_alloc_time(mrb, nowsecs, ausec * NSECS_PER_USEC, timezone); } /* @@ -734,11 +748,19 @@ time_get_ptr(mrb_state *mrb, mrb_value time) { struct mrb_time *tm = DATA_GET_PTR(mrb, time, &time_type, struct mrb_time); if (!tm) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized time"); + time_uninitialized(mrb); } return tm; } +MRB_API struct tm* +mrb_time_get_tm(mrb_state *mrb, mrb_value time) +{ + struct mrb_time *tm = time_get_ptr(mrb, time); + time_update_datetime(mrb, tm, FALSE); + return &tm->datetime; +} + /* * call-seq: * time == other_time -> true or false @@ -758,7 +780,7 @@ time_eq(mrb_state *mrb, mrb_value self) mrb_value other = mrb_get_arg1(mrb); struct mrb_time *tm1 = DATA_GET_PTR(mrb, self, &time_type, struct mrb_time); struct mrb_time *tm2 = DATA_CHECK_GET_PTR(mrb, other, &time_type, struct mrb_time); - mrb_bool eq_p = tm1 && tm2 && tm1->sec == tm2->sec && tm1->usec == tm2->usec; + mrb_bool eq_p = tm1 && tm2 && tm1->sec == tm2->sec && tm1->nsec == tm2->nsec; return mrb_bool_value(eq_p); } @@ -792,10 +814,10 @@ time_cmp(mrb_state *mrb, mrb_value self) return mrb_fixnum_value(-1); } /* tm1->sec == tm2->sec */ - if (tm1->usec > tm2->usec) { + if (tm1->nsec > tm2->nsec) { return mrb_fixnum_value(1); } - else if (tm1->usec < tm2->usec) { + else if (tm1->nsec < tm2->nsec) { return mrb_fixnum_value(-1); } return mrb_fixnum_value(0); @@ -804,7 +826,7 @@ time_cmp(mrb_state *mrb, mrb_value self) static mrb_noreturn void int_overflow(mrb_state *mrb, const char *reason) { - mrb_raisef(mrb, E_RANGE_ERROR, "time_t overflow in Time %s", reason); + mrb_raisef(mrb, E_RANGE_ERROR, "Time out of range in %s", reason); } /* @@ -849,7 +871,7 @@ time_plus(mrb_state *mrb, mrb_value self) } sec = tm->sec + sec; /* Perform the addition */ #endif - return time_make_time(mrb, mrb_obj_class(mrb, self), sec, tm->usec+usec, tm->timezone); + return time_make_time(mrb, mrb_obj_class(mrb, self), sec, tm->nsec + usec * NSECS_PER_USEC, tm->timezone); } /* @@ -877,12 +899,11 @@ time_minus(mrb_state *mrb, mrb_value self) #ifndef MRB_NO_FLOAT mrb_float f; f = (mrb_float)(tm->sec - tm2->sec) - + (mrb_float)(tm->usec - tm2->usec) / USECS_PER_SEC_F; + + (mrb_float)(tm->nsec - tm2->nsec) / 1.0e9; return mrb_float_value(mrb, f); #else - mrb_int f; - f = tm->sec - tm2->sec; - if (tm->usec < tm2->usec) f--; + mrb_int f = tm->sec - tm2->sec; + if (tm->nsec < tm2->nsec) f--; return mrb_int_value(mrb, f); #endif } @@ -911,7 +932,7 @@ time_minus(mrb_state *mrb, mrb_value self) } sec = tm->sec - sec; /* Perform the subtraction */ #endif - return time_make_time(mrb, mrb_obj_class(mrb, self), sec, tm->usec-usec, tm->timezone); + return time_make_time(mrb, mrb_obj_class(mrb, self), sec, tm->nsec - usec * NSECS_PER_USEC, tm->timezone); } } @@ -968,7 +989,7 @@ time_year(mrb_state *mrb, mrb_value self) static size_t time_zonename(mrb_state *mrb, struct mrb_time *tm, char *buf, size_t len) { -#if defined(_MSC_VER) && _MSC_VER < 1900 || defined(__MINGW64__) || defined(__MINGW32__) +#ifdef MRB_TIME_WINDOWS_NO_STRFTIME_Z /* * On some Windows versions (specifically with MSC_VER < 1900, i.e., pre-VS2015, or MinGW), * strftime's "%z" (timezone offset) specifier might not be available or reliable. @@ -978,7 +999,10 @@ time_zonename(mrb_state *mrb, struct mrb_time *tm, char *buf, size_t len) time_t utc_sec = timegm(&tm->datetime); /* Convert current datetime (interpreted as UTC) to time_t */ /* Calculate offset in minutes: difference between this UTC time_t and the stored local time_t */ int offset = abs((int)(utc_sec - tm->sec) / SECS_PER_MIN); - datetime.tm_year = 100; /* Arbitrary year for strftime, not relevant to offset display (e.g. Y2K bug-like) */ + /* Copy actual date components for accurate timezone/DST calculation */ + datetime.tm_year = tm->datetime.tm_year; + datetime.tm_mon = tm->datetime.tm_mon; + datetime.tm_mday = tm->datetime.tm_mday; datetime.tm_hour = offset / MINS_PER_HOUR; /* Convert offset to hours and minutes */ datetime.tm_min = offset % MINS_PER_HOUR; buf[0] = utc_sec < tm->sec ? '-' : '+'; /* Determine sign of the offset */ @@ -1104,8 +1128,10 @@ time_getutc(mrb_state *mrb, mrb_value self) struct mrb_time *tm = time_get_ptr(mrb, self); struct mrb_time *tm2 = (struct mrb_time*)mrb_malloc(mrb, sizeof(*tm)); *tm2 = *tm; - tm2->timezone = MRB_TIMEZONE_UTC; - time_update_datetime(mrb, tm2, TRUE); + if (tm2->timezone != MRB_TIMEZONE_UTC) { + tm2->timezone = MRB_TIMEZONE_UTC; + time_update_datetime(mrb, tm2, TRUE); + } return time_wrap(mrb, mrb_obj_class(mrb, self), tm2); } @@ -1126,8 +1152,10 @@ time_getlocal(mrb_state *mrb, mrb_value self) struct mrb_time *tm = time_get_ptr(mrb, self); struct mrb_time *tm2 = (struct mrb_time*)mrb_malloc(mrb, sizeof(*tm)); *tm2 = *tm; - tm2->timezone = MRB_TIMEZONE_LOCAL; - time_update_datetime(mrb, tm2, TRUE); + if (tm2->timezone != MRB_TIMEZONE_LOCAL) { + tm2->timezone = MRB_TIMEZONE_LOCAL; + time_update_datetime(mrb, tm2, TRUE); + } return time_wrap(mrb, mrb_obj_class(mrb, self), tm2); } @@ -1211,7 +1239,7 @@ time_init_copy(mrb_state *mrb, mrb_value copy) struct mrb_time *t2 = (struct mrb_time*)DATA_PTR(src); if (!t2) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized time"); + time_uninitialized(mrb); } if (!t1) { t1 = (struct mrb_time*)mrb_malloc(mrb, sizeof(struct mrb_time)); @@ -1241,22 +1269,6 @@ time_localtime(mrb_state *mrb, mrb_value self) return self; } -/* - * call-seq: - * time.mday -> integer - * - * Returns the day of the month (1-31) of the time. - * This is an alias for time.day. - * - * Time.local(2023, 12, 25).mday #=> 25 - * Time.local(2023, 1, 1).mday #=> 1 - */ -static mrb_value -time_mday(mrb_state *mrb, mrb_value self) -{ - struct mrb_time *tm = time_get_ptr(mrb, self); - return mrb_fixnum_value(tm->datetime.tm_mday); -} /* * call-seq: @@ -1327,7 +1339,7 @@ static mrb_value time_to_f(mrb_state *mrb, mrb_value self) { struct mrb_time *tm = time_get_ptr(mrb, self); - return mrb_float_value(mrb, (mrb_float)tm->sec + (mrb_float)tm->usec/USECS_PER_SEC_F); + return mrb_float_value(mrb, (mrb_float)tm->sec + (mrb_float)tm->nsec/1.0e9); } #endif @@ -1363,7 +1375,25 @@ static mrb_value time_usec(mrb_state *mrb, mrb_value self) { struct mrb_time *tm = time_get_ptr(mrb, self); - return mrb_fixnum_value((mrb_int)tm->usec); + return mrb_fixnum_value((mrb_int)(tm->nsec / NSECS_PER_USEC)); +} + +/* + * call-seq: + * time.nsec -> integer + * time.tv_nsec -> integer + * + * Returns the nanosecond component (0-999999999) of the time. + * + * Time.at(1000000000, 123456).nsec #=> 123456000 + * Time.at(1000000000.123456789).nsec #=> 123456789 + * Time.at(1000000000).nsec #=> 0 + */ +static mrb_value +time_nsec(mrb_state *mrb, mrb_value self) +{ + struct mrb_time *tm = time_get_ptr(mrb, self); + return mrb_fixnum_value((mrb_int)tm->nsec); } /* @@ -1427,8 +1457,14 @@ time_to_s(mrb_state *mrb, mrb_value self) len = strftime(buf, sizeof(buf), TO_S_FMT "UTC", &tm->datetime); } else { +#ifdef MRB_TIME_WINDOWS_NO_STRFTIME_Z + /* Use two-step approach on Windows platforms without reliable %z support */ len = strftime(buf, sizeof(buf), TO_S_FMT, &tm->datetime); len += time_zonename(mrb, tm, buf+len, sizeof(buf)-len); +#else + /* Use combined format string on platforms with %z support */ + len = strftime(buf, sizeof(buf), TO_S_FMT "%z", &tm->datetime); +#endif } mrb_value str = mrb_str_new(mrb, buf, len); RSTR_SET_ASCII_FLAG(mrb_str_ptr(str)); @@ -1451,11 +1487,21 @@ time_hash(mrb_state *mrb, mrb_value self) { struct mrb_time *tm = time_get_ptr(mrb, self); uint32_t hash = mrb_byte_hash((uint8_t*)&tm->sec, sizeof(time_t)); - hash = mrb_byte_hash_step((uint8_t*)&tm->usec, sizeof(time_t), hash); + hash = mrb_byte_hash_step((uint8_t*)&tm->nsec, sizeof(time_t), hash); hash = mrb_byte_hash_step((uint8_t*)&tm->timezone, sizeof(tm->timezone), hash); return mrb_int_value(mrb, hash); } +/* + * Generic function for weekday checks. Used by all weekday methods. + */ +static mrb_value +time_wday_p(mrb_state *mrb, mrb_value self, int target_wday) +{ + struct mrb_time *tm = time_get_ptr(mrb, self); + return mrb_bool_value(tm->datetime.tm_wday == target_wday); +} + /* * call-seq: * time.sunday? -> true or false @@ -1468,8 +1514,7 @@ time_hash(mrb_state *mrb, mrb_value self) static mrb_value time_sunday(mrb_state *mrb, mrb_value self) { - struct mrb_time *tm = time_get_ptr(mrb, self); - return mrb_bool_value(tm->datetime.tm_wday == 0); + return time_wday_p(mrb, self, 0); } /* @@ -1484,8 +1529,7 @@ time_sunday(mrb_state *mrb, mrb_value self) static mrb_value time_monday(mrb_state *mrb, mrb_value self) { - struct mrb_time *tm = time_get_ptr(mrb, self); - return mrb_bool_value(tm->datetime.tm_wday == 1); + return time_wday_p(mrb, self, 1); } /* @@ -1500,8 +1544,7 @@ time_monday(mrb_state *mrb, mrb_value self) static mrb_value time_tuesday(mrb_state *mrb, mrb_value self) { - struct mrb_time *tm = time_get_ptr(mrb, self); - return mrb_bool_value(tm->datetime.tm_wday == 2); + return time_wday_p(mrb, self, 2); } /* @@ -1516,8 +1559,7 @@ time_tuesday(mrb_state *mrb, mrb_value self) static mrb_value time_wednesday(mrb_state *mrb, mrb_value self) { - struct mrb_time *tm = time_get_ptr(mrb, self); - return mrb_bool_value(tm->datetime.tm_wday == 3); + return time_wday_p(mrb, self, 3); } /* @@ -1532,8 +1574,7 @@ time_wednesday(mrb_state *mrb, mrb_value self) static mrb_value time_thursday(mrb_state *mrb, mrb_value self) { - struct mrb_time *tm = time_get_ptr(mrb, self); - return mrb_bool_value(tm->datetime.tm_wday == 4); + return time_wday_p(mrb, self, 4); } /* @@ -1548,8 +1589,7 @@ time_thursday(mrb_state *mrb, mrb_value self) static mrb_value time_friday(mrb_state *mrb, mrb_value self) { - struct mrb_time *tm = time_get_ptr(mrb, self); - return mrb_bool_value(tm->datetime.tm_wday == 5); + return time_wday_p(mrb, self, 5); } /* @@ -1563,9 +1603,41 @@ time_friday(mrb_state *mrb, mrb_value self) */ static mrb_value time_saturday(mrb_state *mrb, mrb_value self) +{ + return time_wday_p(mrb, self, 6); +} + +/* + * ISO 15.2.19.7.12 + * ISO 15.2.19.7.14 + * ISO 15.2.19.7.29 + */ +/* + * call-seq: + * time.gmt_offset -> integer + * time.utc_offset -> integer + * time.gmtoff -> integer + * + * Returns the offset in seconds between the timezone of time and UTC. + * + * Time.local(2000, 1, 1).gmt_offset #=> 32400 (JST: UTC+9) + * Time.utc(2000, 1, 1).utc_offset #=> 0 (UTC) + * Time.local(2000, 7, 1).gmtoff #=> 32400 (or 28800 if DST) + */ +static mrb_value +time_utc_offset(mrb_state *mrb, mrb_value self) { struct mrb_time *tm = time_get_ptr(mrb, self); - return mrb_bool_value(tm->datetime.tm_wday == 6); + + if (tm->timezone == MRB_TIMEZONE_UTC) { + return mrb_fixnum_value(0); /* UTC is always offset 0 */ + } + + /* For local times, calculate offset = local_time_t - utc_time_t */ + time_t utc_time_t = timegm(&tm->datetime); /* Convert datetime as UTC */ + mrb_int offset_seconds = (mrb_int)(tm->sec - utc_time_t); + + return mrb_fixnum_value(offset_seconds); } void @@ -1611,7 +1683,7 @@ mrb_mruby_time_gem_init(mrb_state* mrb) mrb_define_method_id(mrb, tc, MRB_SYM(gmtime), time_utc , MRB_ARGS_NONE()); /* 15.2.19.7.13 */ mrb_define_method_id(mrb, tc, MRB_SYM(hour), time_hour, MRB_ARGS_NONE()); /* 15.2.19.7.15 */ mrb_define_method_id(mrb, tc, MRB_SYM(localtime), time_localtime, MRB_ARGS_NONE()); /* 15.2.19.7.18 */ - mrb_define_method_id(mrb, tc, MRB_SYM(mday), time_mday, MRB_ARGS_NONE()); /* 15.2.19.7.19 */ + mrb_define_method_id(mrb, tc, MRB_SYM(mday), time_day, MRB_ARGS_NONE()); /* 15.2.19.7.19 */ mrb_define_method_id(mrb, tc, MRB_SYM(min), time_min, MRB_ARGS_NONE()); /* 15.2.19.7.20 */ mrb_define_method_id(mrb, tc, MRB_SYM(mon), time_mon, MRB_ARGS_NONE()); /* 15.2.19.7.21 */ @@ -1623,6 +1695,8 @@ mrb_mruby_time_gem_init(mrb_state* mrb) mrb_define_method_id(mrb, tc, MRB_SYM(to_f), time_to_f, MRB_ARGS_NONE()); /* 15.2.19.7.24 */ #endif mrb_define_method_id(mrb, tc, MRB_SYM(usec), time_usec, MRB_ARGS_NONE()); /* 15.2.19.7.26 */ + mrb_define_method_id(mrb, tc, MRB_SYM(nsec), time_nsec, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, tc, MRB_SYM(tv_nsec), time_nsec, MRB_ARGS_NONE()); mrb_define_method_id(mrb, tc, MRB_SYM(utc), time_utc, MRB_ARGS_NONE()); /* 15.2.19.7.27 */ mrb_define_method_id(mrb, tc, MRB_SYM_Q(utc), time_utc_p,MRB_ARGS_NONE()); /* 15.2.19.7.28 */ mrb_define_method_id(mrb, tc, MRB_SYM(wday), time_wday, MRB_ARGS_NONE()); /* 15.2.19.7.30 */ @@ -1641,12 +1715,9 @@ mrb_mruby_time_gem_init(mrb_state* mrb) mrb_define_method_id(mrb, tc, MRB_SYM_Q(friday), time_friday, MRB_ARGS_NONE()); mrb_define_method_id(mrb, tc, MRB_SYM_Q(saturday), time_saturday, MRB_ARGS_NONE()); - /* - methods not available: - gmt_offset(15.2.19.7.12) - gmtoff(15.2.19.7.14) - utc_offset(15.2.19.7.29) - */ + mrb_define_method_id(mrb, tc, MRB_SYM(gmt_offset), time_utc_offset, MRB_ARGS_NONE()); /* 15.2.19.7.12 */ + mrb_define_method_id(mrb, tc, MRB_SYM(gmtoff), time_utc_offset, MRB_ARGS_NONE()); /* 15.2.19.7.14 */ + mrb_define_method_id(mrb, tc, MRB_SYM(utc_offset), time_utc_offset, MRB_ARGS_NONE()); /* 15.2.19.7.29 */ } void diff --git a/mrbgems/mruby-time/test/time.rb b/mrbgems/mruby-time/test/time.rb index 2c3a354f4..58e1363b1 100644 --- a/mrbgems/mruby-time/test/time.rb +++ b/mrbgems/mruby-time/test/time.rb @@ -219,8 +219,52 @@ assert('Time#utc?', '15.2.19.7.28') do assert_predicate(Time.at(1300000000).utc, :utc?) end -# ATM not implemented -# assert('Time#utc_offset', '15.2.19.7.29') do +assert('Time#utc_offset, #gmt_offset, #gmtoff', '15.2.19.7.12, 15.2.19.7.14, 15.2.19.7.29') do + # UTC times should have zero offset + utc_time = Time.utc(2000, 1, 1) + assert_equal(0, utc_time.utc_offset) + + # Local times should return integer offsets in seconds + local_time = Time.local(2000, 1, 1) + assert_kind_of(Integer, local_time.utc_offset) + + # Test that offset values make sense (should be multiple of 900 seconds = 15 minutes) + assert_equal(0, local_time.utc_offset % 900) + + # All three methods should be aliases returning identical values + assert_equal(utc_time.utc_offset, utc_time.gmt_offset) + assert_equal(utc_time.utc_offset, utc_time.gmtoff) + assert_equal(local_time.utc_offset, local_time.gmt_offset) + assert_equal(local_time.utc_offset, local_time.gmtoff) +end + +assert('Time#nsec, #tv_nsec') do + # Test nanosecond methods exist and return integers + t = Time.now + assert_kind_of(Integer, t.nsec) + assert_kind_of(Integer, t.tv_nsec) + + # nsec and tv_nsec should be aliases + assert_equal(t.nsec, t.tv_nsec) + + # Nanoseconds should be in valid range (0-999999999) + assert_operator(t.nsec, :>=, 0) + assert_operator(t.nsec, :<=, 999999999) + + # Test with Time.at using microseconds + t1 = Time.at(1000000000, 123456) + assert_equal(123456000, t1.nsec) # 123456 usec = 123456000 nsec + assert_equal(123456, t1.usec) # usec should still work + + # Test that usec == nsec/1000 + assert_equal(t1.usec, t1.nsec / 1000) + + # Test nanosecond precision in comparisons + t2 = Time.at(1000000000, 123457) + assert_equal(123457000, t2.nsec) + assert_not_equal(t1, t2) # Different nanoseconds should not be equal + assert_operator(t1, :<, t2) # t1 should be less than t2 +end assert('Time#wday', '15.2.19.7.30') do assert_equal(0, Time.gm(2012, 12, 23).wday) diff --git a/mrbgems/mruby-toplevel-ext/mrblib/toplevel.rb b/mrbgems/mruby-toplevel-ext/mrblib/toplevel.rb index e5fc6239f..688d78afe 100644 --- a/mrbgems/mruby-toplevel-ext/mrblib/toplevel.rb +++ b/mrbgems/mruby-toplevel-ext/mrblib/toplevel.rb @@ -1,6 +1,23 @@ class < self + # + # Invokes Module.append_features on each parameter in reverse order. + # When called at the toplevel, this includes the module(s) into Object, + # making their methods available to all objects. + # + # module Greeting + # def hello + # "Hello, world!" + # end + # end + # + # include Greeting + # "".hello #=> "Hello, world!" + # def include(*modules) Object.include(*modules) end diff --git a/mrblib/array.rb b/mrblib/array.rb index 5c1a9a89f..937b5d1be 100644 --- a/mrblib/array.rb +++ b/mrblib/array.rb @@ -8,7 +8,7 @@ class Array # array.each {|element| ... } -> self # array.each -> Enumerator # - # Calls the given block for each element of +self+ + # Calls the given block for each element of `self` # and pass the respective element. # # ISO 15.2.12.5.10 @@ -28,7 +28,7 @@ class Array # array.each_index {|index| ... } -> self # array.each_index -> Enumerator # - # Calls the given block for each element of +self+ + # Calls the given block for each element of `self` # and pass the index of the respective element. # # ISO 15.2.12.5.11 @@ -48,7 +48,7 @@ class Array # array.collect! {|element| ... } -> self # array.collect! -> new_enumerator # - # Calls the given block for each element of +self+ + # Calls the given block for each element of `self` # and pass the respective element. Each element will # be replaced by the resulting values. # @@ -80,7 +80,7 @@ class Array # array.sort -> new_array # array.sort {|a, b| ... } -> new_array # - # Returns a new Array whose elements are those from +self+, sorted. + # Returns a new Array whose elements are those from `self`, sorted. def sort(&block) self.dup.sort!(&block) end diff --git a/mrblib/compar.rb b/mrblib/compar.rb index 2ac70ac6e..4ca0b6748 100644 --- a/mrblib/compar.rb +++ b/mrblib/compar.rb @@ -8,8 +8,8 @@ module Comparable # call-seq: # obj < other -> true or false # - # Return true if +self+ is less - # than +other+. Otherwise return + # Return true if `self` is less + # than `other`. Otherwise return # false. # # ISO 15.3.3.2.1 @@ -25,8 +25,8 @@ module Comparable # call-seq: # obj <= other -> true or false # - # Return true if +self+ is less - # than or equal to +other+. + # Return true if `self` is less + # than or equal to `other`. # Otherwise return false. # # ISO 15.3.3.2.2 @@ -42,8 +42,8 @@ module Comparable # call-seq: # obj == other -> true or false # - # Return true if +self+ is equal - # to +other+. Otherwise return + # Return true if `self` is equal + # to `other`. Otherwise return # false. # # ISO 15.3.3.2.3 @@ -56,8 +56,8 @@ module Comparable # call-seq: # obj > other -> true or false # - # Return true if +self+ is greater - # than +other+. Otherwise return + # Return true if `self` is greater + # than `other`. Otherwise return # false. # # ISO 15.3.3.2.4 @@ -73,8 +73,8 @@ module Comparable # call-seq: # obj >= other -> true or false # - # Return true if +self+ is greater - # than or equal to +other+. + # Return true if `self` is greater + # than or equal to `other`. # Otherwise return false. # # ISO 15.3.3.2.5 @@ -90,9 +90,9 @@ module Comparable # call-seq: # obj.between?(min,max) -> true or false # - # Return true if +self+ is greater - # than or equal to +min+ and - # less than or equal to +max+. + # Return true if `self` is greater + # than or equal to `min` and + # less than or equal to `max`. # Otherwise return false. # # ISO 15.3.3.2.6 diff --git a/mrblib/enum.rb b/mrblib/enum.rb index 23b275ae2..e4f8973dd 100644 --- a/mrblib/enum.rb +++ b/mrblib/enum.rb @@ -1,7 +1,7 @@ ## # Enumerable # -# The Enumerable mixin provides collection classes with +# The `Enumerable` mixin provides collection classes with # several traversal and searching methods, and with the ability to # sort. The class must provide a method `each`, which # yields successive members of the collection. If @@ -17,10 +17,10 @@ module Enumerable ## # Call the given block for each element - # which is yield by +each+. Return false + # which is yield by `each`. Return false # if one block value is false. Otherwise # return true. If no block is given and - # +self+ is false return false. + # `self` is false return false. # # ISO 15.3.2.2.1 def all?(&block) @@ -34,10 +34,10 @@ module Enumerable ## # Call the given block for each element - # which is yield by +each+. Return true + # which is yield by `each`. Return true # if one block value is true. Otherwise # return false. If no block is given and - # +self+ is true object return true. + # `self` is true object return true. # # ISO 15.3.2.2.2 def any?(&block) @@ -51,7 +51,7 @@ module Enumerable ## # Call the given block for each element - # which is yield by +each+. Append all + # which is yield by `each`. Append all # values of each block together and # return this value. # @@ -67,9 +67,9 @@ module Enumerable ## # Return the first element for which # value from the block is true. If no - # object matches, calls +ifnone+ and + # object matches, calls `ifnone` and # returns its result. Otherwise returns - # +nil+. + # `nil`. # # ISO 15.3.2.2.4 def detect(ifnone=nil, &block) @@ -85,7 +85,7 @@ module Enumerable ## # Call the given block for each element - # which is yield by +each+. Pass an + # which is yield by `each`. Pass an # index to the block which starts at 0 # and increase by 1 for each element. # @@ -103,7 +103,7 @@ module Enumerable ## # Return an array of all elements which - # are yield by +each+. + # are yield by `each`. # # ISO 15.3.2.2.6 def entries @@ -123,7 +123,7 @@ module Enumerable ## # Call the given block for each element - # which is yield by +each+. Return an array + # which is yield by `each`. Return an array # which contains all elements whose block # value was true. # @@ -140,9 +140,9 @@ module Enumerable ## # Call the given block for each element - # which is yield by +each+ and which return + # which is yield by `each` and which return # value was true when invoking === with - # +pattern+. Return an array with all + # `pattern`. Return an array with all # elements or the respective block values. # # ISO 15.3.2.2.9 @@ -159,8 +159,8 @@ module Enumerable ## # Return true if at least one element which - # is yield by +each+ returns a true value - # by invoking == with +obj+. Otherwise return + # is yield by `each` returns a true value + # by invoking == with `obj`. Otherwise return # false. # # ISO 15.3.2.2.10 @@ -173,7 +173,7 @@ module Enumerable ## # Call the given block for each element - # which is yield by +each+. Return value + # which is yield by `each`. Return value # is the sum of all block values. Pass # to each block the current sum and the # current element. @@ -215,7 +215,7 @@ module Enumerable ## # Return the maximum value of all elements - # yield by +each+. If no block is given <=> + # yield by `each`. If no block is given <=> # will be invoked to define this value. If # a block is given it will be used instead. # @@ -242,7 +242,7 @@ module Enumerable ## # Return the minimum value of all elements - # yield by +each+. If no block is given <=> + # yield by `each`. If no block is given <=> # will be invoked to define this value. If # a block is given it will be used instead. # @@ -275,7 +275,7 @@ module Enumerable ## # Call the given block for each element - # which is yield by +each+. Return an + # which is yield by `each`. Return an # array which contains two arrays. The # first array contains all elements # whose block value was true. The second @@ -300,7 +300,7 @@ module Enumerable ## # Call the given block for each element - # which is yield by +each+. Return an + # which is yield by `each`. Return an # array which contains only the elements # whose block value was false. # @@ -323,7 +323,7 @@ module Enumerable ## # Return a sorted array of all elements - # which are yield by +each+. If no block + # which are yield by `each`. If no block # is given <=> will be invoked on each # element to define the order. Otherwise # the given block will be used for @@ -342,6 +342,11 @@ module Enumerable # redefine #hash 15.3.1.3.15 def hash + if __method_recursive?(:hash) + # Recursion detected, return a fixed value to break the loop + return 0 + end + h = 12347 i = 0 self.each do |e| diff --git a/mrblib/hash.rb b/mrblib/hash.rb index 897523c14..bee4abf23 100644 --- a/mrblib/hash.rb +++ b/mrblib/hash.rb @@ -16,8 +16,8 @@ class Hash # hash.delete(key) -> value or nil # hash.delete(key) {|key| ... } -> object # - # Delete the element with the key +key+. - # Return the value of the element if +key+ + # Delete the element with the key `key`. + # Return the value of the element if `key` # was found. Return nil if nothing was # found. If a block is given, call the # block with the value of the element. @@ -37,7 +37,7 @@ class Hash # hsh.each -> an_enumerator # hsh.each_pair -> an_enumerator # - # Calls the given block for each element of +self+ + # Calls the given block for each element of `self` # and pass the key and value of each element. # # If no block is given, an enumerator is returned instead. @@ -70,7 +70,7 @@ class Hash # hsh.each_key {| key | block } -> hsh # hsh.each_key -> an_enumerator # - # Calls the given block for each element of +self+ + # Calls the given block for each element of `self` # and pass the key of each element. # # If no block is given, an enumerator is returned instead. @@ -96,7 +96,7 @@ class Hash # hsh.each_value {| value | block } -> self # hsh.each_value -> an_enumerator # - # Calls the given block with each value; returns +self+: + # Calls the given block with each value; returns `self`: # # If no block is given, an enumerator is returned instead. # @@ -121,10 +121,10 @@ class Hash # hsh.merge(other_hash..) -> hsh # hsh.merge(other_hash..){|key, oldval, newval| block} -> hsh # - # Returns the new \Hash formed by merging each of +other_hashes+ - # into a copy of +self+. + # Returns the new \Hash formed by merging each of `other_hashes` + # into a copy of `self`. # - # Each argument in +other_hashes+ must be a \Hash. + # Each argument in `other_hashes` must be a \Hash. # Adds the contents of _other_hash_ to _hsh_. If no block is specified, # entries with duplicate keys are overwritten with the values from # _other_hash_, otherwise the value of each duplicate key is determined by @@ -159,8 +159,8 @@ class Hash # hsh.reject! {| key, value | block } -> hsh or nil # hsh.reject! -> an_enumerator # - # Equivalent to Hash#delete_if, but returns - # nil if no changes were made. + # Equivalent to `Hash#delete_if`, but returns + # `nil` if no changes were made. # # 1.8/1.9 Hash#reject! returns Hash; ISO says nothing. # @@ -212,8 +212,8 @@ class Hash # hsh.select! {| key, value | block } -> hsh or nil # hsh.select! -> an_enumerator # - # Equivalent to Hash#keep_if, but returns - # nil if no changes were made. + # Equivalent to `Hash#keep_if`, but returns + # `nil` if no changes were made. # # 1.9 Hash#select! returns Hash; ISO says nothing. # diff --git a/mrblib/numeric.rb b/mrblib/numeric.rb index 0d8dff8a0..004af69b9 100644 --- a/mrblib/numeric.rb +++ b/mrblib/numeric.rb @@ -41,7 +41,7 @@ end class Integer ## # Calls the given block once for each Integer - # from +self+ downto +num+. + # from `self` downto `num`. # # ISO 15.2.8.3.15 def downto(num, &block) @@ -66,7 +66,7 @@ class Integer alias succ next ## - # Calls the given block +self+ times. + # Calls the given block `self` times. # # ISO 15.2.8.3.22 def times(&block) @@ -82,7 +82,7 @@ class Integer ## # Calls the given block once for each Integer - # from +self+ upto +num+. + # from `self` upto `num`. # # ISO 15.2.8.3.27 def upto(num, &block) @@ -97,8 +97,8 @@ class Integer end ## - # Calls the given block from +self+ to +num+ - # incremented by +step+ (default 1). + # Calls the given block from `self` to `num` + # incremented by `step` (default 1). # def step(num=nil, step=1, &block) raise ArgumentError, "step can't be 0" if step == 0 @@ -129,8 +129,8 @@ end class Float ## - # Calls the given block from +self+ to +num+ - # incremented by +step+ (default 1). + # Calls the given block from `self` to `num` + # incremented by `step` (default 1). # def step(num=nil, step=1, &block) raise ArgumentError, "step can't be 0" if step == 0 diff --git a/mrblib/range.rb b/mrblib/range.rb index 4a8e10f30..0fb0e8cbd 100644 --- a/mrblib/range.rb +++ b/mrblib/range.rb @@ -10,7 +10,7 @@ class Range include Enumerable ## - # Calls the given block for each element of +self+ + # Calls the given block for each element of `self` # and pass the respective element. # # ISO 15.2.14.4.4 diff --git a/mrblib/string.rb b/mrblib/string.rb index 51be9b7dd..dfbbbd255 100644 --- a/mrblib/string.rb +++ b/mrblib/string.rb @@ -43,9 +43,9 @@ class String end ## - # Replace all matches of +pattern+ with +replacement+. + # Replace all matches of `pattern` with `replacement`. # Call block (if given) for each match and replace - # +pattern+ with the value of the block. Return the + # `pattern` with the value of the block. Return the # final value. # # ISO 15.2.10.5.18 @@ -78,10 +78,10 @@ class String end ## - # Replace all matches of +pattern+ with +replacement+. + # Replace all matches of `pattern` with `replacement`. # Call block (if given) for each match and replace - # +pattern+ with the value of the block. Modify - # +self+ with the final value. + # `pattern` with the value of the block. Modify + # `self` with the final value. # # ISO 15.2.10.5.19 def gsub!(*args, &block) @@ -93,9 +93,9 @@ class String end # ## -# # Calls the given block for each match of +pattern+ +# # Calls the given block for each match of `pattern` # # If no block is given return an array with all -# # matches of +pattern+. +# # matches of `pattern`. # # # # ISO 15.2.10.5.32 # def scan(pattern, &block) @@ -103,9 +103,9 @@ class String # end ## - # Replace only the first match of +pattern+ with - # +replacement+. Call block (if given) for each - # match and replace +pattern+ with the value of the + # Replace only the first match of `pattern` with + # `replacement`. Call block (if given) for each + # match and replace `pattern` with the value of the # block. Return the final value. # # ISO 15.2.10.5.36 @@ -133,10 +133,10 @@ class String end ## - # Replace only the first match of +pattern+ with - # +replacement+. Call block (if given) for each - # match and replace +pattern+ with the value of the - # block. Modify +self+ with the final value. + # Replace only the first match of `pattern` with + # `replacement`. Call block (if given) for each + # match and replace `pattern` with the value of the + # block. Modify `self` with the final value. # # ISO 15.2.10.5.37 def sub!(*args, &block) @@ -147,7 +147,7 @@ class String end ## - # Call the given block for each byte of +self+. + # Call the given block for each byte of `self`. def each_byte(&block) return to_enum(:each_byte, &block) unless block pos = 0 diff --git a/src/array.c b/src/array.c index 28af8d5c4..9b15995e9 100644 --- a/src/array.c +++ b/src/array.c @@ -22,12 +22,14 @@ #endif #define ARY_MAX_SIZE ((mrb_int)((ARY_C_MAX_SIZE < (size_t)MRB_INT_MAX) ? ARY_C_MAX_SIZE : MRB_INT_MAX-1)) +/* Raises an ArgumentError when array size exceeds limits */ static void ary_too_big(mrb_state *mrb) { mrb_raise(mrb, E_ARGUMENT_ERROR, "array size too big"); } +/* Checks if array size would exceed limits and raises error if so */ static inline void ary_check_too_big(mrb_state *mrb, mrb_int a, mrb_int b) { @@ -39,6 +41,7 @@ ary_check_too_big(mrb_state *mrb, mrb_int a, mrb_int b) #endif } +/* Creates a new RArray with specified capacity */ static struct RArray* ary_new_capa(mrb_state *mrb, mrb_int capa) { @@ -105,6 +108,7 @@ mrb_ary_new(mrb_state *mrb) * * See also https://togetter.com/li/462898 (Japanese) */ +/* Portable array copy function to avoid memcpy issues on some platforms */ static inline void array_copy(mrb_value *dst, const mrb_value *src, mrb_int size) { @@ -113,6 +117,7 @@ array_copy(mrb_value *dst, const mrb_value *src, mrb_int size) } } +/* Creates a new RArray initialized with values from an array */ static struct RArray* ary_new_from_values(mrb_state *mrb, mrb_int size, const mrb_value *vals) { @@ -165,6 +170,7 @@ mrb_assoc_new(mrb_state *mrb, mrb_value car, mrb_value cdr) return mrb_obj_value(a); } +/* Fills array elements with nil values */ static void ary_fill_with_nil(mrb_value *ptr, mrb_int size) { @@ -177,6 +183,7 @@ ary_fill_with_nil(mrb_value *ptr, mrb_int size) #define ary_modify_check(mrb, a) mrb_check_frozen((mrb), (a)) +/* Prepares array for modification, handling shared arrays and frozen check */ static void ary_modify(mrb_state *mrb, struct RArray *a) { @@ -223,6 +230,7 @@ mrb_ary_modify(mrb_state *mrb, struct RArray* a) ary_modify(mrb, a); } +/* Converts array to shared representation for copy-on-write semantics */ static void ary_make_shared(mrb_state *mrb, struct RArray *a) { @@ -244,6 +252,45 @@ ary_make_shared(mrb_state *mrb, struct RArray *a) } } +/* Creates a shared copy of array for temporary GC protection. + * Frozen arrays are returned as-is (cannot be modified). + * Embedded arrays get full copy (cannot be shared). + * Heap arrays get zero-copy shared reference. + */ +MRB_API mrb_value +mrb_ary_make_shared_copy(mrb_state *mrb, mrb_value ary) +{ + struct RArray *orig = mrb_ary_ptr(ary); + + // Frozen arrays don't need protection + if (mrb_frozen_p(orig)) { + return ary; + } + + // Embedded arrays can't be shared - make full copy + if (ARY_EMBED_P(orig)) { + return mrb_ary_dup(mrb, ary); + } + + // Make original array shared if not already + if (!ARY_SHARED_P(orig)) { + ary_make_shared(mrb, orig); + } + + // Create new array that shares the buffer + struct RArray *shared = (struct RArray*)mrb_obj_alloc(mrb, MRB_TT_ARRAY, mrb->array_class); + + shared->as.heap.ptr = orig->as.heap.ptr; + shared->as.heap.len = orig->as.heap.len; + shared->as.heap.aux.shared = orig->as.heap.aux.shared; + shared->as.heap.aux.shared->refcnt++; + ARY_SET_SHARED_FLAG(shared); + mrb_write_barrier(mrb, (struct RBasic*)shared); + + return mrb_obj_value(shared); +} + +/* Expands array capacity to accommodate at least len elements */ static void ary_expand_capa(mrb_state *mrb, struct RArray *a, mrb_int len) { @@ -284,6 +331,7 @@ ary_expand_capa(mrb_state *mrb, struct RArray *a, mrb_int len) } } +/* Shrinks array capacity to save memory when array becomes much smaller */ static void ary_shrink_capa(mrb_state *mrb, struct RArray *a) { @@ -342,6 +390,16 @@ mrb_ary_resize(mrb_state *mrb, mrb_value ary, mrb_int new_len) return ary; } +/* + * call-seq: + * Array[obj, ...] -> new_array + * + * Creates a new Array containing the given objects: + * + * Array[1, 'a', /^A/] # => [1, "a", /^A/] + * Array[1, 2, 3] # => [1, 2, 3] + * Array[] # => [] + */ static mrb_value mrb_ary_s_create(mrb_state *mrb, mrb_value klass) { @@ -358,6 +416,42 @@ mrb_ary_s_create(mrb_state *mrb, mrb_value klass) static void ary_replace(mrb_state*, struct RArray*, struct RArray*); +/* + * call-seq: + * Array.new(size=0, default=nil) -> new_array + * Array.new(array) -> new_array + * Array.new(size) {|index| ... } -> new_array + * + * Returns a new Array. + * + * With no block and no arguments, returns a new empty Array object. + * + * With no block and a single `size` argument, returns a new Array object + * of the given size whose elements are all `nil`: + * + * a = Array.new(3) + * a # => [nil, nil, nil] + * a.size # => 3 + * + * With no block and arguments `size` and `default`, returns an Array object + * of the given size; each element is the same `default` object: + * + * a = Array.new(3, 'x') + * a # => ['x', 'x', 'x'] + * + * With a block and argument `size`, returns an Array object of the given size; + * the block is called with each successive integer `index`; + * the element for that `index` is the return value from the block: + * + * a = Array.new(3) {|index| "Element #{index}" } + * a # => ["Element 0", "Element 1", "Element 2"] + * + * With a single Array argument `array`, returns a new Array formed from `array`: + * + * a = Array.new([:foo, 'bar', 2]) + * a.class # => Array + * a # => [:foo, "bar", 2] + */ static mrb_value mrb_ary_init(mrb_state *mrb, mrb_value ary) { @@ -394,6 +488,7 @@ mrb_ary_init(mrb_state *mrb, mrb_value ary) return ary; } +/* Internal helper to concatenate two arrays */ static void ary_concat(mrb_state *mrb, struct RArray *a, struct RArray *a2) { @@ -439,7 +534,7 @@ mrb_ary_concat(mrb_state *mrb, mrb_value self, mrb_value other) * call-seq: * array.concat(*other_arrays) -> self * - * Adds to +array+ all elements from each \Array in +other_arrays+; returns +self+: + * Adds to `array` all elements from each \Array in `other_arrays`; returns `self`: * * a = [0, 1] * a.concat([2, 3], [4, 5]) # => [0, 1, 2, 3, 4, 5] @@ -461,6 +556,16 @@ mrb_ary_concat_m(mrb_state *mrb, mrb_value self) return self; } +/* + * call-seq: + * array + other_array -> new_array + * + * Returns a new Array containing all elements of `array` + * followed by all elements of `other_array`: + * + * a = [0, 1] + [2, 3] + * a # => [0, 1, 2, 3] + */ static mrb_value mrb_ary_plus(mrb_state *mrb, mrb_value self) { @@ -481,6 +586,7 @@ mrb_ary_plus(mrb_state *mrb, mrb_value self) #define ARY_REPLACE_SHARED_MIN 20 +/* Internal helper to replace array contents with another array */ static void ary_replace(mrb_state *mrb, struct RArray *a, struct RArray *b) { @@ -543,6 +649,18 @@ mrb_ary_replace(mrb_state *mrb, mrb_value self, mrb_value other) } } +/* + * call-seq: + * array.replace(other_array) -> self + * array.initialize_copy(other_array) -> self + * + * Replaces the contents of `self` with the contents of `other_array`; + * returns `self`: + * + * a = [0, 1, 2] + * a.replace(['foo', 'bar']) # => ["foo", "bar"] + * a # => ["foo", "bar"] + */ static mrb_value mrb_ary_replace_m(mrb_state *mrb, mrb_value self) { @@ -554,6 +672,22 @@ mrb_ary_replace_m(mrb_state *mrb, mrb_value self) return self; } +/* + * call-seq: + * array * int -> new_array + * array * str -> new_string + * + * When the argument is an Integer `n`, + * returns a new Array built by concatenating `n` copies of `self`: + * + * a = ['x', 'y'] + * a * 3 # => ["x", "y", "x", "y", "x", "y"] + * + * When the argument is a String `separator`, + * equivalent to `array.join(separator)`: + * + * [1, 2, 3] * '|' # => "1|2|3" + */ static mrb_value mrb_ary_times(mrb_state *mrb, mrb_value self) { @@ -587,6 +721,16 @@ mrb_ary_times(mrb_state *mrb, mrb_value self) return mrb_obj_value(a2); } +/* + * call-seq: + * array.reverse! -> self + * + * Reverses `self` in place: + * + * a = ['foo', 'bar', 'two'] + * a.reverse! # => ["two", "bar", "foo"] + * a # => ["two", "bar", "foo"] + */ static mrb_value mrb_ary_reverse_bang(mrb_state *mrb, mrb_value self) { @@ -608,6 +752,17 @@ mrb_ary_reverse_bang(mrb_state *mrb, mrb_value self) return self; } +/* + * call-seq: + * array.reverse -> new_array + * + * Returns a new Array with the elements of `self` in reverse order: + * + * a = ['foo', 'bar', 'two'] + * a1 = a.reverse + * a1 # => ["two", "bar", "foo"] + * a # => ["foo", "bar", "two"] + */ static mrb_value mrb_ary_reverse(mrb_state *mrb, mrb_value self) { @@ -651,6 +806,23 @@ mrb_ary_push(mrb_state *mrb, mrb_value ary, mrb_value elem) mrb_field_write_barrier_value(mrb, (struct RBasic*)a, elem); } +/* + * call-seq: + * array.push(*objects) -> self + * array << object -> self + * + * Appends trailing elements. + * + * Appends each argument in `objects` to `self`; returns `self`: + * + * a = [:foo, 'bar', 2] + * a.push(:baz, :bat) # => [:foo, "bar", 2, :baz, :bat] + * + * Appends `object` to `self`; returns `self`: + * + * a = [:foo, 'bar', 2] + * a << :baz # => [:foo, "bar", 2, :baz] + */ static mrb_value mrb_ary_push_m(mrb_state *mrb, mrb_value self) { @@ -743,6 +915,27 @@ mrb_ary_shift(mrb_state *mrb, mrb_value self) } } +/* + * call-seq: + * array.shift -> object or nil + * array.shift(n) -> new_array + * + * Removes and returns leading elements. + * + * When no argument is given, removes and returns the first element: + * + * a = [:foo, 'bar', 2] + * a.shift # => :foo + * a # => ["bar", 2] + * + * Returns `nil` if `self` is empty. + * + * When argument `n` is given, removes and returns the first `n` elements in a new Array: + * + * a = [:foo, 'bar', 2] + * a.shift(2) # => [:foo, "bar"] + * a # => [2] + */ static mrb_value mrb_ary_shift_m(mrb_state *mrb, mrb_value self) { @@ -835,7 +1028,7 @@ mrb_ary_unshift(mrb_state *mrb, mrb_value self, mrb_value item) * call-seq: * array.unshift(*objects) -> self * - * Prepends the given +objects+ to +self+: + * Prepends the given `objects` to `self`: * * a = [:foo, 'bar', 2] * a.unshift(:bam, :bat) # => [:bam, :bat, :foo, "bar", 2] @@ -931,6 +1124,7 @@ mrb_ary_set(mrb_state *mrb, mrb_value ary, mrb_int n, mrb_value val) mrb_field_write_barrier_value(mrb, (struct RBasic*)a, val); } +/* Creates a duplicate of an array */ static struct RArray* ary_dup(mrb_state *mrb, struct RArray *a) { @@ -968,7 +1162,6 @@ mrb_ary_splice(mrb_state *mrb, mrb_value ary, mrb_int head, mrb_int len, mrb_val mrb_int alen = ARY_LEN(a); const mrb_value *argv; mrb_int argc; - mrb_int tail; ary_modify(mrb, a); @@ -984,7 +1177,8 @@ mrb_ary_splice(mrb_state *mrb, mrb_value ary, mrb_int head, mrb_int len, mrb_val out_of_range: mrb_raisef(mrb, E_INDEX_ERROR, "index %i is out of array", head); } - tail = head + len; + + mrb_int tail = head + len; if (alen < len || alen < tail) { len = alen - head; tail = head + len; @@ -1057,6 +1251,7 @@ mrb_ary_decref(mrb_state *mrb, mrb_shared_array *shared) } } +/* Creates a subsequence array, using shared storage when appropriate */ static mrb_value ary_subseq(mrb_state *mrb, struct RArray *a, mrb_int beg, mrb_int len) { @@ -1096,6 +1291,7 @@ mrb_ary_subseq(mrb_state *mrb, mrb_value ary, mrb_int beg, mrb_int len) return ary_subseq(mrb, a, beg, len); } +/* Converts various types to array index integer */ static mrb_int aget_index(mrb_state *mrb, mrb_value index) { @@ -1125,16 +1321,16 @@ aget_index(mrb_state *mrb, mrb_value index) * ary.slice(start, length) -> new_ary or nil * ary.slice(range) -> new_ary or nil * - * Element Reference --- Returns the element at +index+, or returns a - * subarray starting at the +start+ index and continuing for +length+ - * elements, or returns a subarray specified by +range+ of indices. + * Element Reference --- Returns the element at `index`, or returns a + * subarray starting at the `start` index and continuing for `length` + * elements, or returns a subarray specified by `range` of indices. * * Negative indices count backward from the end of the array (-1 is the last - * element). For +start+ and +range+ cases the starting index is just before + * element). For `start` and `range` cases the starting index is just before * an element. Additionally, an empty array is returned when the starting * index for an element range is at the end of the array. * - * Returns +nil+ if the index (or starting index) are out of range. + * Returns `nil` if the index (or starting index) are out of range. * * a = [ "a", "b", "c", "d", "e" ] * a[1] => "b" @@ -1186,16 +1382,16 @@ mrb_ary_aget(mrb_state *mrb, mrb_value self) * ary[start, length] = obj or other_ary or nil -> obj or other_ary or nil * ary[range] = obj or other_ary or nil -> obj or other_ary or nil * - * Element Assignment --- Sets the element at +index+, or replaces a subarray - * from the +start+ index for +length+ elements, or replaces a subarray - * specified by the +range+ of indices. + * Element Assignment --- Sets the element at `index`, or replaces a subarray + * from the `start` index for `length` elements, or replaces a subarray + * specified by the `range` of indices. * * If indices are greater than the current capacity of the array, the array - * grows automatically. Elements are inserted into the array at +start+ if - * +length+ is zero. + * grows automatically. Elements are inserted into the array at `start` if + * `length` is zero. * * Negative indices will count backward from the end of the array. For - * +start+ and +range+ cases the starting index is just before an element. + * `start` and `range` cases the starting index is just before an element. * * An IndexError is raised if a negative index points past the beginning of * the array. @@ -1273,6 +1469,27 @@ mrb_ary_delete_at(mrb_state *mrb, mrb_value self) return val; } +/* + * call-seq: + * array.first -> object or nil + * array.first(n) -> new_array + * + * Returns elements from the beginning of `self`. + * + * When no argument is given, returns the first element: + * + * a = [:foo, 'bar', 2] + * a.first # => :foo + * a # => [:foo, "bar", 2] + * + * If `self` is empty, returns `nil`. + * + * When non-negative Integer argument `n` is given, + * returns the first `n` elements in a new Array: + * + * a = [:foo, 'bar', 2] + * a.first(2) # => [:foo, "bar"] + */ static mrb_value mrb_ary_first(mrb_state *mrb, mrb_value self) { @@ -1296,6 +1513,27 @@ mrb_ary_first(mrb_state *mrb, mrb_value self) return mrb_ary_new_from_values(mrb, size, ARY_PTR(a)); } +/* + * call-seq: + * array.last -> object or nil + * array.last(n) -> new_array + * + * Returns elements from the end of `self`. + * + * When no argument is given, returns the last element: + * + * a = [:foo, 'bar', 2] + * a.last # => 2 + * a # => [:foo, "bar", 2] + * + * If `self` is empty, returns `nil`. + * + * When non-negative Integer argument `n` is given, + * returns the last `n` elements in a new Array: + * + * a = [:foo, 'bar', 2] + * a.last(2) # => ["bar", 2] + */ static mrb_value mrb_ary_last(mrb_state *mrb, mrb_value self) { @@ -1324,11 +1562,11 @@ mrb_ary_last(mrb_state *mrb, mrb_value self) * ary.index {|item| block } -> int or nil * array.index -> enumerator * - * Returns the _index_ of the first object in +ary+ such that the object is - * == to +obj+. + * Returns the _index_ of the first object in `ary` such that the object is + * `==` to `obj`. * * If a block is given instead of an argument, returns the _index_ of the - * first object for which the block returns +true+. Returns +nil+ if no + * first object for which the block returns `true`. Returns `nil` if no * match is found. * * ISO 15.2.12.5.14 @@ -1366,11 +1604,11 @@ mrb_ary_index_m(mrb_state *mrb, mrb_value self) * ary.rindex {|item| block } -> int or nil * array.rindex -> enumerator * - * Returns the _index_ of the first object in +ary+ such that the object is - * == to +obj+. + * Returns the _index_ of the first object in `ary` such that the object is + * `==` to `obj`. * * If a block is given instead of an argument, returns the _index_ of the - * first object for which the block returns +true+. Returns +nil+ if no + * first object for which the block returns `true`. Returns `nil` if no * match is found. * * ISO 15.2.12.5.26 @@ -1440,6 +1678,16 @@ mrb_ary_splat(mrb_state *mrb, mrb_value v) return mrb_obj_value(a); } +/* + * call-seq: + * array.size -> integer + * array.length -> integer + * + * Returns the count of elements in `self`: + * + * [0, 1, 2].size # => 3 + * [].size # => 0 + */ static mrb_value mrb_ary_size(mrb_state *mrb, mrb_value self) { @@ -1481,6 +1729,16 @@ mrb_ary_clear(mrb_state *mrb, mrb_value self) return self; } +/* + * call-seq: + * array.empty? -> true or false + * + * Returns `true` if the count of elements in `self` is zero, + * `false` otherwise: + * + * [].empty? # => true + * [0].empty? # => false + */ static mrb_value mrb_ary_empty_p(mrb_state *mrb, mrb_value self) { @@ -1598,7 +1856,7 @@ mrb_ary_join(mrb_state *mrb, mrb_value ary, mrb_value sep) * ary.join(sep="") -> str * * Returns a string created by converting each element of the array to - * a string, separated by sep. + * a string, separated by *sep*. * * [ "a", "b", "c" ].join #=> "abc" * [ "a", "b", "c" ].join("-") #=> "a-b-c" @@ -1670,7 +1928,7 @@ mrb_ary_eq(mrb_state *mrb, mrb_value ary1) if (n == 0) return mrb_false_value(); /* Check for recursion */ - if (MRB_RECURSIVE_BINARY_P(mrb, MRB_OPSYM(eq), ary1, ary2)) { + if (MRB_RECURSIVE_BINARY_FUNC_P(mrb, MRB_OPSYM(eq), ary1, ary2)) { return mrb_false_value(); } @@ -1687,7 +1945,7 @@ mrb_ary_eq(mrb_state *mrb, mrb_value ary1) * call-seq: * array.eql? other_array -> true or false * - * Returns true if +self+ and _other_ are the same object, + * Returns `true` if `self` and _other_ are the same object, * or are both arrays with the same content. * */ @@ -1701,7 +1959,7 @@ mrb_ary_eql(mrb_state *mrb, mrb_value ary1) if (n == 0) return mrb_false_value(); /* Check for recursion */ - if (MRB_RECURSIVE_BINARY_P(mrb, MRB_SYM_Q(eql), ary1, ary2)) { + if (MRB_RECURSIVE_BINARY_FUNC_P(mrb, MRB_SYM_Q(eql), ary1, ary2)) { return mrb_false_value(); } @@ -1719,12 +1977,12 @@ mrb_ary_eql(mrb_state *mrb, mrb_value ary1) * array <=> other_array -> -1, 0, or 1 * * Comparison---Returns an integer (-1, 0, or +1) - * if this array is less than, equal to, or greater than other_ary. + * if this array is less than, equal to, or greater than *other_ary*. * Each object in each array is compared (using <=>). If any value isn't * equal, then that inequality is the return value. If all the * values found are equal, then the return is based on a * comparison of the array lengths. Thus, two arrays are - * "equal" according to Array*<=> if and only if they have + * "equal" according to `Array#<=>` if and only if they have * the same length and the value of each element is equal to the * value of the corresponding element in the other array. */ @@ -1826,8 +2084,11 @@ mrb_ary_delete(mrb_state *mrb, mrb_value self) static mrb_bool -sort_cmp(mrb_state *mrb, mrb_value ary, mrb_value *p, mrb_value a_val, mrb_value b_val, mrb_value blk) +sort_cmp(mrb_state *mrb, mrb_value ary, mrb_value a_val, mrb_value b_val, mrb_value blk) { + mrb_value *p = RARRAY_PTR(ary); + mrb_int n = RARRAY_LEN(ary); + mrb_int cmp; int ai = mrb_gc_arena_save(mrb); @@ -1871,7 +2132,7 @@ sort_cmp(mrb_state *mrb, mrb_value ary, mrb_value *p, mrb_value a_val, mrb_value if (cmp == -2) { mrb_raise(mrb, E_ARGUMENT_ERROR, "comparison failed"); } - if (RARRAY_PTR(ary) != p) { + if (RARRAY_PTR(ary) != p || RARRAY_LEN(ary) != n) { mrb_raise(mrb, E_RUNTIME_ERROR, "array modified during sort"); } return cmp > 0; @@ -1886,10 +2147,10 @@ heapify(mrb_state *mrb, mrb_value ary, mrb_value *a, mrb_int index, mrb_int size mrb_int left_index = 2 * index + 1; mrb_int right_index = left_index + 1; - if (left_index < size && sort_cmp(mrb, ary, a, a[left_index], a[max], blk)) { + 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, a[right_index], a[max], blk)) { + if (right_index < size && sort_cmp(mrb, ary, a[right_index], a[max], blk)) { max = right_index; } @@ -1916,7 +2177,7 @@ insertion_sort(mrb_state *mrb, mrb_value ary, mrb_value *a, mrb_int size, mrb_va mrb_int j = i - 1; /* Move elements that are greater than key to one position ahead */ - while (j >= 0 && sort_cmp(mrb, ary, a, a[j], key, blk)) { + while (j >= 0 && sort_cmp(mrb, ary, a[j], key, blk)) { a[j + 1] = a[j]; j--; } @@ -1929,7 +2190,7 @@ insertion_sort(mrb_state *mrb, mrb_value ary, mrb_value *a, mrb_int size, mrb_va * array.sort! -> self * array.sort! {|a, b| ... } -> self * - * Sort all elements and replace +self+ with these + * Sort all elements and replace `self` with these * elements. */ static mrb_value diff --git a/src/class.c b/src/class.c index 7018ecc5f..2e1a053b4 100644 --- a/src/class.c +++ b/src/class.c @@ -50,18 +50,19 @@ typedef struct mt_tbl { union mt_ptr *ptr; /* block: [ ptr[0...alloc] | keys[0...alloc] ] */ } mt_tbl; -/* helper to get keys array */ +/* Helper to get keys array from method table */ static inline mrb_sym* mt_keys(mt_tbl *t) { return (mrb_sym*)&t->ptr[t->alloc]; } +/* Helper to get values array from method table */ static union mt_ptr* mt_vals(mt_tbl *t) { return t->ptr; } -/* allocate or grow the block to exactly alloc entries */ +/* Allocates or grows the method table block to exactly new_alloc entries */ static void mt_grow(mrb_state *mrb, mt_tbl *t, int new_alloc) { @@ -81,7 +82,7 @@ mt_grow(mrb_state *mrb, mt_tbl *t, int new_alloc) t->alloc = new_alloc; } -/* Creates the method table. */ +/* Creates a new empty method table */ static mt_tbl* mt_new(mrb_state *mrb) { @@ -95,7 +96,7 @@ mt_new(mrb_state *mrb) return t; } -/* Branch-free binary search helper: returns the index where `target` should be inserted/found. */ +/* Branch-free binary search helper for method table keys */ static inline int bsearch_idx(mrb_sym *keys, int size, mrb_sym target) { @@ -121,7 +122,7 @@ bsearch_idx(mrb_sym *keys, int size, mrb_sym target) return (int)(p - keys) + offset; } -/* Insert or update an entry in the method table using branch-free binary search */ +/* Inserts or updates an entry in the method table */ static void mt_put(mrb_state *mrb, mt_tbl *t, mrb_sym sym, mrb_sym flags, union mt_ptr ptrval) { @@ -164,7 +165,7 @@ mt_put(mrb_state *mrb, mt_tbl *t, mrb_sym sym, mrb_sym flags, union mt_ptr ptrva t->size++; } -/* Retrieve a value from the method table using branch-free binary search */ +/* Retrieves a value from the method table */ static mrb_sym mt_get(mrb_state *mrb, mt_tbl *t, mrb_sym sym, union mt_ptr *pp) { @@ -187,7 +188,7 @@ mt_get(mrb_state *mrb, mt_tbl *t, mrb_sym sym, union mt_ptr *pp) return 0; } -/* Deletes the entry for `sym` from the method table using branch-free search. */ +/* Deletes an entry from the method table */ static mrb_bool mt_del(mrb_state *mrb, mt_tbl *t, mrb_sym sym) { @@ -214,7 +215,7 @@ mt_del(mrb_state *mrb, mt_tbl *t, mrb_sym sym) return FALSE; } -/* Copy the method table. */ +/* Creates a copy of the method table */ static mt_tbl* mt_copy(mrb_state *mrb, mt_tbl *t) { @@ -228,7 +229,7 @@ mt_copy(mrb_state *mrb, mt_tbl *t) return t2; } -/* Free memory of the method table. */ +/* Frees memory of the method table */ static void mt_free(mrb_state *mrb, mt_tbl *t) { @@ -236,6 +237,7 @@ mt_free(mrb_state *mrb, mt_tbl *t) mrb_free(mrb, t); } +/* Creates a method value structure from key and pointer */ static inline mrb_method_t create_method_value(mrb_state *mrb, mrb_sym key, union mt_ptr val) { @@ -243,16 +245,7 @@ create_method_value(mrb_state *mrb, mrb_sym key, union mt_ptr val) return m; } -/* - * Iterates over the methods in a class's method table. - * - * @param mrb The mruby state. - * @param c The class whose method table is to be iterated. - * @param fn The callback function to be called for each method. - * The function receives the mruby state, the method symbol, the method itself, and user data. - * It should return 0 to continue iteration, or a non-zero value to stop. - * @param p User data to be passed to the callback function. - */ +/* Iterates over methods in a class's method table with callback function */ MRB_API void mrb_mt_foreach(mrb_state *mrb, struct RClass *c, mrb_mt_foreach_func *fn, void *p) { @@ -269,6 +262,7 @@ mrb_mt_foreach(mrb_state *mrb, struct RClass *c, mrb_mt_foreach_func *fn, void * } } +/* Marks method table entries for garbage collection */ size_t mrb_gc_mark_mt(mrb_state *mrb, struct RClass *c) { @@ -288,6 +282,7 @@ mrb_gc_mark_mt(mrb_state *mrb, struct RClass *c) return (size_t)t->size; } +/* Returns memory size of class method table */ size_t mrb_class_mt_memsize(mrb_state *mrb, struct RClass *c) { @@ -297,12 +292,14 @@ mrb_class_mt_memsize(mrb_state *mrb, struct RClass *c) return sizeof(struct mt_tbl) + (size_t)h->size * sizeof(mrb_method_t); } +/* Frees class method table for garbage collection */ void mrb_gc_free_mt(mrb_state *mrb, struct RClass *c) { if (c->mt) mt_free(mrb, c->mt); } +/* Sets the name of a class within an outer namespace */ void mrb_class_name_class(mrb_state *mrb, struct RClass *outer, struct RClass *c, mrb_sym id) { @@ -333,12 +330,14 @@ mrb_class_name_class(mrb_state *mrb, struct RClass *outer, struct RClass *c, mrb mrb_obj_iv_set_force(mrb, (struct RObject*)c, nsym, name); } +/* Checks if a name is a valid constant name */ mrb_bool mrb_const_name_p(mrb_state *mrb, const char *name, mrb_int len) { return len > 0 && ISUPPER(name[0]) && mrb_ident_p(name+1, len-1); } +/* Sets up a class by defining it as a constant in the outer namespace */ static void setup_class(mrb_state *mrb, struct RClass *outer, struct RClass *c, mrb_sym id) { @@ -347,6 +346,7 @@ setup_class(mrb_state *mrb, struct RClass *outer, struct RClass *c, mrb_sym id) #define make_metaclass(mrb, c) prepare_singleton_class((mrb), (struct RBasic*)(c)) +/* Prepares and creates a singleton class for an object */ static void prepare_singleton_class(mrb_state *mrb, struct RBasic *o) { @@ -384,6 +384,7 @@ prepare_singleton_class(mrb_state *mrb, struct RBasic *o) sc->frozen = o->frozen; } +/* Returns a string representation of a class name */ static mrb_value class_name_str(mrb_state *mrb, struct RClass* c) { @@ -397,6 +398,7 @@ class_name_str(mrb_state *mrb, struct RClass* c) return path; } +/* Gets a class from a constant symbol, ensuring it's a class */ static struct RClass* class_from_sym(mrb_state *mrb, struct RClass *klass, mrb_sym id) { @@ -406,6 +408,7 @@ class_from_sym(mrb_state *mrb, struct RClass *klass, mrb_sym id) return mrb_class_ptr(c); } +/* Gets a module from a constant symbol, ensuring it's a module */ static struct RClass* module_from_sym(mrb_state *mrb, struct RClass *klass, mrb_sym id) { @@ -415,6 +418,7 @@ module_from_sym(mrb_state *mrb, struct RClass *klass, mrb_sym id) return mrb_class_ptr(c); } +/* Checks if an object is a class or module */ static mrb_bool class_ptr_p(mrb_value obj) { @@ -428,6 +432,7 @@ class_ptr_p(mrb_value obj) } } +/* Checks if object is class/module and raises TypeError if not */ static void check_if_class_or_module(mrb_state *mrb, mrb_value obj) { @@ -436,6 +441,7 @@ check_if_class_or_module(mrb_state *mrb, mrb_value obj) } } +/* Defines a new module or returns existing one */ static struct RClass* define_module(mrb_state *mrb, mrb_sym name, struct RClass *outer) { @@ -1631,7 +1637,6 @@ get_args_v(mrb_state *mrb, mrb_args_format format, void** ptr, va_list *ap) mrb_int required = kwargs->required; const mrb_sym *kname = kwargs->table; mrb_value *values = kwargs->values; - mrb_int j; const mrb_int keyword_max = 40; mrb_assert(kwnum >= 0); @@ -1640,7 +1645,7 @@ get_args_v(mrb_state *mrb, mrb_args_format format, void** ptr, va_list *ap) mrb_raise(mrb, E_ARGUMENT_ERROR, "keyword number is too large"); } - for (j = required; j > 0; j--, kname++, values++) { + for (mrb_int j = required; j > 0; j--, kname++, values++) { mrb_value k = mrb_symbol_value(*kname); if (!mrb_hash_key_p(mrb, ksrc, k)) { mrb_raisef(mrb, E_ARGUMENT_ERROR, "missing keyword: %n", *kname); @@ -1649,7 +1654,7 @@ get_args_v(mrb_state *mrb, mrb_args_format format, void** ptr, va_list *ap) mrb_gc_protect(mrb, *values); } - for (j = kwnum - required; j > 0; j--, kname++, values++) { + for (mrb_int j = kwnum - required; j > 0; j--, kname++, values++) { mrb_value k = mrb_symbol_value(*kname); if (mrb_hash_key_p(mrb, ksrc, k)) { *values = mrb_hash_delete_key(mrb, ksrc, k); @@ -2019,6 +2024,26 @@ mrb_prepend_module(mrb_state *mrb, struct RClass *c, struct RClass *m) } } +/* + * call-seq: + * mod.prepend(module, ...) -> self + * + * Invokes Module.prepend_features on each parameter in reverse order. + * + * module Mod + * def hello + * "Hello from Mod.\n" + * end + * end + * + * class Klass + * def hello + * "Hello from Klass.\n" + * end + * prepend Mod + * end + * Klass.new.hello #=> "Hello from Mod.\n" + */ static mrb_value mrb_mod_prepend(mrb_state *mrb, mrb_value mod) { @@ -2039,6 +2064,23 @@ mrb_mod_prepend(mrb_state *mrb, mrb_value mod) return mod; } +/* + * call-seq: + * mod.include(module, ...) -> self + * + * Invokes Module.append_features on each parameter in reverse order. + * + * module Mod + * def hello + * "Hello from Mod.\n" + * end + * end + * + * class Klass + * include Mod + * end + * Klass.new.hello #=> "Hello from Mod.\n" + */ static mrb_value mrb_mod_include(mrb_state *mrb, mrb_value mod) { @@ -2124,8 +2166,8 @@ mrb_obj_extend(mrb_state *mrb, mrb_value obj) * call-seq: * mod.include?(module) -> true or false * - * Returns true if module is included in - * mod or one of mod's ancestors. + * Returns `true` if *module* is included in + * *mod* or one of *mod*'s ancestors. * * module A * end @@ -2156,6 +2198,22 @@ mrb_mod_include_p(mrb_state *mrb, mrb_value mod) return mrb_false_value(); } +/* + * call-seq: + * mod.ancestors -> array + * + * Returns a list of modules included/prepended in mod (including mod itself). + * + * module Mod + * include Math + * include Comparable + * prepend Enumerable + * end + * + * Mod.ancestors #=> [Enumerable, Mod, Comparable, Math] + * Math.ancestors #=> [Math] + * Numeric.ancestors #=> [Numeric, Comparable] + */ static mrb_value mrb_mod_ancestors(mrb_state *mrb, mrb_value self) { @@ -2678,18 +2736,14 @@ mod_attr_define(mrb_state *mrb, mrb_value mod, mrb_value (*accessor)(mrb_state*, int ai = mrb_gc_arena_save(mrb); for (int i=0; i obj * - * Creates a new object of class's class, then - * invokes that object's initialize method, - * passing it args. This is the method that ends + * Creates a new object of *class*'s class, then + * invokes that object's `initialize` method, + * passing it *args*. This is the method that ends * up getting called whenever an object is constructed using * `.new`. * @@ -2777,11 +2831,10 @@ mrb_instance_new(mrb_state *mrb, mrb_value cv) const mrb_value *argv; mrb_int argc; mrb_value blk; - mrb_sym init; mrb_get_args(mrb, "*!&", &argv, &argc, &blk); mrb_value obj = mrb_instance_alloc(mrb, cv); - init = MRB_SYM(initialize); + mrb_sym init = MRB_SYM(initialize); if (!mrb_func_basic_p(mrb, obj, init, mrb_do_nothing)) { mrb_funcall_with_block(mrb, obj, init, argc, argv, blk); } @@ -2893,24 +2946,24 @@ mrb_bob_not(mrb_state *mrb, mrb_value cv) * obj.equal?(other) -> true or false * obj.eql?(other) -> true or false * - * Equality---At the Object level, == returns - * true only if obj and other are the + * Equality---At the `Object` level, `==` returns + * `true` only if *obj* and *other* are the * same object. Typically, this method is overridden in descendant * classes to provide class-specific meaning. * - * Unlike ==, the equal? method should never be + * Unlike `==`, the `equal?` method should never be * overridden by subclasses: it is used to determine object identity - * (that is, a.equal?(b) iff a is the same - * object as b). + * (that is, `a.equal?(b)` iff `a` is the same + * object as `b`). * - * The eql? method returns true if - * obj and anObject have the same value. Used by - * Hash to test members for equality. For objects of - * class Object, eql? is synonymous with - * ==. Subclasses normally continue this tradition, but - * there are exceptions. Numeric types, for example, - * perform type conversion across ==, but not across - * eql?, so: + * The `eql?` method returns `true` if + * *obj* and *anObject* have the same value. Used by + * `Hash` to test members for equality. For objects of + * class `Object`, `eql?` is synonymous with + * `==`. Subclasses normally continue this tradition, but + * there are exceptions. `Numeric` types, for example, + * perform type conversion across `==`, but not across + * `eql?`, so: * * 1 == 1.0 #=> true * 1.eql? 1.0 #=> false @@ -3174,10 +3227,10 @@ mrb_module_new(mrb_state *mrb) * call-seq: * obj.class => class * - * Returns the class of obj, now preferred over - * Object#type, as an object's type in Ruby is only + * Returns the class of *obj*, now preferred over + * `Object#type`, as an object's type in Ruby is only * loosely tied to that object's class. This method must always be - * called with an explicit receiver, as class is also a + * called with an explicit receiver, as `class` is also a * reserved word in Ruby. * * 1.class #=> Integer @@ -3344,9 +3397,9 @@ mrb_mod_alias(mrb_state *mrb, mrb_value mod) static void undef_method(mrb_state *mrb, struct RClass *c, mrb_sym a) { - mrb_method_t m; mrb_sym undefined; mrb_value recv; + mrb_method_t m; MRB_METHOD_FROM_PROC(m, NULL); mrb_define_method_raw(mrb, c, a, m); @@ -3650,7 +3703,7 @@ mrb_mod_const_missing(mrb_state *mrb, mrb_value mod) * call-seq: * mod.method_defined?(symbol) -> true or false * - * Returns +true+ if the named method is defined by + * Returns `true` if the named method is defined by * _mod_ (or its included modules and, if _mod_ is a class, * its ancestors). Public and protected methods are matched. * @@ -3935,10 +3988,10 @@ init_copy(mrb_state *mrb, mrb_value dest, mrb_value obj) * call-seq: * obj.clone -> an_object * - * Produces a shallow copy of obj---the instance variables of - * obj are copied, but not the objects they reference. Copies - * the frozen state of obj. See also the discussion - * under Object#dup. + * Produces a shallow copy of *obj*---the instance variables of + * *obj* are copied, but not the objects they reference. Copies + * the frozen state of *obj*. See also the discussion + * under `Object#dup`. * * class Klass * attr_accessor :str @@ -3951,7 +4004,7 @@ init_copy(mrb_state *mrb, mrb_value dest, mrb_value obj) * s2.inspect #=> "#" * * This method may have class-specific behavior. If so, that - * behavior will be documented under the #+initialize_copy+ method of + * behavior will be documented under the #`initialize_copy` method of * the class. * * Some Class(True False Nil Symbol Integer Float) Object cannot clone. @@ -4007,17 +4060,17 @@ mrb_obj_clone(mrb_state *mrb, mrb_value self) * call-seq: * obj.dup -> an_object * - * Produces a shallow copy of obj---the instance variables of - * obj are copied, but not the objects they reference. - * dup copies the frozen state of obj. See also - * the discussion under Object#clone. In general, - * clone and dup may have different semantics - * in descendant classes. While clone is used to duplicate - * an object, including its internal state, dup typically + * Produces a shallow copy of *obj*---the instance variables of + * *obj* are copied, but not the objects they reference. + * `dup` copies the frozen state of *obj*. See also + * the discussion under `Object#clone`. In general, + * `clone` and `dup` may have different semantics + * in descendant classes. While `clone` is used to duplicate + * an object, including its internal state, `dup` typically * uses the class of the descendant object to create the new instance. * * This method may have class-specific behavior. If so, that - * behavior will be documented under the #+initialize_copy+ method of + * behavior will be documented under the #`initialize_copy` method of * the class. */ @@ -4076,16 +4129,16 @@ mrb_method_missing(mrb_state *mrb, mrb_sym name, mrb_value self, mrb_value args) * call-seq: * obj.method_missing(symbol [, *args] ) -> result * - * Invoked by Ruby when obj is sent a message it cannot handle. - * symbol is the symbol for the method called, and args + * Invoked by Ruby when *obj* is sent a message it cannot handle. + * *symbol* is the symbol for the method called, and *args* * are any arguments that were passed to it. By default, the interpreter * raises an error when this method is called. However, it is possible * to override the method to provide more dynamic behavior. * If it is decided that a particular method should not be handled, then - * super should be called, so that ancestors can pick up the + * *super* should be called, so that ancestors can pick up the * missing method. * The example below creates - * a class Roman, which responds to methods with names + * a class `Roman`, which responds to methods with names * consisting of roman numerals, returning the corresponding integer * values. * @@ -4125,14 +4178,14 @@ inspect_main(mrb_state *mrb, mrb_value mod) } static const mrb_code new_iseq[] = { - OP_ENTER, 0x0, 0x10, 0x3, // OP_ENTER 0:0:1:0:0:1:1 - OP_SSEND, 4, 0, 0, // OP_SSEND R4 :allocate n=0 - OP_MOVE, 0, 4, // OP_MOVE R0 R4 - OP_MOVE, 4, 3, // OP_MOVE R4 R3 (&) - OP_MOVE, 3, 2, // OP_MOVE R3 R2 (**) - OP_MOVE, 2, 1, // OP_MOVE R2 R1 (*) - OP_SSENDB, 1, 1, 255, // OP_SSENDB R1 :initialize n=*|nk=* - OP_RETURN, 0 // OP_RETURN R0 + OP_ENTER, 0x0, 0x10, 0x3, // 000 OP_ENTER 0:0:1:0:0:1:1 + OP_SSEND, 4, 0, 0, // 004 OP_SSEND R4 :allocate n=0 + OP_MOVE, 0, 4, // 008 OP_MOVE R0 R4 + OP_MOVE, 4, 3, // 011 OP_MOVE R4 R3 ; & + OP_MOVE, 3, 2, // 014 OP_MOVE R3 R2 ; ** + OP_MOVE, 2, 1, // 017 OP_MOVE R2 R1 ; * + OP_SSENDB, 1, 1, 255, // 020 OP_SSENDB R1 :initialize n=*|nk=* + OP_RETURN, 0 // 024 OP_RETURN R0 }; MRB_PRESYM_DEFINE_VAR_AND_INITER(new_syms, 2, MRB_SYM(allocate), MRB_SYM(initialize)) @@ -4160,13 +4213,13 @@ init_class_new(mrb_state *mrb, struct RClass *cls) } static const mrb_code neq_iseq[] = { - OP_ENTER, 0x4, 0, 0, // OP_ENTER 1:0:0:0:0:0:0 - OP_EQ, 0, // OP_EQ R0 (R1) - OP_JMPNOT, 0, 0, 5, // OP_JMPNOT R3 016 - OP_LOADF, 0, // OP_LOADF R0 (true) - OP_JMP, 0, 2, // OP_JMP R1 018 - OP_LOADT, 0, // OP_LOADT R3 (true) - OP_RETURN, 0 // OP_RETURN R0 + OP_ENTER, 0x4, 0, 0, // 000 OP_ENTER 1:0:0:0:0:0:0 + OP_EQ, 0, // 004 OP_EQ R0 (R1) + OP_JMPNOT, 0, 0, 5, // 006 OP_JMPNOT R0 015 + OP_LOADF, 0, // 010 OP_LOADF R0 (false) + OP_JMP, 0, 2, // 012 OP_JMP 017 + OP_LOADT, 0, // 015 OP_LOADT R0 (true) + OP_RETURN, 0 // 017 OP_RETURN R0 }; static const mrb_irep neq_irep = { diff --git a/src/dump.c b/src/dump.c index b06863e22..705f96ab3 100644 --- a/src/dump.c +++ b/src/dump.c @@ -165,7 +165,7 @@ get_pool_block_size(mrb_state *mrb, const mrb_irep *irep) case IREP_TT_BIGINT: { - mrb_int len = irep->pool[pool_no].u.str[0]; + mrb_int len = (uint8_t)irep->pool[pool_no].u.str[0]; mrb_assert_int_fit(mrb_int, len, size_t, SIZE_MAX); size += (size_t)len+2; } @@ -242,7 +242,7 @@ write_pool_block(mrb_state *mrb, const mrb_irep *irep, uint8_t *buf) case IREP_TT_BIGINT: cur += uint8_to_bin(IREP_TT_BIGINT, cur); /* data type */ - len = irep->pool[pool_no].u.str[0]; + len = (uint8_t)irep->pool[pool_no].u.str[0]; memcpy(cur, irep->pool[pool_no].u.str, (size_t)len+2); cur += len+2; break; @@ -288,14 +288,12 @@ write_pool_block(mrb_state *mrb, const mrb_irep *irep, uint8_t *buf) static size_t get_syms_block_size(mrb_state *mrb, const mrb_irep *irep) { - size_t size = 0; - int sym_no; - mrb_int len; + size_t size = sizeof(uint16_t); /* slen */ - size += sizeof(uint16_t); /* slen */ - for (sym_no = 0; sym_no < irep->slen; sym_no++) { + for (int sym_no = 0; sym_no < irep->slen; sym_no++) { size += sizeof(uint16_t); /* snl(n) */ if (irep->syms[sym_no] != 0) { + mrb_int len; mrb_sym_name_len(mrb, irep->syms[sym_no], &len); size += len + 1; /* sn(n) + null char */ } @@ -506,10 +504,7 @@ get_filename_table_size(mrb_state *mrb, const mrb_irep *irep, mrb_sym **fp, uint mrb_assert(lp); for (int i = 0; i < di->flen; i++) { - mrb_irep_debug_info_file *file; - mrb_int filename_len; - - file = di->files[i]; + mrb_irep_debug_info_file *file = di->files[i]; if (find_filename_index(filenames, *lp, file->filename_sym) == -1) { /* register filename */ *lp += 1; @@ -517,6 +512,7 @@ get_filename_table_size(mrb_state *mrb, const mrb_irep *irep, mrb_sym **fp, uint filenames[*lp - 1] = file->filename_sym; /* filename */ + mrb_int filename_len; mrb_sym_name_len(mrb, file->filename_sym, &filename_len); size += sizeof(uint16_t) + (size_t)filename_len; } @@ -617,10 +613,9 @@ write_section_debug(mrb_state *mrb, const mrb_irep *irep, uint8_t *cur, mrb_sym cur += uint16_to_bin(filenames_len, cur); section_size += sizeof(uint16_t); for (int i = 0; i < filenames_len; i++) { - char const *sym; mrb_int sym_len; + char const *sym = mrb_sym_name_len(mrb, filenames[i], &sym_len); - sym = mrb_sym_name_len(mrb, filenames[i], &sym_len); mrb_assert(sym); cur += uint16_to_bin((uint16_t)sym_len, cur); memcpy(cur, sym, sym_len); diff --git a/src/error.c b/src/error.c index 0007daf36..487e1afc7 100644 --- a/src/error.c +++ b/src/error.c @@ -80,7 +80,7 @@ exc_initialize(mrb_state *mrb, mrb_value exc) * With no argument, or if the argument is the same as the receiver, * return the receiver. Otherwise, create a new * exception object of the same class as the receiver, but with a - * message equal to string. + * message equal to `string`. * */ @@ -741,13 +741,7 @@ mrb_core_init_protect(mrb_state *mrb, void (*body)(mrb_state*, void*), void *opa body(mrb, opaque); err = 0; } MRB_CATCH(&c_jmp) { - if (mrb->exc) { - mrb_print_error(mrb); - mrb->exc = NULL; - } - else { - mrb_core_init_printabort(mrb); - } + /* Leave mrb->exc set for caller to inspect */ } MRB_END_EXC(&c_jmp); mrb->jmp = prev_jmp; @@ -851,6 +845,11 @@ MRB_API void mrb_print_error(mrb_state *mrb) { #ifndef MRB_NO_STDIO + if (!mrb) { + /* mrb_open() returned NULL - allocation failed */ + fputs("Failed to allocate mrb_state\n", stderr); + return; + } if (mrb->jmp == NULL) { struct mrb_jmpbuf c_jmp; MRB_TRY(&c_jmp) { diff --git a/src/etc.c b/src/etc.c index f02bf16d3..039f5fb7f 100644 --- a/src/etc.c +++ b/src/etc.c @@ -239,8 +239,8 @@ mrb_word_boxing_value_float(mrb_value v) MRB_API mrb_value mrb_word_boxing_cptr_value(mrb_state *mrb, void *p) { - mrb_value v; struct RCptr *cptr = MRB_OBJ_ALLOC(mrb, MRB_TT_CPTR, mrb->object_class); + mrb_value v; SET_OBJ_VALUE(v, cptr); cptr->p = p; diff --git a/src/gc.c b/src/gc.c index 0be46af7c..49850d99a 100644 --- a/src/gc.c +++ b/src/gc.c @@ -28,6 +28,11 @@ #include #endif +#ifdef MRB_USE_TASK_SCHEDULER +/* Forward declaration - actual implementation in task.c */ +void mrb_task_mark_all(mrb_state *mrb); +#endif + /* = Tri-color Incremental Garbage Collection @@ -238,16 +243,17 @@ mrb_calloc(mrb_state *mrb, size_t nelem, size_t len) { void *p; - if (nelem > 0 && len > 0 && - nelem <= SIZE_MAX / len) { - size_t size; - size = nelem * len; + if (nelem == 0 || len == 0) { + p = NULL; + } + else if (nelem <= SIZE_MAX / len) { + size_t size = nelem * len; p = mrb_malloc(mrb, size); memset(p, 0, size); } else { - p = NULL; + mrb_raise(mrb, E_ARGUMENT_ERROR, "memory allocation overflow"); } return p; @@ -425,10 +431,8 @@ mrb_gc_protect(mrb_state *mrb, mrb_value obj) MRB_API void mrb_gc_register(mrb_state *mrb, mrb_value obj) { - mrb_value table; - if (mrb_immediate_p(obj)) return; - table = mrb_gv_get(mrb, GC_ROOT_SYM); + mrb_value table = mrb_gv_get(mrb, GC_ROOT_SYM); int ai = mrb_gc_arena_save(mrb); mrb_gc_protect(mrb, obj); if (!mrb_array_p(table)) { @@ -444,13 +448,10 @@ mrb_gc_register(mrb_state *mrb, mrb_value obj) MRB_API void mrb_gc_unregister(mrb_state *mrb, mrb_value obj) { - mrb_value table; - struct RArray *a; - if (mrb_immediate_p(obj)) return; - table = mrb_gv_get(mrb, GC_ROOT_SYM); + mrb_value table = mrb_gv_get(mrb, GC_ROOT_SYM); if (!mrb_array_p(table)) return; - a = mrb_ary_ptr(table); + struct RArray *a = mrb_ary_ptr(table); mrb_ary_modify(mrb, a); mrb_int len = ARY_LEN(a)-1; mrb_value *ptr = ARY_PTR(a); @@ -956,6 +957,11 @@ root_scan_phase(mrb_state *mrb, mrb_gc *gc) if (mrb->root_c != mrb->c) { mark_context(mrb, mrb->root_c); } + +#ifdef MRB_USE_TASK_SCHEDULER + /* mark tasks - calls into task.c to mark all task queues */ + mrb_task_mark_all(mrb); +#endif } static void @@ -1335,7 +1341,7 @@ gc_start(mrb_state *mrb, mrb_value obj) * call-seq: * GC.enable -> true or false * - * Enables garbage collection, returning true if garbage + * Enables garbage collection, returning `true` if garbage * collection was previously disabled. * * GC.disable #=> false @@ -1358,7 +1364,7 @@ gc_enable(mrb_state *mrb, mrb_value obj) * call-seq: * GC.disable -> true or false * - * Disables garbage collection, returning true if garbage + * Disables garbage collection, returning `true` if garbage * collection was already disabled. * * GC.disable #=> false diff --git a/src/hash.c b/src/hash.c index b8ac9fd48..f39577af7 100644 --- a/src/hash.c +++ b/src/hash.c @@ -57,7 +57,7 @@ */ #define EA_N_RESERVED_INDICES 2 /* empty and deleted */ -#define EA_INCREASE_RATIO 6 / 5 + 6 + #define EA_MAX_INCREASE UINT16_MAX #define EA_MAX_CAPA U32(lesser(IB_MAX_CAPA - EA_N_RESERVED_INDICES, MRB_INT_MAX)) #define IB_MAX_CAPA (U32(1) << IB_MAX_BIT) @@ -93,6 +93,7 @@ typedef struct index_buckets_iter { struct RHash *h; uint32_t bit; uint32_t mask; + uint32_t initial_pos; uint32_t pos; uint32_t ary_index; uint32_t ea_index; @@ -311,6 +312,17 @@ h_check_modified_validate(mrb_state *mrb, struct h_check_modified *checker, stru } } +#ifndef MRB_NO_FLOAT +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; + return mrb_byte_hash((const uint8_t*)&f, sizeof(f)); +} +#endif + uint32_t mrb_obj_hash_code(mrb_state *mrb, mrb_value key) { @@ -329,14 +341,22 @@ mrb_obj_hash_code(mrb_state *mrb, mrb_value key) case MRB_TT_INTEGER: if (mrb_fixnum_p(key)) { hash_code = U32(mrb_fixnum(key)); - break; } -#ifndef MRB_NO_FLOAT - /* fall through */ - case MRB_TT_FLOAT: + else { +#ifdef MRB_USE_BIGINT + hash_code = U32(mrb_integer(mrb_bint_hash(mrb, key))); +#else + /* This path should not be reached if bignum is not configured. + * Hashing object_id is a fallback to avoid uninitialized value. */ + hash_code = U32(mrb_obj_id(key)); #endif - hash_code = U32(mrb_obj_id(key)); + } break; +#ifndef MRB_NO_FLOAT + case MRB_TT_FLOAT: + hash_code = float_hash_code(mrb_float(key)); + break; +#endif default: hash_code_obj = mrb_funcall_argv(mrb, key, MRB_SYM(hash), 0, NULL); hash_code = U32(tt) ^ U32(mrb_integer(hash_code_obj)); @@ -348,7 +368,7 @@ mrb_obj_hash_code(mrb_state *mrb, mrb_value key) static uint32_t obj_hash_code(mrb_state *mrb, mrb_value key, struct RHash *h) { - uint32_t hash_code; + uint32_t hash_code = 0; H_CHECK_MODIFIED(mrb, h) { hash_code = mrb_obj_hash_code(mrb, key); @@ -359,7 +379,7 @@ obj_hash_code(mrb_state *mrb, mrb_value key, struct RHash *h) static mrb_bool obj_eql(mrb_state *mrb, mrb_value a, mrb_value b, struct RHash *h) { - mrb_bool eql; + mrb_bool eql = FALSE; switch (mrb_type(a)) { case MRB_TT_STRING: @@ -418,7 +438,8 @@ ea_next_capa_for(uint32_t size, uint32_t max_capa) * `EA_INCREASE_RATIO` is the current value, 32-bit range will not be * exceeded during the calculation of `capa`, so `size_t` is used. */ - size_t capa = (size_t)size * EA_INCREASE_RATIO, inc = capa - size; + size_t capa = ((size_t)size * 6) / 5 + 6; + size_t inc = capa - size; if (EA_MAX_INCREASE < inc) capa = size + EA_MAX_INCREASE; return capa <= max_capa ? U32(capa) : max_capa; } @@ -654,7 +675,8 @@ ib_it_init(mrb_state *mrb, struct RHash *h, mrb_value key) it.h = h; it.bit = ib_bit(h); it.mask = ib_bit_to_capa(it.bit) - 1; - it.pos = ib_it_pos_for(&it, obj_hash_code(mrb, key, h)); + it.initial_pos = ib_it_pos_for(&it, obj_hash_code(mrb, key, h)); + it.pos = it.initial_pos; it.step = 0; return it; } @@ -695,7 +717,8 @@ ib_it_next(index_buckets_iter *it) else { it->shift1 = 0; } - it->pos = ib_it_pos_for(it, it->pos + (++it->step)); + it->step++; + it->pos = ib_it_pos_for(it, it->initial_pos + (it->step * it->step + it->step) / 2); } static mrb_bool @@ -1417,10 +1440,10 @@ hash_set_default_proc(mrb_state *mrb, mrb_value hash, mrb_value proc) * * Returns a new, empty hash. If this hash is subsequently accessed by * a key that doesn't correspond to a hash entry, the value returned - * depends on the style of new used to create the hash. In - * the first form, the access returns nil. If - * obj is specified, this single object will be used for - * all default values. If a block is specified, it will be + * depends on the style of `new` used to create the hash. In + * the first form, the access returns `nil`. If + * `obj` is specified, this single object will be used for + * all default values. If a block is specified, it will be * called with the hash object and the key, and should return the * default value. It is the block's responsibility to store the value * in the hash if required. @@ -1472,9 +1495,9 @@ mrb_hash_init(mrb_state *mrb, mrb_value hash) * call-seq: * hsh[key] -> value * - * Element Reference---Retrieves the value object corresponding - * to the key object. If not found, returns the default value (see - * Hash::new for details). + * Element Reference---Retrieves the `value` object corresponding + * to the `key` object. If not found, returns the default value (see + * `Hash::new` for details). * * h = { "a" => 100, "b" => 200 } * h["a"] #=> 100 @@ -1495,8 +1518,8 @@ mrb_hash_aget(mrb_state *mrb, mrb_value self) * hsh.default(key=nil) -> obj * * Returns the default value, the value that would be returned by - * hsh[key] if key did not exist in hsh. - * See also Hash::new and Hash#default=. + * `hsh`[`key`] if `key` did not exist in `hsh`. + * See also `Hash::new` and `Hash#default=`. * * h = Hash.new #=> {} * h.default #=> nil @@ -1537,7 +1560,7 @@ mrb_hash_default(mrb_state *mrb, mrb_value hash) * * Sets the default value, the value returned for a key that does not * exist in the hash. It is not possible to set the default to a - * Proc that will be executed on each key lookup. + * `Proc` that will be executed on each key lookup. * * h = { "a" => 100, "b" => 200 } * h.default = "Go fish" @@ -1573,8 +1596,8 @@ mrb_hash_set_default(mrb_state *mrb, mrb_value hash) * call-seq: * hsh.default_proc -> anObject * - * If Hash::new was invoked with a block, return that - * block, otherwise return nil. + * If `Hash::new` was invoked with a block, return that + * block, otherwise return `nil`. * * h = Hash.new {|h,k| h[k] = k*k } #=> {} * p = h.default_proc #=> # @@ -1664,8 +1687,8 @@ mrb_hash_delete(mrb_state *mrb, mrb_value self) * call-seq: * hsh.shift -> anArray or obj * - * Removes a key-value pair from hsh and returns it as the - * two-item array [ key, value ], or + * Removes a key-value pair from `hsh` and returns it as the + * two-item array [ `key`, `value` ], or * the hash's default value if the hash is empty. * * h = { 1 => "a", 2 => "b", 3 => "c" } @@ -1719,9 +1742,9 @@ mrb_hash_clear(mrb_state *mrb, mrb_value hash) * hsh.store(key, value) -> value * * Element Assignment---Associates the value given by - * value with the key given by key. - * key should not have its value changed while it is in - * use as a key (a String passed as a key will be + * `value` with the key given by `key`. + * `key` should not have its value changed while it is in + * use as a key (a `String` passed as a key will be * duplicated and frozen). * * h = { "a" => 100, "b" => 200 } @@ -1792,7 +1815,7 @@ mrb_hash_empty_p(mrb_state *mrb, mrb_value self) * call-seq: * hsh.empty? -> true or false * - * Returns true if hsh contains no key-value pairs. + * Returns `true` if `hsh` contains no key-value pairs. * * {}.empty? #=> true * @@ -1809,7 +1832,7 @@ mrb_hash_empty_m(mrb_state *mrb, mrb_value self) * hsh.keys -> array * * Returns a new array populated with the keys from this hash. See also - * Hash#values. + * `Hash#values`. * * h = { "a" => 100, "b" => 200, "c" => 300, "d" => 400 } * h.keys #=> ["a", "b", "c", "d"] @@ -1832,8 +1855,8 @@ mrb_hash_keys(mrb_state *mrb, mrb_value hash) * call-seq: * hsh.values -> array * - * Returns a new array populated with the values from hsh. See - * also Hash#keys. + * Returns a new array populated with the values from `hsh`. See + * also `Hash#keys`. * * h = { "a" => 100, "b" => 200, "c" => 300 } * h.values #=> [100, 200, 300] @@ -1862,7 +1885,7 @@ mrb_hash_values(mrb_state *mrb, mrb_value hash) * hsh.key?(key) -> true or false * hsh.member?(key) -> true or false * - * Returns true if the given key is present in hsh. + * Returns `true` if the given key is present in `hsh`. * * h = { "a" => 100, "b" => 200 } * h.has_key?("a") #=> true @@ -1894,8 +1917,8 @@ mrb_hash_has_key(mrb_state *mrb, mrb_value hash) * hsh.has_value?(value) -> true or false * hsh.value?(value) -> true or false * - * Returns true if the given value is present for some key - * in hsh. + * Returns `true` if the given value is present for some key + * in `hsh`. * * h = { "a" => 100, "b" => 200 } * h.has_value?(100) #=> true @@ -1971,7 +1994,7 @@ mrb_hash_merge_m(mrb_state *mrb, mrb_value hash) * * Rebuilds the hash based on the current hash values for each key. If * values of key objects have changed since they were inserted, this - * method will reindex hsh. + * method will reindex `hsh`. * * keys = (1..17).map{|n| [n]} * k = keys[0] @@ -2140,7 +2163,7 @@ mrb_hash_equal(mrb_state *mrb, mrb_value hash) } /* Check for recursion */ - if (MRB_RECURSIVE_BINARY_P(mrb, MRB_OPSYM(eq), hash, hash2)) { + if (MRB_RECURSIVE_BINARY_FUNC_P(mrb, MRB_OPSYM(eq), hash, hash2)) { return mrb_false_value(); } @@ -2188,7 +2211,7 @@ mrb_hash_eql(mrb_state *mrb, mrb_value hash) } /* Check for recursion */ - if (MRB_RECURSIVE_BINARY_P(mrb, MRB_SYM_Q(eql), hash, hash2)) { + if (MRB_RECURSIVE_BINARY_FUNC_P(mrb, MRB_SYM_Q(eql), hash, hash2)) { return mrb_false_value(); } diff --git a/src/kernel.c b/src/kernel.c index 1886013a3..2cb055985 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -65,9 +65,6 @@ inspect_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) } } - const char *s; - mrb_int len; - mrb_value ins; char *sp = RSTRING_PTR(a->str); /* need not to show internal data */ @@ -78,10 +75,13 @@ inspect_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) else { mrb_str_cat_lit(mrb, a->str, ", "); } - s = mrb_sym_name_len(mrb, sym, &len); + + mrb_int len; + const char *s = mrb_sym_name_len(mrb, sym, &len); mrb_str_cat(mrb, a->str, s, len); mrb_str_cat_lit(mrb, a->str, "="); - ins = mrb_inspect(mrb, v); + + mrb_value ins = mrb_inspect(mrb, v); mrb_str_cat_str(mrb, a->str, ins); return 0; } @@ -92,9 +92,9 @@ inspect_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) * obj.inspect -> string * * Returns a string containing a human-readable representation of - * obj. If not overridden and no instance variables, uses the - * to_s method to generate the string. - * obj. If not overridden, uses the to_s method to + * *obj*. If not overridden and no instance variables, uses the + * `to_s` method to generate the string. + * *obj*. If not overridden, uses the `to_s` method to * generate the string. * * [ 1, 2, 3..4, 'five' ].inspect #=> "[1, 2, 3..4, \"five\"]" @@ -120,9 +120,9 @@ mrb_obj_inspect(mrb_state *mrb, mrb_value obj) * call-seq: * obj === other -> true or false * - * Case Equality---For class Object, effectively the same - * as calling #==, but typically overridden by descendants - * to provide meaningful semantics in case statements. + * Case Equality---For class `Object`, effectively the same + * as calling `#==`, but typically overridden by descendants + * to provide meaningful semantics in `case` statements. */ static mrb_value mrb_eqq_m(mrb_state *mrb, mrb_value self) @@ -167,23 +167,36 @@ mrb_recursive_method_p(mrb_state *mrb, mrb_sym mid, mrb_value obj1, mrb_value ob return FALSE; } -#define MRB_RECURSIVE_P(mrb, mid, obj1, obj2) \ - mrb_recursive_method_p(mrb, mid, obj1, obj2) +/** + * Check if a C function call is recursive. + * + * Like mrb_recursive_method_p, but starts from ci[-2] to skip the immediate + * parent frame. Use this from C functions implementing Ruby methods that can + * be overridden with super calls. + */ +MRB_API mrb_bool +mrb_recursive_func_p(mrb_state *mrb, mrb_sym mid, mrb_value obj1, mrb_value obj2) +{ + /* Start from ci[-2] to skip immediate parent frame which may be a + Ruby override calling super */ + for (mrb_callinfo *ci=&mrb->c->ci[-2]; ci>=mrb->c->cibase; ci--) { + if (ci->mid == mid && mrb_obj_eq(mrb, obj1, ci->stack[0])) { + /* For unary methods, only check first argument */ + if (mrb_nil_p(obj2)) return TRUE; -#define MRB_RECURSIVE_UNARY_P(mrb, mid, obj) \ - mrb_recursive_method_p(mrb, mid, obj, mrb_nil_value()) - -#define MRB_RECURSIVE_BINARY_P(mrb, mid, obj1, obj2) \ - mrb_recursive_method_p(mrb, mid, obj1, obj2) + /* For binary methods, check both arguments */ + if (mrb_obj_eq(mrb, obj2, ci->stack[1])) return TRUE; + } + } + return FALSE; +} static mrb_value mrb_obj_method_recursive_p(mrb_state *mrb, mrb_value obj) { mrb_sym mid; mrb_value arg2 = mrb_nil_value(); - mrb_int argc; - - argc = mrb_get_args(mrb, "n|o", &mid, &arg2); + mrb_int argc = mrb_get_args(mrb, "n|o", &mid, &arg2); /* Use frame-skipping version for Ruby method calls */ for (mrb_callinfo *ci=&mrb->c->ci[-2]; ci>=mrb->c->cibase; ci--) { @@ -208,12 +221,12 @@ mrb_obj_method_recursive_p(mrb_state *mrb, mrb_value obj) * obj.__id__ -> int * obj.object_id -> int * - * Returns an integer identifier for obj. The same number will - * be returned on all calls to id for a given object, and + * Returns an integer identifier for *obj*. The same number will + * be returned on all calls to `id` for a given object, and * no two active objects will share an id. - * Object#object_id is a different concept from the - * :name notation, which returns the symbol id of - * name. Replaces the deprecated Object#id. + * `Object#object_id` is a different concept from the + * `:name` notation, which returns the symbol id of + * `name`. Replaces the deprecated `Object#id`. */ mrb_value mrb_obj_id_m(mrb_state *mrb, mrb_value self) @@ -242,8 +255,8 @@ env_bidx(struct REnv *e) * block_given? -> true or false * iterator? -> true or false * - * Returns true if yield would execute a - * block in the current context. The iterator? form + * Returns `true` if `yield` would execute a + * block in the current context. The `iterator?` form * is mildly deprecated. * * def try @@ -323,8 +336,8 @@ mrb_f_block_given_p_m(mrb_state *mrb, mrb_value self) * call-seq: * obj.class -> class * - * Returns the class of obj. This method must always be - * called with an explicit receiver, as class is also a + * Returns the class of *obj*. This method must always be + * called with an explicit receiver, as `class` is also a * reserved word in Ruby. * * 1.class #=> Integer @@ -364,11 +377,11 @@ mrb_obj_frozen(mrb_state *mrb, mrb_value self) * call-seq: * obj.hash -> int * - * Generates a Integer hash value for this object. This - * function must have the property that a.eql?(b) implies - * a.hash == b.hash. The hash value is used by class - * Hash. Any hash value that exceeds the capacity of a - * Integer will be truncated before being used. + * Generates a `Integer` hash value for this object. This + * function must have the property that `a.eql?(b)` implies + * `a.hash == b.hash`. The hash value is used by class + * `Hash`. Any hash value that exceeds the capacity of a + * `Integer` will be truncated before being used. */ static mrb_value mrb_obj_hash(mrb_state *mrb, mrb_value self) @@ -409,8 +422,8 @@ mrb_obj_is_instance_of(mrb_state *mrb, mrb_value obj, const struct RClass* c) * call-seq: * obj.instance_of?(class) -> true or false * - * Returns true if obj is an instance of the given - * class. See also Object#kind_of?. + * Returns `true` if *obj* is an instance of the given + * class. See also `Object#kind_of?`. */ static mrb_value obj_is_instance_of(mrb_state *mrb, mrb_value self) @@ -429,9 +442,9 @@ obj_is_instance_of(mrb_state *mrb, mrb_value self) * obj.is_a?(class) -> true or false * obj.kind_of?(class) -> true or false * - * Returns true if class is the class of - * obj, or if class is one of the superclasses of - * obj or modules included in obj. + * Returns `true` if *class* is the class of + * *obj*, or if *class* is one of the superclasses of + * *obj* or modules included in *obj*. * * module M; end * class A @@ -465,7 +478,7 @@ mrb_obj_is_kind_of_m(mrb_state *mrb, mrb_value self) * nil.nil? -> true * .nil? -> false * - * Only the object nil responds true to nil?. + * Only the object *nil* responds `true` to `nil?`. */ static mrb_value mrb_false(mrb_state *mrb, mrb_value self) @@ -481,15 +494,15 @@ mrb_false(mrb_state *mrb, mrb_value self) * raise(string) * raise(exception [, string]) * - * With no arguments, raises a RuntimeError - * With a single +String+ argument, raises a - * +RuntimeError+ with the string as a message. Otherwise, - * the first parameter should be the name of an +Exception+ - * class (or an object that returns an +Exception+ object when sent - * an +exception+ message). The optional second parameter sets the + * With no arguments, raises a `RuntimeError` + * With a single `String` argument, raises a + * `RuntimeError` with the string as a message. Otherwise, + * the first parameter should be the name of an `Exception` + * class (or an object that returns an `Exception` object when sent + * an `exception` message). The optional second parameter sets the * message associated with the exception, and the third parameter is an * array of callback information. Exceptions are caught by the - * +rescue+ clause of begin...end blocks. + * `rescue` clause of `begin...end` blocks. * * raise "Failed to create socket" * raise ArgumentError, "No parameters", caller @@ -498,9 +511,8 @@ mrb_value mrb_f_raise(mrb_state *mrb, mrb_value self) { mrb_value exc, mesg; - mrb_int argc; + mrb_int argc = mrb_get_args(mrb, "|oo", &exc, &mesg); - argc = mrb_get_args(mrb, "|oo", &exc, &mesg); mrb->c->ci->mid = 0; switch (argc) { case 0: @@ -528,7 +540,7 @@ mrb_f_raise(mrb_state *mrb, mrb_value self) * call-seq: * obj.remove_instance_variable(symbol) -> obj * - * Removes the named instance variable from obj, returning that + * Removes the named instance variable from *obj*, returning that * variable's value. * * class Dummy @@ -549,11 +561,10 @@ static mrb_value mrb_obj_remove_instance_variable(mrb_state *mrb, mrb_value self) { mrb_sym sym; - mrb_value val; mrb_get_args(mrb, "n", &sym); mrb_iv_name_sym_check(mrb, sym); - val = mrb_iv_remove(mrb, self, sym); + mrb_value val = mrb_iv_remove(mrb, self, sym); if (mrb_undef_p(val)) { mrb_name_error(mrb, sym, "instance variable %n not defined", sym); } @@ -565,15 +576,15 @@ mrb_obj_remove_instance_variable(mrb_state *mrb, mrb_value self) * call-seq: * obj.respond_to?(symbol, include_private=false) -> true or false * - * Returns +true+ if _obj_ responds to the given + * Returns `true` if _obj_ responds to the given * method. Private methods are included in the search only if the - * optional second parameter evaluates to +true+. + * optional second parameter evaluates to `true`. * * If the method is not implemented, * as Process.fork on Windows, File.lchmod on GNU/Linux, etc., * false is returned. * - * If the method is not defined, respond_to_missing? + * If the method is not defined, `respond_to_missing?` * method is called and the result is returned. */ static mrb_value @@ -587,8 +598,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; - v = mrb_funcall_id(mrb, self, rtm_id, 2, mrb_symbol_value(id), mrb_bool_value(priv)); + mrb_value v = mrb_funcall_id(mrb, self, rtm_id, 2, mrb_symbol_value(id), mrb_bool_value(priv)); return mrb_bool_value(mrb_bool(v)); } } @@ -599,7 +609,6 @@ static mrb_value mrb_obj_ceqq(mrb_state *mrb, mrb_value self) { mrb_value v = mrb_get_arg1(mrb); - mrb_int i, len; mrb_sym eqq = MRB_OPSYM(eqq); mrb_value ary; @@ -622,8 +631,8 @@ mrb_obj_ceqq(mrb_state *mrb, mrb_value self) } mrb_ensure_array_type(mrb, ary); } - len = RARRAY_LEN(ary); - for (i=0; i end) return MRB_DUMP_GENERAL_FAILURE; - filenames_obj = mrb_str_new(mrb, NULL, sizeof(mrb_sym) * (size_t)filenames_len); - filenames = (mrb_sym*)RSTRING_PTR(filenames_obj); - for (i = 0; i < filenames_len; i++) { + mrb_value filenames_obj = mrb_str_new(mrb, NULL, sizeof(mrb_sym) * (size_t)filenames_len); + mrb_sym *filenames = (mrb_sym*)RSTRING_PTR(filenames_obj); + for (uint16_t i = 0; i < filenames_len; i++) { uint16_t f_len = bin_to_uint16(bin); bin += sizeof(uint16_t); if (bin + f_len > end) { @@ -470,14 +464,12 @@ static int read_lv_record(mrb_state *mrb, const uint8_t *start, mrb_irep *irep, size_t *record_len, mrb_sym const *syms, uint32_t syms_len) { const uint8_t *bin = start; - mrb_sym *lv; - ptrdiff_t diff; - int i; if (irep->nlocals == 0) return MRB_DUMP_GENERAL_FAILURE; - irep->lv = lv = (mrb_sym*)mrb_malloc(mrb, sizeof(mrb_sym) * (irep->nlocals - 1)); + mrb_sym *lv = (mrb_sym*)mrb_malloc(mrb, sizeof(mrb_sym) * (irep->nlocals - 1)); + irep->lv = lv; - for (i = 0; i + 1 < irep->nlocals; i++) { + for (int i = 0; i + 1 < irep->nlocals; i++) { uint16_t const sym_idx = bin_to_uint16(bin); bin += sizeof(uint16_t); if (sym_idx == RITE_LV_NULL_MARK) { @@ -491,7 +483,7 @@ read_lv_record(mrb_state *mrb, const uint8_t *start, mrb_irep *irep, size_t *rec } } - for (i = 0; i < irep->rlen; i++) { + for (int i = 0; i < irep->rlen; i++) { size_t len; int ret; @@ -500,7 +492,7 @@ read_lv_record(mrb_state *mrb, const uint8_t *start, mrb_irep *irep, size_t *rec bin += len; } - diff = bin - start; + ptrdiff_t diff = bin - start; mrb_assert_int_fit(ptrdiff_t, diff, size_t, SIZE_MAX); *record_len = (size_t)diff; diff --git a/src/numeric.c b/src/numeric.c index 71ffa74c6..5bbaeee1a 100644 --- a/src/numeric.c +++ b/src/numeric.c @@ -133,7 +133,7 @@ mrb_int_pow(mrb_state *mrb, mrb_value x, mrb_value y) * * num ** other -> num * - * Raises num the other power. + * Raises `num` the `other` power. * * 2.0**3 #=> 8.0 */ @@ -198,7 +198,7 @@ mrb_div_int_value(mrb_state *mrb, mrb_int x, mrb_int y) * int / num -> num * * Performs division: the class of the resulting object depends on - * the class of num and on the magnitude of the + * the class of `num` and on the magnitude of the * result. */ static mrb_value @@ -333,7 +333,7 @@ coerce_step_counter(mrb_state *mrb, mrb_value self) * * Document-class: Float * - * Float objects represent inexact real numbers using + * `Float` objects represent inexact real numbers using * the native architecture's double-precision floating-point * representation. */ @@ -448,8 +448,8 @@ mrb_float_to_str(mrb_state *mrb, mrb_value flo, const char *fmt) * * Returns a string containing a representation of self. As well as a * fixed or exponential form of the number, the call may return - * "NaN", "Infinity", and - * "-Infinity". + * "`NaN`", "`Infinity`", and + * "`-Infinity`". * * 3.0.to_s #=> 3.0 * 3.25.to_s #=> 3.25 @@ -481,8 +481,8 @@ flo_to_s(mrb_state *mrb, mrb_value flt) * call-seq: * float + other -> float * - * Returns a new float which is the sum of float - * and other. + * Returns a new float which is the sum of `float` + * and `other`. */ static mrb_value flo_add(mrb_state *mrb, mrb_value x) @@ -507,8 +507,8 @@ flo_add(mrb_state *mrb, mrb_value x) * call-seq: * float - other -> float * - * Returns a new float which is the difference of float - * and other. + * Returns a new float which is the difference of `float` + * and `other`. */ static mrb_value @@ -534,8 +534,8 @@ flo_sub(mrb_state *mrb, mrb_value x) * call-seq: * float * other -> float * - * Returns a new float which is the product of float - * and other. + * Returns a new float which is the product of `float` + * and `other`. */ static mrb_value @@ -599,7 +599,7 @@ flodivmod(mrb_state *mrb, double x, double y, mrb_float *divp, mrb_float *modp) * flt % other -> float * flt.modulo(other) -> float * - * Return the modulo after division of flt by other. + * Return the modulo after division of `flt` by `other`. * * 6543.21.modulo(137) #=> 104.21 * 6543.21.modulo(137.24) #=> 92.9299999999996 @@ -621,7 +621,7 @@ flo_mod(mrb_state *mrb, mrb_value x) * call-seq: * num.eql?(numeric) -> true or false * - * Returns true if num and numeric are the + * Returns `true` if `num` and `numeric` are the * same type and have equal values. * * 1 == 1.0 #=> true @@ -657,9 +657,9 @@ num_eql(mrb_state *mrb, mrb_value x) * call-seq: * flt == obj -> true or false * - * Returns true only if obj has the same value - * as flt. Contrast this with Float#eql?, which - * requires obj to be a Float. + * Returns `true` only if *obj* has the same value + * as *flt*. Contrast this with `Float#eql?`, which + * requires *obj* to be a `Float`. * * 1.0 == 1 #=> true * @@ -695,7 +695,7 @@ flo_eq(mrb_state *mrb, mrb_value x) * call-seq: * flt.to_f -> self * - * As flt is already a float, returns +self+. + * As `flt` is already a float, returns `self`. */ /* 15.2.9.3.11 */ @@ -703,7 +703,7 @@ flo_eq(mrb_state *mrb, mrb_value x) * call-seq: * flt.infinite? -> nil, -1, +1 * - * Returns nil, -1, or +1 depending on whether flt + * Returns `nil`, -1, or +1 depending on whether *flt* * is finite, -infinity, or +infinity. * * (0.0).infinite? #=> nil @@ -727,9 +727,9 @@ flo_infinite_p(mrb_state *mrb, mrb_value num) * call-seq: * flt.finite? -> true or false * - * Returns true if flt is a valid IEEE floating - * point number (it is not infinite, and nan? is - * false). + * Returns `true` if *flt* is a valid IEEE floating + * point number (it is not infinite, and `nan?` is + * `false`). * */ @@ -825,13 +825,13 @@ flo_rounding(mrb_state *mrb, mrb_value num, double (*func)(double)) * call-seq: * float.floor([ndigits]) -> integer or float * - * Returns the largest number less than or equal to +float+ with - * a precision of +ndigits+ decimal digits (default: 0). + * Returns the largest number less than or equal to `float` with + * a precision of `ndigits` decimal digits (default: 0). * * When the precision is negative, the returned value is an integer - * with at least ndigits.abs trailing zeros. + * with at least `ndigits.abs` trailing zeros. * - * Returns a floating-point number when +ndigits+ is positive, + * Returns a floating-point number when `ndigits` is positive, * otherwise returns an integer. * * 1.2.floor #=> 1 @@ -870,13 +870,13 @@ flo_floor(mrb_state *mrb, mrb_value num) * call-seq: * float.ceil([ndigits]) -> integer or float * - * Returns the smallest number greater than or equal to +float+ with - * a precision of +ndigits+ decimal digits (default: 0). + * Returns the smallest number greater than or equal to `float` with + * a precision of `ndigits` decimal digits (default: 0). * * When the precision is negative, the returned value is an integer - * with at least ndigits.abs trailing zeros. + * with at least `ndigits.abs` trailing zeros. * - * Returns a floating-point number when +ndigits+ is positive, + * Returns a floating-point number when `ndigits` is positive, * otherwise returns an integer. * * 1.2.ceil #=> 2 @@ -916,7 +916,7 @@ flo_ceil(mrb_state *mrb, mrb_value num) * call-seq: * flt.round([ndigits]) -> integer or float * - * Rounds flt to a given precision in decimal digits (default 0 digits). + * Rounds *flt* to a given precision in decimal digits (default 0 digits). * Precision may be negative. Returns a floating-point number when ndigits * is more than zero. * @@ -947,7 +947,6 @@ flo_round(mrb_state *mrb, mrb_value num) { double number, f; mrb_int ndigits = 0; - mrb_int i; mrb_get_args(mrb, "|i", &ndigits); number = mrb_float(num); @@ -959,7 +958,8 @@ flo_round(mrb_state *mrb, mrb_value num) f = 1.0; if (ndigits < -DBL_DIG-2) return mrb_fixnum_value(0); - i = ndigits >= 0 ? ndigits : -ndigits; + + mrb_int i = ndigits >= 0 ? ndigits : -ndigits; if (ndigits > DBL_DIG+2) return num; while (--i >= 0) f = f*10.0; @@ -1022,7 +1022,7 @@ flo_to_i(mrb_state *mrb, mrb_value num) * flt.to_i -> integer * flt.truncate -> integer * - * Returns flt truncated to an Integer. + * Returns *flt* truncated to an `Integer`. */ static mrb_value @@ -1051,7 +1051,7 @@ flo_abs(mrb_state *mrb, mrb_value num) /* * Document-class: Integer * - * Integer is hold whole numbers. + * `Integer` is hold whole numbers. * */ @@ -1064,7 +1064,7 @@ flo_abs(mrb_state *mrb, mrb_value num) * int.to_i -> integer * int.to_int -> integer * - * As int is already an Integer, all these + * As *int* is already an `Integer`, all these * methods simply return the receiver. */ @@ -1087,9 +1087,8 @@ flo_abs(mrb_state *mrb, mrb_value num) mrb_value mrb_int_mul(mrb_state *mrb, mrb_value x, mrb_value y) { - mrb_int a; + mrb_int a = mrb_integer(x); - a = mrb_integer(x); if (mrb_integer_p(y)) { mrb_int b, c; @@ -1142,7 +1141,7 @@ mrb_int_mul(mrb_state *mrb, mrb_value x, mrb_value y) * int * numeric -> numeric_result * * Performs multiplication: the class of the resulting object depends on - * the class of numeric and on the magnitude of the + * the class of `numeric` and on the magnitude of the * result. */ @@ -1186,8 +1185,8 @@ intdivmod(mrb_state *mrb, mrb_int x, mrb_int y, mrb_int *divp, mrb_int *modp) * call-seq: * int % num -> num * - * Returns int modulo other. - * See numeric.divmod for more information. + * Returns `int` modulo `other`. + * See `numeric.divmod` for more information. */ static mrb_value @@ -1234,7 +1233,7 @@ static mrb_value flo_divmod(mrb_state *mrb, mrb_value x); * call-seq: * int.divmod(numeric) -> array * - * See Numeric#divmod. + * See `Numeric#divmod`. */ static mrb_value int_divmod(mrb_state *mrb, mrb_value x) @@ -1291,7 +1290,7 @@ flo_divmod(mrb_state *mrb, mrb_value x) * call-seq: * int == other -> true or false * - * Return true if int equals other + * Return `true` if `int` equals `other` * numerically. * * 1 == 2 #=> false @@ -1585,7 +1584,7 @@ prepare_int_rounding(mrb_state *mrb, mrb_value x) * Returns self. * * When the precision (ndigits) is negative, the returned value is an integer - * with at least ndigits.abs trailing zeros. + * with at least `ndigits.abs` trailing zeros. */ static mrb_value int_ceil(mrb_state *mrb, mrb_value x) @@ -1627,7 +1626,7 @@ int_ceil(mrb_state *mrb, mrb_value x) * Returns self. * * When the precision (ndigits) is negative, the returned value is an integer - * with at least ndigits.abs trailing zeros. + * with at least `ndigits.abs` trailing zeros. */ static mrb_value int_floor(mrb_state *mrb, mrb_value x) @@ -1668,7 +1667,7 @@ int_floor(mrb_state *mrb, mrb_value x) * Returns self. * * When the precision (ndigits) is negative, the returned value is an integer - * with at least ndigits.abs trailing zeros. + * with at least `ndigits.abs` trailing zeros. */ static mrb_value int_round(mrb_state *mrb, mrb_value x) @@ -1732,7 +1731,7 @@ int_round(mrb_state *mrb, mrb_value x) * Returns self. * * When the precision (ndigits) is negative, the returned value is an integer - * with at least ndigits.abs trailing zeros. + * with at least `ndigits.abs` trailing zeros. */ static mrb_value int_truncate(mrb_state *mrb, mrb_value x) @@ -1760,7 +1759,7 @@ int_truncate(mrb_state *mrb, mrb_value x) * call-seq: * int.to_f -> float * - * Converts int to a Float. + * Converts *int* to a `Float`. * */ @@ -1818,9 +1817,8 @@ mrb_float_to_integer(mrb_state *mrb, mrb_value x) mrb_value mrb_int_add(mrb_state *mrb, mrb_value x, mrb_value y) { - mrb_int a; + mrb_int a = mrb_integer(x); - a = mrb_integer(x); if (mrb_integer_p(y)) { mrb_int b, c; @@ -1865,7 +1863,7 @@ mrb_int_add(mrb_state *mrb, mrb_value x, mrb_value y) * int + numeric -> numeric_result * * Performs addition: the class of the resulting object depends on - * the class of numeric and on the magnitude of the + * the class of `numeric` and on the magnitude of the * result. */ static mrb_value @@ -1900,9 +1898,8 @@ int_add(mrb_state *mrb, mrb_value self) mrb_value mrb_int_sub(mrb_state *mrb, mrb_value x, mrb_value y) { - mrb_int a; + mrb_int a = mrb_integer(x); - a = mrb_integer(x); if (mrb_integer_p(y)) { mrb_int b, c; @@ -1945,7 +1942,7 @@ mrb_int_sub(mrb_state *mrb, mrb_value x, mrb_value y) * int - numeric -> numeric * * Performs subtraction: the class of the resulting object depends on - * the class of numeric and on the magnitude of the + * the class of `numeric` and on the magnitude of the * result. */ static mrb_value @@ -2039,8 +2036,8 @@ mrb_integer_to_str(mrb_state *mrb, mrb_value x, mrb_int base) * call-seq: * int.to_s(base=10) -> string * - * Returns a string containing the representation of int radix - * base (between 2 and 36). + * Returns a string containing the representation of *int* radix + * *base* (between 2 and 36). * * 12345.to_s #=> "12345" * 12345.to_s(2) #=> "11000000111001" @@ -2160,18 +2157,17 @@ int_hash(mrb_state *mrb, mrb_value self) * < => -1 * = => 0 * > => +1 - * Comparison---Returns -1, 0, or +1 depending on whether int is - * less than, equal to, or greater than numeric. This is the - * basis for the tests in Comparable. When the operands are + * Comparison---Returns -1, 0, or +1 depending on whether *int* is + * less than, equal to, or greater than *numeric*. This is the + * basis for the tests in `Comparable`. When the operands are * not comparable, it returns nil instead of raising an exception. */ static mrb_value num_cmp(mrb_state *mrb, mrb_value self) { mrb_value other = mrb_get_arg1(mrb); - mrb_int n; + mrb_int n = cmpnum(mrb, self, other); - n = cmpnum(mrb, self, other); if (n == -2) return mrb_nil_value(); return mrb_fixnum_value(n); } @@ -2186,9 +2182,8 @@ static mrb_value num_lt(mrb_state *mrb, mrb_value self) { mrb_value other = mrb_get_arg1(mrb); - mrb_int n; + mrb_int n = cmpnum(mrb, self, other); - n = cmpnum(mrb, self, other); if (n == -2) cmperr(mrb, self, other); if (n < 0) return mrb_true_value(); return mrb_false_value(); @@ -2198,9 +2193,8 @@ static mrb_value num_le(mrb_state *mrb, mrb_value self) { mrb_value other = mrb_get_arg1(mrb); - mrb_int n; + mrb_int n = cmpnum(mrb, self, other); - n = cmpnum(mrb, self, other); if (n == -2) cmperr(mrb, self, other); if (n <= 0) return mrb_true_value(); return mrb_false_value(); @@ -2210,9 +2204,8 @@ static mrb_value num_gt(mrb_state *mrb, mrb_value self) { mrb_value other = mrb_get_arg1(mrb); - mrb_int n; + mrb_int n = cmpnum(mrb, self, other); - n = cmpnum(mrb, self, other); if (n == -2) cmperr(mrb, self, other); if (n > 0) return mrb_true_value(); return mrb_false_value(); @@ -2222,9 +2215,8 @@ static mrb_value num_ge(mrb_state *mrb, mrb_value self) { mrb_value other = mrb_get_arg1(mrb); - mrb_int n; + mrb_int n = cmpnum(mrb, self, other); - n = cmpnum(mrb, self, other); if (n == -2) cmperr(mrb, self, other); if (n >= 0) return mrb_true_value(); return mrb_false_value(); diff --git a/src/object.c b/src/object.c index d42ea8ca4..292ce17eb 100644 --- a/src/object.c +++ b/src/object.c @@ -108,7 +108,7 @@ mrb_equal(mrb_state *mrb, mrb_value obj1, mrb_value obj2) /* * Document-class: NilClass * - * The class of the singleton object nil. + * The class of the singleton object `nil`. */ /* 15.2.4.3.4 */ @@ -116,7 +116,7 @@ mrb_equal(mrb_state *mrb, mrb_value obj1, mrb_value obj2) * call_seq: * nil.nil? -> true * - * Only the object nil responds true to nil?. + * Only the object *nil* responds `true` to `nil?`. */ static mrb_value @@ -152,10 +152,10 @@ nil_inspect(mrb_state *mrb, mrb_value obj) /*********************************************************************** * Document-class: TrueClass * - * The global value true is the only instance of class - * TrueClass and represents a logically true value in + * The global value `true` is the only instance of class + * `TrueClass` and represents a logically true value in * boolean expressions. The class provides operators allowing - * true to be used in logical expressions. + * `true` to be used in logical expressions. */ /* 15.2.5.3.1 */ @@ -163,8 +163,8 @@ nil_inspect(mrb_state *mrb, mrb_value obj) * call-seq: * true & obj -> true or false * - * And---Returns false if obj is - * nil or false, true otherwise. + * And---Returns `false` if *obj* is + * `nil` or `false`, `true` otherwise. */ static mrb_value @@ -182,8 +182,8 @@ true_and(mrb_state *mrb, mrb_value obj) * call-seq: * true ^ obj -> !obj * - * Exclusive Or---Returns true if obj is - * nil or false, false + * Exclusive Or---Returns `true` if *obj* is + * `nil` or `false`, `false` * otherwise. */ @@ -201,7 +201,7 @@ true_xor(mrb_state *mrb, mrb_value obj) * call-seq: * true.to_s -> "true" * - * The string representation of true is "true". + * The string representation of `true` is "true". */ static mrb_value @@ -217,7 +217,7 @@ true_to_s(mrb_state *mrb, mrb_value obj) * call-seq: * true | obj -> true * - * Or---Returns true. As anObject is an argument to + * Or---Returns `true`. As *anObject* is an argument to * a method call, it is always evaluated; there is no short-circuit * evaluation in this case. * @@ -238,10 +238,10 @@ true_or(mrb_state *mrb, mrb_value obj) /* * Document-class: FalseClass * - * The global value false is the only instance of class - * FalseClass and represents a logically false value in + * The global value `false` is the only instance of class + * `FalseClass` and represents a logically false value in * boolean expressions. The class provides operators allowing - * false to participate correctly in logical expressions. + * `false` to participate correctly in logical expressions. * */ @@ -252,7 +252,7 @@ true_or(mrb_state *mrb, mrb_value obj) * false & obj -> false * nil & obj -> false * - * And---Returns false. obj is always + * And---Returns `false`. *obj* is always * evaluated as it is the argument to a method call---there is no * short-circuit evaluation in this case. */ @@ -270,9 +270,9 @@ false_and(mrb_state *mrb, mrb_value obj) * false ^ obj -> true or false * nil ^ obj -> true or false * - * Exclusive Or---If obj is nil or - * false, returns false; otherwise, returns - * true. + * Exclusive Or---If *obj* is `nil` or + * `false`, returns `false`; otherwise, returns + * `true`. * */ @@ -292,8 +292,8 @@ false_xor(mrb_state *mrb, mrb_value obj) * false | obj -> true or false * nil | obj -> true or false * - * Or---Returns false if obj is - * nil or false; true otherwise. + * Or---Returns `false` if *obj* is + * `nil` or `false`; `true` otherwise. */ static mrb_value @@ -399,12 +399,10 @@ convert_type(mrb_state *mrb, mrb_value val, const char *tname, mrb_sym method, m MRB_API mrb_value mrb_type_convert(mrb_state *mrb, mrb_value val, enum mrb_vtype type, mrb_sym method) { - mrb_value v; - const char *tname; - if (mrb_type(val) == type) return val; - tname = type_name(type); - v = convert_type(mrb, val, tname, method, TRUE); + + const char *tname = type_name(type); + mrb_value v = convert_type(mrb, val, tname, method, TRUE); if (mrb_type(v) != type) { if (type == MRB_TT_STRING) return mrb_any_to_s(mrb, val); mrb_raisef(mrb, E_TYPE_ERROR, "%v cannot be converted to %s by #%n", val, tname, method); @@ -434,10 +432,8 @@ mrb_type_convert(mrb_state *mrb, mrb_value val, enum mrb_vtype type, mrb_sym met MRB_API mrb_value mrb_type_convert_check(mrb_state *mrb, mrb_value val, enum mrb_vtype type, mrb_sym method) { - mrb_value v; - if (mrb_type(val) == type && type != MRB_TT_CDATA && type != MRB_TT_ISTRUCT) return val; - v = convert_type(mrb, val, type_name(type), method, FALSE); + mrb_value v = convert_type(mrb, val, type_name(type), method, FALSE); if (mrb_nil_p(v) || mrb_type(v) != type) return mrb_nil_value(); return v; } @@ -485,8 +481,8 @@ mrb_check_type(mrb_state *mrb, mrb_value x, enum mrb_vtype t) * call-seq: * obj.to_s => string * - * Returns a string representing obj. The default - * to_s prints the object's class and an encoding of the + * Returns a string representing *obj*. The default + * `to_s` prints the object's class and an encoding of the * object id. As a special case, the top-level object that is the * initial execution context of Ruby programs returns "main." */ diff --git a/src/proc.c b/src/proc.c index 129843aa3..0292335ac 100644 --- a/src/proc.c +++ b/src/proc.c @@ -294,14 +294,12 @@ static mrb_value mrb_proc_s_new(mrb_state *mrb, mrb_value proc_class) { mrb_value blk; - mrb_value proc; - struct RProc *p; /* Calling Proc.new without a block is not implemented yet */ mrb_get_args(mrb, "&!", &blk); - p = MRB_OBJ_ALLOC(mrb, MRB_TT_PROC, mrb_class_ptr(proc_class)); + struct RProc *p = MRB_OBJ_ALLOC(mrb, MRB_TT_PROC, mrb_class_ptr(proc_class)); mrb_proc_copy(mrb, p, mrb_proc_ptr(blk)); - proc = mrb_obj_value(p); + mrb_value proc = mrb_obj_value(p); mrb_funcall_with_block(mrb, proc, MRB_SYM(initialize), 0, NULL, proc); if (!MRB_PROC_STRICT_P(p) && mrb->c->ci > mrb->c->cibase && MRB_PROC_ENV(p) == mrb->c->ci[-1].u.env) { @@ -370,21 +368,20 @@ proc_hash(mrb_state *mrb, mrb_value self) * call-seq: * lambda { |...| block } -> a_proc * - * Equivalent to Proc.new, except the resulting Proc objects + * Equivalent to `Proc.new`, except the resulting Proc objects * check the number of parameters passed when called. */ static mrb_value proc_lambda(mrb_state *mrb, mrb_value self) { mrb_value blk; - const struct RProc *p; mrb_get_args(mrb, "&", &blk); if (mrb_nil_p(blk)) { mrb_raise(mrb, E_ARGUMENT_ERROR, "tried to create Proc object without a block"); } check_proc(mrb, blk); - p = mrb_proc_ptr(blk); + const struct RProc *p = mrb_proc_ptr(blk); if (!MRB_PROC_STRICT_P(p)) { struct RProc *p2 = MRB_OBJ_ALLOC(mrb, MRB_TT_PROC, p->c); mrb_proc_copy(mrb, p2, p); @@ -431,19 +428,15 @@ mrb_proc_arity(const struct RProc *p) mrb_value mrb_proc_local_variables(mrb_state *mrb, const struct RProc *proc) { - const mrb_irep *irep; - mrb_value vars; - size_t i; - if (proc == NULL || MRB_PROC_CFUNC_P(proc)) { return mrb_ary_new(mrb); } - vars = mrb_hash_new(mrb); + mrb_value vars = mrb_hash_new(mrb); while (proc) { if (MRB_PROC_CFUNC_P(proc)) break; - irep = proc->body.irep; + const mrb_irep *irep = proc->body.irep; if (irep->lv) { - for (i = 0; i + 1 < irep->nlocals; i++) { + for (size_t i = 0; i + 1 < irep->nlocals; i++) { if (irep->lv[i]) { mrb_sym sym = irep->lv[i]; const char *name = mrb_sym_name(mrb, sym); diff --git a/src/range.c b/src/range.c index 552ad9663..676a6de96 100644 --- a/src/range.c +++ b/src/range.c @@ -20,12 +20,9 @@ static void r_check(mrb_state *mrb, mrb_value a, mrb_value b) { - enum mrb_vtype ta; - enum mrb_vtype tb; - mrb_int n; + enum mrb_vtype ta = mrb_type(a); + enum mrb_vtype tb = mrb_type(b); - ta = mrb_type(a); - tb = mrb_type(b); #ifdef MRB_NO_FLOAT if (ta == MRB_TT_INTEGER && tb == MRB_TT_INTEGER ) return; #else @@ -37,7 +34,7 @@ r_check(mrb_state *mrb, mrb_value a, mrb_value b) if (mrb_nil_p(a) || mrb_nil_p(b)) return; - n = mrb_cmp(mrb, a, b); + mrb_int n = mrb_cmp(mrb, a, b); if (n == -2) { /* can not be compared */ mrb_raise(mrb, E_ARGUMENT_ERROR, "bad value for range"); } @@ -114,7 +111,7 @@ range_ptr_replace(mrb_state *mrb, struct RRange *r, mrb_value beg, mrb_value end * rng.first => obj * rng.begin => obj * - * Returns the first object in rng. + * Returns the first object in `rng`. */ static mrb_value range_beg(mrb_state *mrb, mrb_value range) @@ -127,7 +124,7 @@ range_beg(mrb_state *mrb, mrb_value range) * rng.end => obj * rng.last => obj * - * Returns the object that defines the end of rng. + * Returns the object that defines the end of `rng`. * * (1..10).end #=> 10 * (1...10).end #=> 10 @@ -142,7 +139,7 @@ range_end(mrb_state *mrb, mrb_value range) * call-seq: * range.exclude_end? => true or false * - * Returns true if range excludes its end value. + * Returns `true` if `range` excludes its end value. */ static mrb_value range_excl(mrb_state *mrb, mrb_value range) @@ -154,8 +151,8 @@ range_excl(mrb_state *mrb, mrb_value range) * call-seq: * Range.new(start, end, exclusive=false) => range * - * Constructs a range using the given start and end. If the third - * parameter is omitted or is false, the range will include + * Constructs a range using the given `start` and `end`. If the third + * parameter is omitted or is `false`, the `range` will include * the end object; otherwise, it will be excluded. */ static mrb_value @@ -174,10 +171,10 @@ range_initialize(mrb_state *mrb, mrb_value range) * call-seq: * range == obj => true or false * - * Returns true only if - * 1) obj is a Range, - * 2) obj has equivalent beginning and end items (by comparing them with ==), - * 3) obj has the same #exclude_end? setting as rng. + * Returns `true` only if + * 1) `obj` is a Range, + * 2) `obj` has equivalent beginning and end items (by comparing them with `==`), + * 3) `obj` has the same #exclude_end? setting as `rng`. * * (0..2) == (0..2) #=> true * (0..2) == Range.new(0,2) #=> true @@ -267,7 +264,7 @@ range_to_s(mrb_state *mrb, mrb_value range) * rng.inspect -> string * * Convert this range object to a printable form (using - * inspect to convert the start and end + * `inspect` to convert the start and end * objects). */ static mrb_value @@ -297,9 +294,9 @@ range_inspect(mrb_state *mrb, mrb_value range) * call-seq: * rng.eql?(obj) -> true or false * - * Returns true only if obj is a Range, has equivalent + * Returns `true` only if `obj` is a Range, has equivalent * beginning and end items (by comparing them with #eql?), and has the same - * #exclude_end? setting as rng. + * #exclude_end? setting as `rng`. * * (0..2).eql?(0..2) #=> true * (0..2).eql?(Range.new(0,2)) #=> true @@ -430,11 +427,10 @@ range_num_to_a(mrb_state *mrb, mrb_value range) mrb_value mrb_get_values_at(mrb_state *mrb, mrb_value obj, mrb_int olen, mrb_int argc, const mrb_value *argv, mrb_value (*func)(mrb_state*, mrb_value, mrb_int)) { - mrb_int i, j, beg, len; - mrb_value result; - result = mrb_ary_new(mrb); + mrb_int beg, len; + mrb_value result = mrb_ary_new(mrb); - for (i = 0; i < argc; i++) { + for (mrb_int i = 0; i < argc; i++) { mrb_value v = argv[i]; if (mrb_integer_p(v) @@ -447,6 +443,7 @@ mrb_get_values_at(mrb_state *mrb, mrb_value obj, mrb_int olen, mrb_int argc, con } else if (mrb_range_beg_len(mrb, v, &beg, &len, olen, FALSE) == MRB_RANGE_OK) { mrb_int const end = olen < beg + len ? olen : beg + len; + mrb_int j; for (j = beg; j < end; j++) { mrb_ary_push(mrb, result, func(mrb, obj, j)); } diff --git a/src/readfloat.c b/src/readfloat.c index a98e53c7f..6c0d9aec6 100644 --- a/src/readfloat.c +++ b/src/readfloat.c @@ -4,6 +4,99 @@ #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. @@ -21,9 +114,12 @@ mrb_read_float(const char *str, char **endp, double *fp) { const char *p = str; const char *a = p; - double res = 0.0, frac = 0.0, div = 1.0; + 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++; @@ -32,9 +128,15 @@ mrb_read_float(const char *str, char **endp, double *fp) if (*p == '-') { sign = -1; p++; } else if (*p == '+') p++; - // Parse integer part + // Parse integer part using integer arithmetic for better accuracy while (ISDIGIT(*p)) { - res = res * 10.0 + (*p - '0'); + 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; } @@ -42,45 +144,66 @@ mrb_read_float(const char *str, char **endp, double *fp) // Parse fractional part if (*p == '.') { p++; - while (ISDIGIT(*p)) { - frac = frac * 10.0 + (*p++ - '0'); - div *= 10.0; + 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 0 + // If no digits were found, return FALSE if (digits == 0) { if (endp) *endp = (char*)str; *fp = 0.0; return FALSE; } - // Combine integer and fractional parts - res += frac / div; - res *= sign; + 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; - sign = 1; + int exp_sign = 1; p++; - if (*p == '-') { 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) // 10000 is big enough to get Infinity + if (e < 10000) { // Prevent integer overflow e = e * 10 + (*p - '0'); + } p++; } - res *= pow(10.0, sign * e); + 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; diff --git a/src/state.c b/src/state.c index 1b3d687b9..ee536901a 100644 --- a/src/state.c +++ b/src/state.c @@ -50,8 +50,8 @@ mrb_open_core(void) mrb->bootstrapping = TRUE; if (mrb_core_init_protect(mrb, init_gc_and_core, NULL)) { - mrb_close(mrb); - return NULL; + /* Return mrb with mrb->exc set for caller to inspect */ + return mrb; } mrb_method_cache_clear(mrb); @@ -74,14 +74,15 @@ mrb_open(void) { mrb_state *mrb = mrb_open_core(); - if (mrb == NULL) { - return NULL; + if (mrb == NULL || mrb->exc) { + /* Either allocation failed or core init failed */ + return mrb; } #ifndef MRB_NO_GEMS if (mrb_core_init_protect(mrb, init_mrbgems, NULL)) { - mrb_close(mrb); - return NULL; + /* Gem init failed - return mrb with mrb->exc set */ + return mrb; } mrb_gc_arena_restore(mrb, 0); #endif diff --git a/src/string.c b/src/string.c index 11ae5a318..9918b2903 100644 --- a/src/string.c +++ b/src/string.c @@ -1005,12 +1005,11 @@ mrb_str_modify(mrb_state *mrb, struct RString *s) MRB_API mrb_value mrb_str_resize(mrb_state *mrb, mrb_value str, mrb_int len) { - mrb_int slen; struct RString *s = mrb_str_ptr(str); str_check_length(mrb, len); mrb_str_modify(mrb, s); - slen = RSTR_LEN(s); + mrb_int slen = RSTR_LEN(s); if (len != slen) { if (slen < len || slen - len > 256) { resize_capa(mrb, s, len); @@ -1096,8 +1095,8 @@ mrb_str_plus(mrb_state *mrb, mrb_value a, mrb_value b) * call-seq: * str + other_str -> new_str * - * Concatenation---Returns a new String containing - * other_str concatenated to str. + * Concatenation---Returns a new `String` containing + * `other_str` concatenated to `str`. * * "Hello from " + self.to_s #=> "Hello from main" */ @@ -1136,7 +1135,7 @@ mrb_str_bytesize(mrb_state *mrb, mrb_value self) * call-seq: * str * integer => new_str * - * Copy---Returns a new String containing integer copies of + * Copy---Returns a new `String` containing `integer` copies of * the receiver. * * "Ho! " * 3 #=> "Ho! Ho! Ho! " @@ -1217,20 +1216,19 @@ mrb_str_cmp(mrb_state *mrb, mrb_value str1, mrb_value str2) * call-seq: * str <=> other_str => -1, 0, +1 * - * Comparison---Returns -1 if other_str is less than, 0 if - * other_str is equal to, and +1 if other_str is greater than - * str. If the strings are of different lengths, and the strings are + * Comparison---Returns -1 if `other_str` is less than, 0 if + * `other_str` is equal to, and +1 if `other_str` is greater than + * `str`. If the strings are of different lengths, and the strings are * equal when compared up to the shortest length, then the longer string is - * considered greater than the shorter one. If the variable $= is - * false, the comparison is based on comparing the binary values + * considered greater than the shorter one. If the variable `$=` is + * `false`, the comparison is based on comparing the binary values * of each character in the string. In older versions of Ruby, setting - * $= allowed case-insensitive comparisons; this is now deprecated - * in favor of using String#casecmp. + * `$=` allowed case-insensitive comparisons; this is now deprecated + * in favor of using `String#casecmp`. * - * <=> is the basis for the methods <, - * <=, >, >=, and between?, - * included from module Comparable. The method - * String#== does not use Comparable#==. + * `<=>` is the basis for the methods `<`, `<=`, `>`, `>=`, and `between?`, + * included from module `Comparable`. The method `String#==` does not use + * `Comparable#==`. * * "abcdef" <=> "abcde" #=> 1 * "abcdef" <=> "abcdef" #=> 0 @@ -1281,10 +1279,10 @@ mrb_str_equal(mrb_state *mrb, mrb_value str1, mrb_value str2) * str == obj => true or false * * Equality--- - * If obj is not a String, returns false. - * Otherwise, returns false or true + * If `obj` is not a `String`, returns `false`. + * Otherwise, returns `false` or `true` * - * caution:if str <=> obj returns zero. + * caution:if `str` `<=>` `obj` returns zero. */ static mrb_value mrb_str_equal_m(mrb_state *mrb, mrb_value str1) @@ -1416,17 +1414,17 @@ mrb_str_aref(mrb_state *mrb, mrb_value str, mrb_value idx, mrb_value alen) * str.slice(range) => new_str or nil * str.slice(other_str) => new_str or nil * - * Element Reference---If passed a single Integer, returns the code - * of the character at that position. If passed two Integer + * Element Reference---If passed a single `Integer`, returns the code + * of the character at that position. If passed two `Integer` * objects, returns a substring starting at the offset given by the first, and * a length given by the second. If given a range, a substring containing * characters at offsets given by the range is returned. In all three cases, if - * an offset is negative, it is counted from the end of str. Returns - * nil if the initial offset falls outside the string, the length + * an offset is negative, it is counted from the end of *str*. Returns + * `nil` if the initial offset falls outside the string, the length * is negative, or the beginning of the range is greater than the end. * - * If a String is given, that string is returned if it occurs in - * str. In both cases, nil is returned if there is no + * If a `String` is given, that string is returned if it occurs in + * *str*. In both cases, `nil` is returned if there is no * match. * * a = "hello there" @@ -1612,9 +1610,9 @@ mrb_str_aset(mrb_state *mrb, mrb_value str, mrb_value idx, mrb_value alen, mrb_v * str[range] = replace * str[other_str] = replace * - * Modify +self+ by replacing the content of +self+. + * Modify `self` by replacing the content of `self`. * The portion of the string affected is determined using the same criteria as +String#[]+. - * The return value of this expression is +replace+. + * The return value of this expression is `replace`. */ static mrb_value mrb_str_aset_m(mrb_state *mrb, mrb_value str) @@ -1638,8 +1636,8 @@ mrb_str_aset_m(mrb_state *mrb, mrb_value str) * call-seq: * str.capitalize! => str or nil * - * Modifies str by converting the first character to uppercase and the - * remainder to lowercase. Returns nil if no changes are made. + * Modifies *str* by converting the first character to uppercase and the + * remainder to lowercase. Returns `nil` if no changes are made. * * a = "hello" * a.capitalize! #=> "Hello" @@ -1676,7 +1674,7 @@ mrb_str_capitalize_bang(mrb_state *mrb, mrb_value str) * call-seq: * str.capitalize => new_str * - * Returns a copy of str with the first character converted to uppercase + * Returns a copy of *str* with the first character converted to uppercase * and the remainder to lowercase. * * "hello".capitalize #=> "Hello" @@ -1686,9 +1684,7 @@ mrb_str_capitalize_bang(mrb_state *mrb, mrb_value str) static mrb_value mrb_str_capitalize(mrb_state *mrb, mrb_value self) { - mrb_value str; - - str = mrb_str_dup(mrb, self); + mrb_value str = mrb_str_dup(mrb, self); mrb_str_capitalize_bang(mrb, str); return str; } @@ -1698,23 +1694,18 @@ mrb_str_capitalize(mrb_state *mrb, mrb_value self) * call-seq: * str.chomp!(separator="\n") => str or nil * - * Modifies str in place as described for String#chomp, - * returning str, or nil if no modifications were made. + * Modifies *str* in place as described for `String#chomp`, + * returning *str*, or `nil` if no modifications were made. */ static mrb_value mrb_str_chomp_bang(mrb_state *mrb, mrb_value str) { mrb_value rs; - mrb_int newline; - char *p, *pp; - mrb_int rslen; - mrb_int len; - mrb_int argc; + mrb_int argc = mrb_get_args(mrb, "|S", &rs); struct RString *s = mrb_str_ptr(str); - argc = mrb_get_args(mrb, "|S", &rs); mrb_str_modify_keep_ascii(mrb, s); - len = RSTR_LEN(s); + mrb_int len = RSTR_LEN(s); if (argc == 0) { if (len == 0) return mrb_nil_value(); smart_chomp: @@ -1736,8 +1727,8 @@ mrb_str_chomp_bang(mrb_state *mrb, mrb_value str) } if (len == 0 || mrb_nil_p(rs)) return mrb_nil_value(); - p = RSTR_PTR(s); - rslen = RSTRING_LEN(rs); + char *p = RSTR_PTR(s); + mrb_int rslen = RSTRING_LEN(rs); if (rslen == 0) { while (len>0 && p[len-1] == '\n') { len--; @@ -1752,13 +1743,13 @@ mrb_str_chomp_bang(mrb_state *mrb, mrb_value str) return mrb_nil_value(); } if (rslen > len) return mrb_nil_value(); - newline = RSTRING_PTR(rs)[rslen-1]; + mrb_int newline = RSTRING_PTR(rs)[rslen-1]; if (rslen == 1 && newline == '\n') newline = RSTRING_PTR(rs)[rslen-1]; if (rslen == 1 && newline == '\n') goto smart_chomp; - pp = p + len - rslen; + char *pp = p + len - rslen; if (p[len-1] == newline && (rslen <= 1 || memcmp(RSTRING_PTR(rs), pp, rslen) == 0)) { @@ -1774,10 +1765,10 @@ mrb_str_chomp_bang(mrb_state *mrb, mrb_value str) * call-seq: * str.chomp(separator="\n") => new_str * - * Returns a new String with the given record separator removed - * from the end of str (if present). chomp also removes - * carriage return characters (that is it will remove \n, - * \r, and \r\n). + * Returns a new `String` with the given record separator removed + * from the end of *str* (if present). `chomp` also removes + * carriage return characters (that is it will remove `\n`, + * `\r`, and `\r\n`). * * "hello".chomp #=> "hello" * "hello\n".chomp #=> "hello" @@ -1790,9 +1781,7 @@ mrb_str_chomp_bang(mrb_state *mrb, mrb_value str) static mrb_value mrb_str_chomp(mrb_state *mrb, mrb_value self) { - mrb_value str; - - str = mrb_str_dup(mrb, self); + mrb_value str = mrb_str_dup(mrb, self); mrb_str_chomp_bang(mrb, str); return str; } @@ -1802,9 +1791,9 @@ mrb_str_chomp(mrb_state *mrb, mrb_value self) * call-seq: * str.chop! => str or nil * - * Processes str as for String#chop, returning str, - * or nil if str is the empty string. See also - * String#chomp!. + * Processes *str* as for `String#chop`, returning *str*, + * or `nil` if *str* is the empty string. See also + * `String#chomp!`. */ static mrb_value mrb_str_chop_bang(mrb_state *mrb, mrb_value str) @@ -1844,10 +1833,10 @@ mrb_str_chop_bang(mrb_state *mrb, mrb_value str) * call-seq: * str.chop => new_str * - * Returns a new String with the last character removed. If the - * string ends with \r\n, both characters are removed. Applying - * chop to an empty string returns an empty - * string. String#chomp is often a safer alternative, as it leaves + * Returns a new `String` with the last character removed. If the + * string ends with `\r\n`, both characters are removed. Applying + * `chop` to an empty string returns an empty + * string. `String#chomp` is often a safer alternative, as it leaves * the string unchanged if it doesn't end in a record separator. * * "string\r\n".chop #=> "string" @@ -1859,8 +1848,7 @@ mrb_str_chop_bang(mrb_state *mrb, mrb_value str) static mrb_value mrb_str_chop(mrb_state *mrb, mrb_value self) { - mrb_value str; - str = mrb_str_dup(mrb, self); + mrb_value str = mrb_str_dup(mrb, self); mrb_str_chop_bang(mrb, str); return str; } @@ -1870,7 +1858,7 @@ mrb_str_chop(mrb_state *mrb, mrb_value self) * call-seq: * str.downcase! => str or nil * - * Downcases the contents of str, returning nil if no + * Downcases the contents of *str*, returning `nil` if no * changes were made. */ static mrb_value @@ -1900,7 +1888,7 @@ mrb_str_downcase_bang(mrb_state *mrb, mrb_value str) * call-seq: * str.downcase => new_str * - * Returns a copy of str with all uppercase letters replaced with their + * Returns a copy of *str* with all uppercase letters replaced with their * lowercase counterparts. The operation is locale insensitive---only * characters 'A' to 'Z' are affected. * @@ -1909,9 +1897,7 @@ mrb_str_downcase_bang(mrb_state *mrb, mrb_value str) static mrb_value mrb_str_downcase(mrb_state *mrb, mrb_value self) { - mrb_value str; - - str = mrb_str_dup(mrb, self); + mrb_value str = mrb_str_dup(mrb, self); mrb_str_downcase_bang(mrb, str); return str; } @@ -1921,7 +1907,7 @@ mrb_str_downcase(mrb_state *mrb, mrb_value self) * call-seq: * str.empty? => true or false * - * Returns true if str has a length of zero. + * Returns `true` if *str* has a length of zero. * * "hello".empty? #=> false * "".empty? #=> true @@ -2031,7 +2017,7 @@ mrb_str_hash_m(mrb_state *mrb, mrb_value self) * str.include? other_str => true or false * str.include? int => true or false * - * Returns true if str contains the given string or + * Returns `true` if *str* contains the given string or * character. * * "hello".include? "lo" #=> true @@ -2053,8 +2039,8 @@ mrb_str_include(mrb_state *mrb, mrb_value self) * call-seq: * str.byteindex(substring, offset = 0) -> integer or nil * - * Returns the \Integer byte-based index of the first occurrence of the given +substring+, - * or +nil+ if none found: + * Returns the \Integer byte-based index of the first occurrence of the given `substring`, + * or `nil` if none found: * * 'foo'.byteindex('f') # => 0 * 'foo'.byteindex('oo') # => 1 @@ -2087,7 +2073,7 @@ mrb_str_byteindex_m(mrb_state *mrb, mrb_value str) * str.index(substring [, offset]) => int or nil * * Returns the index of the first occurrence of the given - * substring. Returns nil if not found. + * *substring*. Returns `nil` if not found. * If the second parameter is present, it * specifies the position in the string to begin the search. * @@ -2149,7 +2135,7 @@ mrb_str_replace(mrb_state *mrb, mrb_value str) * call-seq: * String.new(str="") => new_str * - * Returns a new string object containing a copy of str. + * Returns a new string object containing a copy of *str*. */ static mrb_value mrb_str_init(mrb_state *mrb, mrb_value self) @@ -2170,7 +2156,7 @@ mrb_str_init(mrb_state *mrb, mrb_value self) * str.intern => symbol * str.to_sym => symbol * - * Returns the Symbol corresponding to str, creating the + * Returns the `Symbol` corresponding to *str*, creating the * symbol if it did not previously exist. * * "Koala".intern #=> :Koala @@ -2180,7 +2166,7 @@ mrb_str_init(mrb_state *mrb, mrb_value self) * s == :@cat #=> true * * This can also be used to create symbols that cannot be represented using the - * :xxx notation. + * `:xxx` notation. * * 'cat and dog'.to_sym #=> :"cat and dog" */ @@ -2282,7 +2268,7 @@ str_reverse(char *p, char *e) * call-seq: * str.reverse! => str * - * Reverses str in place. + * Reverses *str* in place. */ static mrb_value mrb_str_reverse_bang(mrb_state *mrb, mrb_value str) @@ -2327,7 +2313,7 @@ mrb_str_reverse_bang(mrb_state *mrb, mrb_value str) * call-seq: * str.reverse => new_str * - * Returns a new string with the characters from str in reverse order. + * Returns a new string with the characters from *str* in reverse order. * * "stressed".reverse #=> "desserts" */ @@ -2343,8 +2329,8 @@ mrb_str_reverse(mrb_state *mrb, mrb_value str) * call-seq: * byterindex(substring, offset = self.bytesize) -> integer or nil * - * Returns the \Integer byte-based index of the _last_ occurrence of the given +substring+, - * or +nil+ if none found: + * Returns the \Integer byte-based index of the _last_ occurrence of the given `substring`, + * or `nil` if none found: * * 'foo'.byterindex('f') # => 0 * 'foo'.byterindex('o') # => 2 @@ -2354,9 +2340,9 @@ mrb_str_reverse(mrb_state *mrb, mrb_value str) static mrb_value mrb_str_byterindex_m(mrb_state *mrb, mrb_value str) { + mrb_int len = RSTRING_LEN(str); mrb_value sub; mrb_int pos; - mrb_int len = RSTRING_LEN(str); if (mrb_get_args(mrb, "S|i", &sub, &pos) == 1) { pos = len; @@ -2382,8 +2368,8 @@ mrb_str_byterindex_m(mrb_state *mrb, mrb_value str) * call-seq: * str.rindex(substring [, offset]) => int or nil * - * Returns the index of the last occurrence of the given substring. - * Returns nil if not found. If the second parameter is + * Returns the index of the last occurrence of the given *substring*. + * Returns `nil` if not found. If the second parameter is * present, it specifies the position in the string to end the * search---characters beyond this point will not be considered. * @@ -2436,20 +2422,20 @@ mrb_str_rindex_m(mrb_state *mrb, mrb_value str) * call-seq: * str.split(separator=nil, [limit]) => anArray * - * Divides str into substrings based on a delimiter, returning an array + * Divides *str* into substrings based on a delimiter, returning an array * of these substrings. * - * If separator is a String, then its contents are used as - * the delimiter when splitting str. If separator is a single - * space, str is split on whitespace, with leading whitespace and runs + * If *separator* is a `String`, then its contents are used as + * the delimiter when splitting *str*. If *separator* is a single + * space, *str* is split on whitespace, with leading whitespace and runs * of contiguous whitespace characters ignored. * - * If separator is omitted or nil (which is the default), - * str is split on whitespace as if ' ' were specified. + * If *separator* is omitted or `nil` (which is the default), + * *str* is split on whitespace as if ' ' were specified. * - * If the limit parameter is omitted, trailing null fields are - * suppressed. If limit is a positive number, at most that number of - * fields will be returned (if limit is 1, the entire + * If the *limit* parameter is omitted, trailing null fields are + * suppressed. If *limit* is a positive number, at most that number of + * fields will be returned (if *limit* is `1`, the entire * string is returned as the only entry in an array). If negative, there is no * limit to the number of fields returned, and trailing null fields are not * suppressed. @@ -2466,18 +2452,14 @@ mrb_str_rindex_m(mrb_state *mrb, mrb_value str) static mrb_value mrb_str_split_m(mrb_state *mrb, mrb_value str) { - mrb_int argc; mrb_value spat = mrb_nil_value(); enum {awk, string} split_type = string; mrb_int i = 0; - mrb_int beg; - mrb_int end; mrb_int lim = 0; - mrb_bool lim_p; - mrb_value result, tmp; + mrb_value tmp; - argc = mrb_get_args(mrb, "|oi", &spat, &lim); - lim_p = (lim > 0 && argc == 2); + mrb_int argc = mrb_get_args(mrb, "|oi", &spat, &lim); + mrb_bool lim_p = (lim > 0 && argc == 2); if (argc == 2) { if (lim == 1) { if (RSTRING_LEN(str) == 0) @@ -2497,16 +2479,16 @@ mrb_str_split_m(mrb_state *mrb, mrb_value str) split_type = awk; } - result = mrb_ary_new(mrb); - beg = 0; + mrb_value result = mrb_ary_new(mrb); + mrb_int beg = 0; if (split_type == awk) { mrb_bool skip = TRUE; - mrb_int idx = 0; mrb_int str_len = RSTRING_LEN(str); - unsigned int c; + mrb_int idx = beg; + mrb_int end = beg; int ai = mrb_gc_arena_save(mrb); + unsigned int c; - idx = end = beg; while (idx < str_len) { c = (unsigned char)RSTRING_PTR(str)[idx++]; if (skip) { @@ -2538,6 +2520,7 @@ mrb_str_split_m(mrb_state *mrb, mrb_value str) int ai = mrb_gc_arena_save(mrb); while (idx < str_len) { + mrb_int end; if (pat_len > 0) { end = mrb_memsearch(RSTRING_PTR(spat), pat_len, RSTRING_PTR(str)+idx, str_len - idx); if (end < 0) break; @@ -2828,12 +2811,9 @@ mrb_string_cstr(mrb_state *mrb, mrb_value str) MRB_API mrb_value mrb_str_to_integer(mrb_state *mrb, mrb_value str, mrb_int base, mrb_bool badcheck) { - const char *s; - mrb_int len; - mrb_ensure_string_type(mrb, str); - s = RSTRING_PTR(str); - len = RSTRING_LEN(str); + const char *s = RSTRING_PTR(str); + mrb_int len = RSTRING_LEN(str); return mrb_str_len_to_integer(mrb, s, len, base, badcheck); } @@ -2842,10 +2822,10 @@ mrb_str_to_integer(mrb_state *mrb, mrb_value str, mrb_int base, mrb_bool badchec * call-seq: * str.to_i(base=10) => integer * - * Returns the result of interpreting leading characters in str as an - * integer base base (between 2 and 36). Extraneous characters past the + * Returns the result of interpreting leading characters in *str* as an + * integer base *base* (between 2 and 36). Extraneous characters past the * end of a valid number are ignored. If there is not a valid number at the - * start of str, 0 is returned. This method never raises an + * start of *str*, `0` is returned. This method never raises an * exception. * * "12345".to_i #=> 12345 @@ -2982,10 +2962,10 @@ mrb_str_to_dbl(mrb_state *mrb, mrb_value str, mrb_bool badcheck) * call-seq: * str.to_f => float * - * Returns the result of interpreting leading characters in str as a + * Returns the result of interpreting leading characters in *str* as a * floating-point number. Extraneous characters past the end of a valid number - * are ignored. If there is not a valid number at the start of str, - * 0.0 is returned. This method never raises an exception. + * are ignored. If there is not a valid number at the start of *str*, + * `0.0` is returned. This method never raises an exception. * * "123.45e1".to_f #=> 1234.5 * "45.67 degrees".to_f #=> 45.67 @@ -3019,7 +2999,7 @@ mrb_str_to_s(mrb_state *mrb, mrb_value self) * call-seq: * str.upcase! => str or nil * - * Upcases the contents of str, returning nil if no changes + * Upcases the contents of *str*, returning `nil` if no changes * were made. */ static mrb_value @@ -3049,7 +3029,7 @@ mrb_str_upcase_bang(mrb_state *mrb, mrb_value str) * call-seq: * str.upcase => new_str * - * Returns a copy of str with all lowercase letters replaced with their + * Returns a copy of *str* with all lowercase letters replaced with their * uppercase counterparts. The operation is locale insensitive---only * characters 'a' to 'z' are affected. * @@ -3058,9 +3038,7 @@ mrb_str_upcase_bang(mrb_state *mrb, mrb_value str) static mrb_value mrb_str_upcase(mrb_state *mrb, mrb_value self) { - mrb_value str; - - str = mrb_str_dup(mrb, self); + mrb_value str = mrb_str_dup(mrb, self); mrb_str_upcase_bang(mrb, str); return str; } @@ -3069,8 +3047,8 @@ mrb_str_upcase(mrb_state *mrb, mrb_value self) * call-seq: * str.dump -> new_str * - * Produces a version of str with all nonprinting characters replaced by - * \nnn notation and all special characters escaped. + * Produces a version of *str* with all nonprinting characters replaced by + * `\nnn` notation and all special characters escaped. */ mrb_value mrb_str_dump(mrb_state *mrb, mrb_value str) @@ -3093,8 +3071,6 @@ MRB_API mrb_value mrb_str_cat(mrb_state *mrb, mrb_value str, const char *ptr, size_t len) { struct RString *s = mrb_str_ptr(str); - mrb_int capa; - mrb_int total; ptrdiff_t off = -1; if (len == 0) return str; @@ -3103,7 +3079,8 @@ mrb_str_cat(mrb_state *mrb, mrb_value str, const char *ptr, size_t len) off = ptr - RSTR_PTR(s); } - capa = RSTR_CAPA(s); + mrb_int capa = RSTR_CAPA(s); + mrb_int total; if (mrb_int_add_overflow(RSTR_LEN(s), len, &total)) { size_error: mrb_raise(mrb, E_ARGUMENT_ERROR, "string size too big"); @@ -3228,7 +3205,7 @@ mrb_str_bytes(mrb_state *mrb, mrb_value str) * call-seq: * str.getbyte(index) -> 0 .. 255 * - * returns the indexth byte as an integer. + * returns the *index*th byte as an integer. */ static mrb_value mrb_str_getbyte(mrb_state *mrb, mrb_value str) @@ -3248,17 +3225,16 @@ mrb_str_getbyte(mrb_state *mrb, mrb_value str) * call-seq: * str.setbyte(index, integer) -> integer * - * modifies the indexth byte as integer. + * modifies the *index*th byte as *integer*. */ static mrb_value mrb_str_setbyte(mrb_state *mrb, mrb_value str) { mrb_int pos, byte; - mrb_int len; mrb_get_args(mrb, "ii", &pos, &byte); - len = RSTRING_LEN(str); + mrb_int len = RSTRING_LEN(str); if (pos < -len || len <= pos) mrb_raisef(mrb, E_INDEX_ERROR, "index %i out of string", pos); if (pos < 0) @@ -3281,8 +3257,8 @@ mrb_str_setbyte(mrb_state *mrb, mrb_value str) * objects, returns a substring starting at the offset given by the first, and * a length given by the second. If given a Range, a substring containing * bytes at offsets given by the range is returned. In all three cases, if - * an offset is negative, it is counted from the end of str. Returns - * nil if the initial offset falls outside the string, the length + * an offset is negative, it is counted from the end of *str*. Returns + * `nil` if the initial offset falls outside the string, the length * is negative, or the beginning of the range is greater than the end. * The encoding of the resulted string keeps original encoding. * @@ -3337,13 +3313,12 @@ sub_replace(mrb_state *mrb, mrb_value self) char *p, *match; mrb_int plen, mlen; mrb_int found, offset; - mrb_value result; mrb_get_args(mrb, "ssi", &p, &plen, &match, &mlen, &found); if (found < 0 || RSTRING_LEN(self) < found) { mrb_raise(mrb, E_RUNTIME_ERROR, "argument out of range"); } - result = mrb_str_new(mrb, 0, 0); + mrb_value result = mrb_str_new(mrb, 0, 0); for (mrb_int i=0; i string * bytesplice(range, str, str_range) -> string * - * Replaces some or all of the content of +self+ with +str+, and returns +self+. + * Replaces some or all of the content of `self` with `str`, and returns `self`. * The portion of the string affected is determined using - * the same criteria as String#byteslice, except that +length+ cannot be omitted. + * the same criteria as String#byteslice, except that `length` cannot be omitted. * If the replacement string is not the same length as the text it is replacing, * the string will be adjusted accordingly. * - * If +str_index+ and +str_length+, or +str_range+ are given, the content of +self+ is replaced by str.byteslice(str_index, str_length) or str.byteslice(str_range); however the substring of +str+ is not allocated as a new string. + * If `str_index` and `str_length`, or `str_range` are given, the content of `self` + * is replaced by str.byteslice(str_index, str_length) or str.byteslice(str_range); + * however the substring of `str` is not allocated as a new string. * * The form that take an Integer will raise an IndexError if the value is out * of range; the Range form will raise a RangeError. diff --git a/src/symbol.c b/src/symbol.c index 3c9e545fd..616a40de5 100644 --- a/src/symbol.c +++ b/src/symbol.c @@ -52,6 +52,32 @@ presym_sym2name(mrb_sym sym, mrb_int *lenp) #endif /* MRB_NO_PRESYM */ /* ------------------------------------------------------ */ + +/* LSB pointer tagging for literal flags */ +#define SYMTBL_LITERAL_FLAG ((uintptr_t)1) + +/* Extract clean pointer for memory operations */ +static inline const char* +symtbl_get_ptr(const char *tagged_ptr) +{ + return (const char*)((uintptr_t)tagged_ptr & ~SYMTBL_LITERAL_FLAG); +} + +/* Check if symbol is literal by testing LSB */ +static inline mrb_bool +symtbl_is_literal(const char *tagged_ptr) +{ + return ((uintptr_t)tagged_ptr & SYMTBL_LITERAL_FLAG) != 0; +} + +/* Create tagged pointer for literal string - with alignment verification */ +static inline const char* +symtbl_tag_literal(const char *ptr) +{ + mrb_assert(((uintptr_t)ptr & 1) == 0); /* Assert alignment */ + return (const char*)((uintptr_t)ptr | SYMTBL_LITERAL_FLAG); +} + static void sym_validate_len(mrb_state *mrb, size_t len) { @@ -60,6 +86,12 @@ sym_validate_len(mrb_state *mrb, size_t len) } } +/* Hash table for symbols (allocated on demand when symbols exceed threshold) */ +struct mrb_sym_hash_table { + uint8_t *symlink; /* collision resolution chains */ + mrb_sym buckets[256]; /* hash buckets */ +}; + #ifdef MRB_USE_ALL_SYMBOLS # define SYMBOL_INLINE_P(sym) FALSE # define sym_inline_pack(name, len) 0 @@ -109,17 +141,21 @@ sym_inline_unpack(mrb_sym sym, char *buf, mrb_int *lenp) } #endif -#define sym_lit_p(mrb, i) (mrb->symflags[i>>3]&(1<<(i&7))) -#define sym_lit_set(mrb, i) mrb->symflags[i>>3]|=(1<<(i&7)) -#define sym_flags_clear(mrb, i) mrb->symflags[i>>3]&=~(1<<(i&7)) +/* Check if using hash table mode */ +static inline mrb_bool +using_hash_table(mrb_state *mrb) +{ + return mrb->symhash != NULL; +} static mrb_bool sym_check(mrb_state *mrb, const char *name, size_t len, mrb_sym i) { - const char *symname = mrb->symtbl[i]; + const char *tagged_ptr = mrb->symtbl[i]; + const char *symname = symtbl_get_ptr(tagged_ptr); /* Untag for access */ size_t symlen; - if (sym_lit_p(mrb, i)) { + if (symtbl_is_literal(tagged_ptr)) { symlen = strlen(symname); } else { @@ -133,31 +169,35 @@ sym_check(mrb_state *mrb, const char *name, size_t len, mrb_sym i) } static mrb_sym -find_symbol(mrb_state *mrb, const char *name, size_t len, uint8_t *hashp) +find_symbol_linear(mrb_state *mrb, const char *name, size_t len) +{ + mrb_sym i; + + for (i = 1; i <= mrb->symidx; i++) { + if (sym_check(mrb, name, len, i)) { + return (i + MRB_PRESYM_MAX); + } + } + return 0; +} + +static mrb_sym +find_symbol_hash(mrb_state *mrb, const char *name, size_t len, uint8_t *hashp) { mrb_sym i; uint8_t hash; - -#ifndef MRB_NO_PRESYM - /* presym */ - i = presym_find(name, len); - if (i > 0) return i; -#endif - - /* inline symbol */ - i = sym_inline_pack(name, len); - if (i > 0) return i; + struct mrb_sym_hash_table *ht = mrb->symhash; hash = mrb_byte_hash((const uint8_t*)name, len); if (hashp) *hashp = hash; - i = mrb->symhash[hash]; + i = ht->buckets[hash]; if (i == 0) return 0; for (;;) { if (sym_check(mrb, name, len, i)) { return (i+MRB_PRESYM_MAX); } - uint8_t diff = mrb->symlink[i]; + uint8_t diff = ht->symlink[i]; if (diff == 0xff) { i -= 0xff; while (i > 0) { @@ -175,56 +215,170 @@ find_symbol(mrb_state *mrb, const char *name, size_t len, uint8_t *hashp) } static mrb_sym -sym_intern(mrb_state *mrb, const char *name, size_t len, mrb_bool lit) +find_symbol(mrb_state *mrb, const char *name, size_t len, uint8_t *hashp) +{ + mrb_sym i; + +#ifndef MRB_NO_PRESYM + /* presym */ + i = presym_find(name, len); + if (i > 0) return i; +#endif + + /* inline symbol */ + i = sym_inline_pack(name, len); + if (i > 0) return i; + + if (using_hash_table(mrb)) { + /* Hash table mode - O(1) average case */ + return find_symbol_hash(mrb, name, len, hashp); + } + else { + /* Linear mode - O(n) but fast for small n */ + if (hashp) *hashp = mrb_byte_hash((const uint8_t*)name, len); + return find_symbol_linear(mrb, name, len); + } +} + +static void +migrate_to_hash_table(mrb_state *mrb) +{ + struct mrb_sym_hash_table *ht; + mrb_sym i; + + mrb_assert(mrb->symhash == NULL); + mrb_assert(mrb->symidx >= MRB_SYMBOL_LINEAR_THRESHOLD); + + /* Allocate hash table structure */ + ht = (struct mrb_sym_hash_table*)mrb_calloc(mrb, 1, sizeof(struct mrb_sym_hash_table)); + ht->symlink = (uint8_t*)mrb_calloc(mrb, mrb->symcapa, sizeof(uint8_t)); + + /* Rebuild hash table from existing linear data */ + for (i = 1; i <= mrb->symidx; i++) { + const char *tagged_ptr = mrb->symtbl[i]; + const char *name = symtbl_get_ptr(tagged_ptr); + size_t len; + uint8_t hash; + + /* Get name and length from tagged pointer */ + if (symtbl_is_literal(tagged_ptr)) { + len = strlen(name); + } + else { + /* This is a packed length string */ + len = mrb_packed_int_decode((const uint8_t*)name, (const uint8_t**)&name); + } + + hash = mrb_byte_hash((const uint8_t*)name, len); + + /* Build collision chain */ + if (ht->buckets[hash] != 0) { + mrb_sym diff = i - ht->buckets[hash]; + ht->symlink[i] = (diff > 0xff) ? 0xff : (uint8_t)diff; + } + else { + ht->symlink[i] = 0; + } + ht->buckets[hash] = i; + } + + mrb->symhash = ht; +} + +static mrb_sym +sym_intern_common(mrb_state *mrb, const char *name, size_t len, mrb_bool lit) { mrb_sym sym; - uint8_t hash; - sym_validate_len(mrb, len); - sym = find_symbol(mrb, name, len, &hash); - if (sym > 0) return sym; - - /* registering a new symbol */ sym = mrb->symidx + 1; if (mrb->symcapa <= sym) { size_t symcapa = mrb->symcapa; 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->symflags = (uint8_t*)mrb_realloc(mrb, mrb->symflags, symcapa/8+1); - memset(mrb->symflags+mrb->symcapa/8+1, 0, (symcapa-mrb->symcapa)/8); - mrb->symlink = (uint8_t*)mrb_realloc(mrb, mrb->symlink, symcapa); + if (using_hash_table(mrb)) { + struct mrb_sym_hash_table *ht = mrb->symhash; + ht->symlink = (uint8_t*)mrb_realloc(mrb, ht->symlink, symcapa); + } mrb->symcapa = symcapa; } - sym_flags_clear(mrb, sym); - if ((lit || mrb_ro_data_p(name)) && name[len] == 0 && strlen(name) == len) { - sym_lit_set(mrb, sym); - mrb->symtbl[sym] = name; + + lit = lit || mrb_ro_data_p(name); + if (lit && name[len] == 0 && strlen(name) == len) { + if (((uintptr_t)name & 1) != 0) { + /* Fallback: unaligned literal, allocate heap copy */ + goto heap_allocation; + } + mrb->symtbl[sym] = symtbl_tag_literal(name); } 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 = (char*)mrb_malloc(mrb, len+ilen+1); mrb_packed_int_encode(ulen, (uint8_t*)p); memcpy(p+ilen, name, len); p[ilen+len] = 0; - mrb->symtbl[sym] = p; + mrb->symtbl[sym] = p; /* Untagged = heap */ } - if (mrb->symhash[hash]) { - mrb_sym i = sym - mrb->symhash[hash]; + + mrb->symidx = sym; + return sym; +} + +static mrb_sym +sym_intern_linear_mode(mrb_state *mrb, const char *name, size_t len, mrb_bool lit) +{ + mrb_sym sym = sym_intern_common(mrb, name, len, lit); + return (sym+MRB_PRESYM_MAX); +} + +static mrb_sym +sym_intern_hash_mode(mrb_state *mrb, const char *name, size_t len, mrb_bool lit) +{ + mrb_sym sym = sym_intern_common(mrb, name, len, lit); + struct mrb_sym_hash_table *ht = mrb->symhash; + uint8_t hash = mrb_byte_hash((const uint8_t*)name, len); + + if (ht->buckets[hash]) { + mrb_sym i = sym - ht->buckets[hash]; if (i > 0xff) - mrb->symlink[sym] = 0xff; + ht->symlink[sym] = 0xff; else - mrb->symlink[sym] = i; + ht->symlink[sym] = i; } else { - mrb->symlink[sym] = 0; + ht->symlink[sym] = 0; } - mrb->symhash[hash] = mrb->symidx = sym; + ht->buckets[hash] = sym; return (sym+MRB_PRESYM_MAX); } +static mrb_sym +sym_intern(mrb_state *mrb, const char *name, size_t len, mrb_bool lit) +{ + mrb_sym sym; + + sym_validate_len(mrb, len); + sym = find_symbol(mrb, name, len, NULL); + if (sym > 0) return sym; + + /* 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); + } + + /* Add new symbol using current mode */ + if (using_hash_table(mrb)) { + return sym_intern_hash_mode(mrb, name, len, lit); + } + else { + return sym_intern_linear_mode(mrb, name, len, lit); + } +} + /* * Interns a string, creating a symbol from it if it doesn't already exist, * or returning the existing symbol if it does. @@ -411,8 +565,10 @@ sym2name_len(mrb_state *mrb, mrb_sym sym, char *buf, mrb_int *lenp) return NULL; } - const char *symname = mrb->symtbl[sym]; - if (!sym_lit_p(mrb, sym)) { + const char *tagged_ptr = mrb->symtbl[sym]; + const char *symname = symtbl_get_ptr(tagged_ptr); /* Untag for access */ + + if (!symtbl_is_literal(tagged_ptr)) { uint32_t len = mrb_packed_int_decode((const uint8_t*)symname, (const uint8_t**)&symname); if (lenp) *lenp = (mrb_int)len; } @@ -451,33 +607,43 @@ mrb_free_symtbl(mrb_state *mrb) mrb_sym i, lim; for (i=1,lim=mrb->symidx+1; isymtbl[i]); + const char *tagged_ptr = mrb->symtbl[i]; + if (!symtbl_is_literal(tagged_ptr)) { + /* CRITICAL: Untag before mrb_free */ + const char *clean_ptr = symtbl_get_ptr(tagged_ptr); + mrb_free(mrb, (char*)clean_ptr); } } mrb_free(mrb, (void*)mrb->symtbl); - mrb_free(mrb, (void*)mrb->symlink); - mrb_free(mrb, (void*)mrb->symflags); + + /* Free hash table if allocated */ + if (mrb->symhash) { + mrb_free(mrb, mrb->symhash->symlink); + mrb_free(mrb, mrb->symhash); + mrb->symhash = NULL; + } } void mrb_init_symtbl(mrb_state *mrb) { + /* Initialize in linear mode - hash table allocated on demand */ + mrb->symhash = NULL; } /********************************************************************** * Document-class: Symbol * - * Symbol objects represent names and some strings + * `Symbol` objects represent names and some strings * inside the Ruby - * interpreter. They are generated using the :name and - * :"string" literals - * syntax, and by the various to_sym methods. The same - * Symbol object will be created for a given name or string + * interpreter. They are generated using the `:name` and + * `:"string"` literals + * syntax, and by the various `to_sym` methods. The same + * `Symbol` object will be created for a given name or string * for the duration of a program's execution, regardless of the context - * or meaning of that name. Thus if Fred is a constant in + * or meaning of that name. Thus if `Fred` is a constant in * one context, a method in another, and a class in a third, the - * Symbol :Fred will be the same object in + * `Symbol` `:Fred` will be the same object in * all three contexts. * * module One @@ -504,7 +670,7 @@ mrb_init_symtbl(mrb_state *mrb) * call-seq: * sym.to_s -> string * - * Returns the name or string corresponding to sym. + * Returns the name or string corresponding to *sym*. * * :fred.to_s #=> "fred" */ @@ -518,7 +684,7 @@ sym_to_s(mrb_state *mrb, mrb_value sym) * call-seq: * sym.name -> string * - * Returns the name or string corresponding to sym. Unlike #to_s, the + * Returns the name or string corresponding to *sym*. Unlike #to_s, the * returned string is frozen. * * :fred.name #=> "fred" @@ -546,8 +712,8 @@ sym_name(mrb_state *mrb, mrb_value vsym) * sym.to_sym -> sym * sym.intern -> sym * - * In general, to_sym returns the Symbol corresponding - * to an object. As sym is already a symbol, self is returned + * In general, `to_sym` returns the `Symbol` corresponding + * to an object. As *sym* is already a symbol, `self` is returned * in this case. */ @@ -556,7 +722,7 @@ sym_name(mrb_state *mrb, mrb_value vsym) * call-seq: * sym.inspect -> string * - * Returns the representation of sym as a symbol literal. + * Returns the representation of *sym* as a symbol literal. * * :fred.inspect #=> ":fred" */ @@ -680,15 +846,12 @@ id: static mrb_value sym_inspect(mrb_state *mrb, mrb_value sym) { - mrb_value str; - const char *name; - mrb_int len; mrb_sym id = mrb_symbol(sym); - char *sp; + mrb_int len; + const char *name = mrb_sym_name_len(mrb, id, &len); + mrb_value str = mrb_str_new(mrb, NULL, len+1); + char *sp = RSTRING_PTR(str); - name = mrb_sym_name_len(mrb, id, &len); - str = mrb_str_new(mrb, NULL, len+1); - sp = RSTRING_PTR(str); sp[0] = ':'; memcpy(sp+1, name, len); mrb_assert_int_fit(mrb_int, len, size_t, SIZE_MAX); diff --git a/src/variable.c b/src/variable.c index e40ede909..90557fdd4 100644 --- a/src/variable.c +++ b/src/variable.c @@ -19,9 +19,7 @@ typedef struct iv_tbl { mrb_value *ptr; } iv_tbl; -#define IV_EMPTY 0 -#define IV_DELETED (1UL<<31) -#define IV_KEY_P(k) (((k)&~((uint32_t)IV_DELETED))!=0) + /* Creates the instance variable table. */ static iv_tbl* @@ -49,7 +47,7 @@ iv_rehash(mrb_state *mrb, iv_tbl *t) mrb_value *old_ptr = t->ptr; /* allocate exactly the same total shape as before */ - t->ptr = (mrb_value*)mrb_calloc(mrb, sizeof(mrb_value)+sizeof(mrb_sym), new_alloc); + t->ptr = (mrb_value*)mrb_calloc(mrb, new_alloc, sizeof(mrb_value)+sizeof(mrb_sym)); /* size remains unchanged, alloc grows */ t->alloc = new_alloc; @@ -90,7 +88,7 @@ bsearch_idx(mrb_sym *keys, int size, mrb_sym target) { n -= half; } /* Final adjustment: if the remaining element is still less than target, advance by one */ - return (int)(p - keys) + (keys[0] < target); + return (int)(p - keys) + (p[0] < target); } /* Set (insert or update) the value for `sym` in the instance variable table using branch-free search. */ @@ -221,7 +219,7 @@ iv_copy(mrb_state *mrb, iv_tbl *t) t2->size = t->size; /* allocate the same block shape */ - t2->ptr = (mrb_value*)mrb_calloc(mrb, sizeof(mrb_value)+sizeof(mrb_sym), t2->alloc); + t2->ptr = (mrb_value*)mrb_calloc(mrb, t2->alloc, sizeof(mrb_value)+sizeof(mrb_sym)); /* copy values[0...size] and keys[0...size] */ memcpy(t2->ptr, t->ptr, sizeof(mrb_value)*t2->size); @@ -540,10 +538,9 @@ mrb_iv_defined(mrb_state *mrb, mrb_value obj, mrb_sym sym) MRB_API mrb_bool mrb_iv_name_sym_p(mrb_state *mrb, mrb_sym iv_name) { - const char *s; mrb_int len; + const char *s = mrb_sym_name_len(mrb, iv_name, &len); - s = mrb_sym_name_len(mrb, iv_name, &len); if (len < 2) return FALSE; if (s[0] != '@') return FALSE; if (ISDIGIT(s[1])) return FALSE; @@ -628,12 +625,10 @@ mrb_iv_remove(mrb_state *mrb, mrb_value obj, mrb_sym sym) static int iv_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) { - mrb_value ary; - const char* s; + mrb_value ary = *(mrb_value*)p; mrb_int len; + const char* s = mrb_sym_name_len(mrb, sym, &len); - ary = *(mrb_value*)p; - s = mrb_sym_name_len(mrb, sym, &len); if (len > 1 && s[0] == '@' && s[1] != '@') { mrb_ary_push(mrb, ary, mrb_symbol_value(sym)); } @@ -660,9 +655,8 @@ iv_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) mrb_value mrb_obj_instance_variables(mrb_state *mrb, mrb_value self) { - mrb_value ary; + mrb_value ary = mrb_ary_new(mrb); - ary = mrb_ary_new(mrb); if (obj_iv_p(self)) { iv_foreach(mrb, mrb_obj_ptr(self)->iv, iv_i, &ary); } @@ -672,12 +666,10 @@ mrb_obj_instance_variables(mrb_state *mrb, mrb_value self) static int cv_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) { - mrb_value ary; - const char* s; + mrb_value ary = *(mrb_value*)p; mrb_int len; + const char* s = mrb_sym_name_len(mrb, sym, &len); - ary = *(mrb_value*)p; - s = mrb_sym_name_len(mrb, sym, &len); if (len > 2 && s[0] == '@' && s[1] == '@') { mrb_ary_push(mrb, ary, mrb_symbol_value(sym)); } @@ -689,7 +681,7 @@ cv_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) * call-seq: * mod.class_variables(inherit=true) -> array * - * Returns an array of the names of class variables in mod. + * Returns an array of the names of class variables in *mod*. * * class One * @@var1 = 1 @@ -703,13 +695,11 @@ cv_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) mrb_value mrb_mod_class_variables(mrb_state *mrb, mrb_value mod) { - mrb_value ary; - struct RClass *c; mrb_bool inherit = TRUE; mrb_get_args(mrb, "|b", &inherit); - ary = mrb_ary_new(mrb); - c = mrb_class_ptr(mod); + mrb_value ary = mrb_ary_new(mrb); + struct RClass *c = mrb_class_ptr(mod); while (c) { iv_foreach(mrb, class_iv_ptr(c), cv_i, &ary); if (!inherit) break; @@ -733,9 +723,8 @@ mrb_mod_cv_get(mrb_state *mrb, struct RClass *c, mrb_sym sym) } if (given) return v; if (cls->tt == MRB_TT_SCLASS) { - mrb_value klass; + mrb_value klass = mrb_obj_iv_get(mrb, (struct RObject*)cls, MRB_SYM(__attached__)); - klass = mrb_obj_iv_get(mrb, (struct RObject*)cls, MRB_SYM(__attached__)); c = mrb_class_ptr(klass); if (c->tt == MRB_TT_CLASS || c->tt == MRB_TT_MODULE) { given = FALSE; @@ -807,9 +796,8 @@ mrb_mod_cv_set(mrb_state *mrb, struct RClass *c, mrb_sym sym, mrb_value v) } if (cls->tt == MRB_TT_SCLASS) { - mrb_value klass; + mrb_value klass = mrb_obj_iv_get(mrb, (struct RObject*)cls, MRB_SYM(__attached__)); - klass = mrb_obj_iv_get(mrb, (struct RObject*)cls, MRB_SYM(__attached__)); switch (mrb_type(klass)) { case MRB_TT_CLASS: case MRB_TT_MODULE: @@ -1077,8 +1065,10 @@ mrb_const_set(mrb_state *mrb, mrb_value mod, mrb_sym sym, mrb_value v) } mrb_obj_iv_set(mrb, mrb_obj_ptr(mod), sym, v); - mrb_value name = mrb_symbol_value(sym); - mrb_funcall_argv(mrb, mod, MRB_SYM(const_added), 1, &name); + if (!mrb->bootstrapping) { + mrb_value name = mrb_symbol_value(sym); + mrb_funcall_argv(mrb, mod, MRB_SYM(const_added), 1, &name); + } } /* @@ -1157,12 +1147,10 @@ mrb_define_global_const(mrb_state *mrb, const char *name, mrb_value val) static int const_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) { - mrb_value ary; - const char* s; + mrb_value ary = *(mrb_value*)p; mrb_int len; + const char* s = mrb_sym_name_len(mrb, sym, &len); - ary = *(mrb_value*)p; - s = mrb_sym_name_len(mrb, sym, &len); if (len >= 1 && ISUPPER(s[0])) { mrb_int i, alen = RARRAY_LEN(ary); @@ -1194,12 +1182,11 @@ mrb_mod_const_at(mrb_state *mrb, struct RClass *c, mrb_value ary) mrb_value mrb_mod_constants(mrb_state *mrb, mrb_value mod) { - mrb_value ary; mrb_bool inherit = TRUE; struct RClass *c = mrb_class_ptr(mod); mrb_get_args(mrb, "|b", &inherit); - ary = mrb_ary_new(mrb); + mrb_value ary = mrb_ary_new(mrb); while (c) { mrb_mod_const_at(mrb, c, ary); if (!inherit) break; @@ -1268,9 +1255,7 @@ mrb_gv_remove(mrb_state *mrb, mrb_sym sym) static int gv_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p) { - mrb_value ary; - - ary = *(mrb_value*)p; + mrb_value ary = *(mrb_value*)p; mrb_ary_push(mrb, ary, mrb_symbol_value(sym)); return 0; } @@ -1412,9 +1397,7 @@ find_class_sym(mrb_state *mrb, struct RClass *outer, struct RClass *c) static struct RClass* outer_class(mrb_state *mrb, struct RClass *c) { - mrb_value ov; - - ov = mrb_obj_iv_get(mrb, (struct RObject*)c, MRB_SYM(__outer__)); + mrb_value ov = mrb_obj_iv_get(mrb, (struct RObject*)c, MRB_SYM(__outer__)); if (mrb_nil_p(ov)) return NULL; switch (mrb_type(ov)) { case MRB_TT_CLASS: @@ -1445,23 +1428,20 @@ detect_outer_loop(mrb_state *mrb, struct RClass *c) mrb_value mrb_class_find_path(mrb_state *mrb, struct RClass *c) { - struct RClass *outer; - mrb_value path; - mrb_sym name; - const char *str; - mrb_int len; - if (detect_outer_loop(mrb, c)) return mrb_nil_value(); - outer = outer_class(mrb, c); + struct RClass *outer = outer_class(mrb, c); if (outer == NULL) return mrb_nil_value(); - name = find_class_sym(mrb, outer, c); + + mrb_sym name = find_class_sym(mrb, outer, c); if (name == 0) return mrb_nil_value(); - path = mrb_str_new_capa(mrb, 40); - str = mrb_class_name(mrb, outer); - mrb_str_cat_cstr(mrb, path, str); + + mrb_value path = mrb_str_new_capa(mrb, 40); + const char *cname = mrb_class_name(mrb, outer); + mrb_str_cat_cstr(mrb, path, cname); mrb_str_cat_cstr(mrb, path, "::"); - str = mrb_sym_name_len(mrb, name, &len); + mrb_int len; + const char *str = mrb_sym_name_len(mrb, name, &len); mrb_str_cat(mrb, path, str, len); if (RSTRING_PTR(path)[0] != '#') { iv_del(mrb, c->iv, MRB_SYM(__outer__), NULL); diff --git a/src/vm.c b/src/vm.c index 95eec8041..ca14aef20 100644 --- a/src/vm.c +++ b/src/vm.c @@ -1004,7 +1004,7 @@ send_method(mrb_state *mrb, mrb_value self, mrb_bool pub) if (m.flags & MRB_METHOD_PRIVATE_FL) { vis_err:; if (n == 15) { - n = RARRAY_LEN(regs[0]) - 1; + n = (int)(RARRAY_LEN(regs[0]) - 1); regs = RARRAY_PTR(regs[0]); } vis_error(mrb, name, mrb_ary_new_from_values(mrb, n, regs+1), self, priv); @@ -1059,8 +1059,8 @@ send_method(mrb_state *mrb, mrb_value self, mrb_bool pub) * obj.__send__(symbol [, args...]) -> obj * * Invokes the method identified by _symbol_, passing it any - * arguments specified. You can use __send__ if the name - * +send+ clashes with an existing method in _obj_. + * arguments specified. You can use `__send__` if the name + * `send` clashes with an existing method in _obj_. * * class Klass * def hello(*args) @@ -1146,7 +1146,7 @@ eval_under(mrb_state *mrb, mrb_value self, mrb_value blk, struct RClass *c) * mod.module_eval {| | block } -> obj * * Evaluates block in the context of _mod_. This can - * be used to add methods to a class. module_eval returns + * be used to add methods to a class. `module_eval` returns * the result of evaluating its argument. */ mrb_value @@ -1166,10 +1166,10 @@ mrb_mod_module_eval(mrb_state *mrb, mrb_value mod) * obj.instance_eval {| | block } -> obj * * Evaluates the given block,within the context of the receiver (_obj_). - * In order to set the context, the variable +self+ is set to _obj_ while + * In order to set the context, the variable `self` is set to _obj_ while * the code is executing, giving the code access to _obj_'s - * instance variables. In the version of instance_eval - * that takes a +String+, the optional second and third + * instance variables. In the version of `instance_eval` + * that takes a `String`, the optional second and third * parameters supply a filename and starting line number that are used * when reporting compilation errors. * @@ -1521,19 +1521,45 @@ prepare_tagged_break(mrb_state *mrb, uint32_t tag, const mrb_callinfo *return_ci #define CASE(insn,ops) case insn: { const mrb_code *pc = ci->pc+1; FETCH_ ## ops (); ci->pc = pc; } L_ ## insn ## _BODY: #define NEXT goto L_END_DISPATCH #define JUMP NEXT +#ifdef MRB_USE_TASK_SCHEDULER +#define END_DISPATCH L_END_DISPATCH: \ + if (mrb->task.switching || mrb->c->status == MRB_TASK_STOPPED) \ + return mrb_nil_value(); \ + }} +#else #define END_DISPATCH L_END_DISPATCH:;}} +#endif #else #define INIT_DISPATCH JUMP; return mrb_nil_value(); #define CASE(insn,ops) L_ ## insn: { const mrb_code *pc = ci->pc+1; FETCH_ ## ops (); ci->pc = pc; } L_ ## insn ## _BODY: +#ifdef MRB_USE_TASK_SCHEDULER +#define NEXT if (mrb->task.switching || mrb->c->status == MRB_TASK_STOPPED) return mrb_nil_value(); \ + insn=BYTECODE_DECODER(*ci->pc); CODE_FETCH_HOOK(mrb, irep, ci->pc, regs); goto *optable[insn] +#else #define NEXT insn=BYTECODE_DECODER(*ci->pc); CODE_FETCH_HOOK(mrb, irep, ci->pc, regs); goto *optable[insn] +#endif #define JUMP NEXT +#ifdef MRB_USE_TASK_SCHEDULER +#define END_DISPATCH \ + if (mrb->task.switching || mrb->c->status == MRB_TASK_STOPPED) \ + return mrb_nil_value(); +#else #define END_DISPATCH +#endif #endif +#ifdef MRB_USE_TASK_SCHEDULER +#define TASK_STOP(mrb) \ + if (mrb->c->status != MRB_TASK_STOPPED) \ + mrb->c->status = MRB_TASK_STOPPED; +#else +#define TASK_STOP(mrb) +#endif + /** * @brief Executes a mruby bytecode sequence (iseq) within the VM. * @@ -1683,7 +1709,21 @@ RETRY_TRY_BLOCK: goto L_BREAK; goto L_RAISE; } + /* Intentionally store stack variable address for exception handling. + * This is safe because the pointer is cleared before function returns. + * Suppress GCC 12+ warning about dangling pointer. */ +#if defined(__GNUC__) && !defined(__clang__) + #if __GNUC__ >= 12 + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdangling-pointer" + #endif +#endif mrb->jmp = &c_jmp; +#if defined(__GNUC__) && !defined(__clang__) + #if __GNUC__ >= 12 + #pragma GCC diagnostic pop + #endif +#endif INIT_DISPATCH { CASE(OP_NOP, Z) { @@ -1718,7 +1758,7 @@ RETRY_TRY_BLOCK: #ifdef MRB_USE_BIGINT { const char *s = irep->pool[b].u.str; - regs[a] = mrb_bint_new_str(mrb, s+2, (uint8_t)s[0], s[1]); + regs[a] = mrb_bint_new_str(mrb, s+2, (uint8_t)s[0], (int8_t)s[1]); } break; #else @@ -2400,7 +2440,7 @@ RETRY_TRY_BLOCK: kdict = regs[mrb_ci_kidx(ci)]; } if (!kd) { - if (!mrb_nil_p(kdict) && mrb_hash_size(mrb, kdict) > 0) { + 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 */ @@ -3006,14 +3046,12 @@ RETRY_TRY_BLOCK: mrb_value v = regs[a]; int pre = b; int post = c; - struct RArray *ary; - int len, idx; if (!mrb_array_p(v)) { v = ary_new_from_regs(mrb, 1, a); } - ary = mrb_ary_ptr(v); - len = (int)ARY_LEN(ary); + struct RArray *ary = mrb_ary_ptr(v); + int len = (int)ARY_LEN(ary); if (len > pre + post) { v = mrb_ary_new_from_values(mrb, len - pre - post, ARY_PTR(ary)+pre); regs[a++] = v; @@ -3024,6 +3062,8 @@ RETRY_TRY_BLOCK: else { v = mrb_ary_new_capa(mrb, 0); regs[a++] = v; + + int idx; for (idx=0; idx+prejmp = prev_jmp; if (!mrb_nil_p(v)) { mrb->exc = mrb_obj_ptr(v); + TASK_STOP(mrb); return v; } mrb->exc = NULL; + TASK_STOP(mrb); return regs[irep->nlocals]; } } diff --git a/tasks/mrbgems.rake b/tasks/mrbgems.rake index 272d476e6..55a49c61d 100644 --- a/tasks/mrbgems.rake +++ b/tasks/mrbgems.rake @@ -2,8 +2,7 @@ MRuby.each_target do active_gems_txt = "#{build_dir}/mrbgems/active_gems.txt" if enable_gems? - # set up all gems - gems.each(&:setup) + gems.setup_build gems.check self # loader all gems diff --git a/test/assert.rb b/test/assert.rb index 7563099e8..b5e61e740 100644 --- a/test/assert.rb +++ b/test/assert.rb @@ -245,25 +245,25 @@ def _assert_operator(affirmed, obj1, op, obj2 = $undefined, msg = nil) end ## -# Fail unless +str+ matches against +pattern+. +# Fail unless `str` matches against `pattern`. # -# +pattern+ is interpreted as pattern for File.fnmatch?. It may contain the +# `pattern` is interpreted as pattern for File.fnmatch?. It may contain the # following metacharacters: # -# * :: +# `*` :: # Matches any string. # -# ? :: +# `?` :: # Matches any one character. # -# [_SET_], [^_SET_] ([!_SET_]) :: +# `[_SET_]`, `[^_SET_]` (`[!_SET_]`) :: # Matches any one character in _SET_. Behaves like character sets in -# Regexp, including set negation ([^a-z]). +# Regexp, including set negation (`[^a-z]`). # -# {_A_,_B_} :: +# `{_A_,_B_}` :: # Matches pattern _A_ or pattern _B_. # -# \ :: +# ` \ ` :: # Escapes the next character. def assert_match(*args); _assert_match(true, *args) end def assert_not_match(*args); _assert_match(false, *args) end @@ -275,7 +275,7 @@ def _assert_match(affirmed, pattern, str, msg = nil) end ## -# Fails unless +obj+ is a kind of +cls+. +# Fails unless `obj` is a kind of `cls`. def assert_kind_of(cls, obj, msg = nil) unless ret = obj.kind_of?(cls) diff = " Expected #{obj.inspect} to be a kind of #{cls}, not #{obj.class}." @@ -284,7 +284,7 @@ def assert_kind_of(cls, obj, msg = nil) end ## -# Fails unless +exp+ is equal to +act+ in terms of a Float +# Fails unless `exp` is equal to `act` in terms of a Float def assert_float(exp, act, msg = nil) e, a = exp.to_f, act.to_f if e.finite? && a.finite? && (n = (e - a).abs) > Mrbtest::FLOAT_TOLERANCE diff --git a/test/t/array.rb b/test/t/array.rb index 52ae7ccb0..ab34264f0 100644 --- a/test/t/array.rb +++ b/test/t/array.rb @@ -468,3 +468,11 @@ assert('Array#delete') do assert_equal nil, a.delete(nil) { "?" } assert_equal [], a end + +assert('Array#hash with self-referencing arrays') do + a = [] + a << a + b = [] + b << b + assert_equal a.hash, b.hash +end diff --git a/tools/lrama/template/bison/yacc.c b/tools/lrama/template/bison/yacc.c index 5577c5f07..6edd59a0d 100644 --- a/tools/lrama/template/bison/yacc.c +++ b/tools/lrama/template/bison/yacc.c @@ -2065,3 +2065,4 @@ yyreturnlab: #line <%= output.aux.epilogue_first_lineno - 1 %> "<%= output.grammar_file_path %>" <%= output.aux.epilogue -%> <%- end -%> +