mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
Merge branch 'master' into manual-hooks
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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]')
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ benchmark/**/*.dat
|
||||
benchmark/*.pdf
|
||||
benchmark/*.png
|
||||
bin
|
||||
build
|
||||
/build
|
||||
doc/api
|
||||
doc/capi
|
||||
compile_commands.json
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 <matz@ruby.or.jp>
|
||||
- **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.
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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 <target>"
|
||||
@echo
|
||||
@echo "Available targets:"
|
||||
@grep -E '^[a-z_-]+:.*##' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## *"}; {printf " $(CYAN)%-20s$(RESET) %s\n", $$1, $$2}'
|
||||
|
||||
@@ -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
|
||||
|
||||
+44
-10
@@ -2,19 +2,53 @@
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you have any security concern, contact <matz@ruby.or.jp>.
|
||||
To report a security vulnerability, please email the mruby team at <matz@ruby.or.jp>. 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+25
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+63
-4
@@ -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. |
|
||||
* | <code>\|</code> | 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))
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
/*
|
||||
|
||||
+2
-40
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
+264
-113
@@ -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; i<old_n_buckets; i++) { \
|
||||
if (!__ac_iseither(old_ed_flags, i)) { \
|
||||
khint_t k = kh_put_##name(mrb, &hh, old_keys[i], NULL); \
|
||||
if (kh_is_map) kh_value(&hh,k) = old_vals[i]; \
|
||||
} \
|
||||
} \
|
||||
/* copy hh to h */ \
|
||||
*h = hh; \
|
||||
mrb_free(mrb, old_keys); \
|
||||
} \
|
||||
kh__rebuild_##name(mrb, h, new_n_buckets); \
|
||||
} \
|
||||
khint_t kh_put_##name(mrb_state *mrb, kh_##name##_t *h, khkey_t key, int *ret) \
|
||||
{ \
|
||||
if (kh__is_small_##name(h)) { \
|
||||
return kh__put_small_##name(mrb, h, key, ret); \
|
||||
} \
|
||||
khint_t k, del_k, step = 0; \
|
||||
if (h->size >= 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 */
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+5
-1
@@ -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
|
||||
|
||||
@@ -144,13 +144,13 @@ module MRuby
|
||||
#
|
||||
# === Example of +.d+ file
|
||||
#
|
||||
# ==== Without <tt>-MP</tt> 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 <tt>-MP</tt> compiler flag
|
||||
# ==== With `-MP` compiler flag
|
||||
#
|
||||
# /build/host/src/array.o: /src/array.c \
|
||||
# /include/mruby/common.h /include/mruby/value.h \
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+43
-5
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 <mruby.h>
|
||||
#include "dir_hal.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef HAVE_SYS_PARAM_H
|
||||
#include <sys/param.h>
|
||||
#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 */
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 <mruby.h>
|
||||
#include "io_hal.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pwd.h>
|
||||
|
||||
#ifndef __DJGPP__
|
||||
#include <libgen.h>
|
||||
#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 */
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 <mruby.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/error.h>
|
||||
#include <mruby/presym.h>
|
||||
#include "socket_hal.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
}
|
||||
@@ -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`
|
||||
@@ -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
|
||||
@@ -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 <mruby.h>
|
||||
#include "task_hal.h"
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* 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 */
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 <mruby.h>
|
||||
#include "dir_hal.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <direct.h>
|
||||
#include <io.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
@@ -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
|
||||
@@ -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 <mruby.h>
|
||||
#include "io_hal.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <direct.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* 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 */
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 <mruby.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/error.h>
|
||||
#include <mruby/presym.h>
|
||||
#include "socket_hal.h"
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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 <mruby.h>
|
||||
#include "task_hal.h"
|
||||
#include <windows.h>
|
||||
#include <timeapi.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* 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 */
|
||||
}
|
||||
@@ -4,8 +4,8 @@ class Array
|
||||
# ary.uniq! -> ary or nil
|
||||
# ary.uniq! { |item| ... } -> ary or nil
|
||||
#
|
||||
# Removes duplicate elements from +self+.
|
||||
# Returns <code>nil</code> 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 <i>idx</i>
|
||||
# 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 <i>array</i> as an array of
|
||||
# <tt>[key, value]</tt> pairs. If a block is given, it should
|
||||
# return <tt>[key, value]</tt> 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 <code>IndexError</code> when one of indexes can't be found.
|
||||
# Also see <code>Array#values_at</code> and <code>Array#fetch</code>.
|
||||
# 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
|
||||
|
||||
+630
-430
File diff suppressed because it is too large
Load Diff
+2056
-420
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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; wcnt<mrdb->wcnt; wcnt++) {
|
||||
mrb_value expr = mrb_str_new_cstr(mrb, NULL);
|
||||
for (uint8_t wcnt=1; wcnt<mrdb->wcnt; 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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,11 +6,17 @@
|
||||
#include <mruby/opcode.h>
|
||||
#include <mruby/presym.h>
|
||||
|
||||
/* 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)
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
#include <mruby/presym.h>
|
||||
|
||||
#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
|
||||
|
||||
@@ -4,9 +4,9 @@ module Comparable
|
||||
# obj.clamp(min, max) -> obj
|
||||
# obj.clamp(range) -> obj
|
||||
#
|
||||
# In <code>(min, max)</code> form, returns _min_ if _obj_
|
||||
# <code><=></code> _min_ is less than zero, _max_ if _obj_
|
||||
# <code><=></code> _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 <code>(range)</code> form, returns _range.begin_ if _obj_
|
||||
# <code><=></code> _range.begin_ is less than zero, _range.end_
|
||||
# if _obj_ <code><=></code> _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
|
||||
|
||||
+3093
-2028
File diff suppressed because it is too large
Load Diff
@@ -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 */
|
||||
|
||||
+1814
-1365
File diff suppressed because it is too large
Load Diff
+3662
-3187
File diff suppressed because it is too large
Load Diff
@@ -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__
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <mruby/numeric.h>
|
||||
#include <mruby/internal.h>
|
||||
#include <mruby/presym.h>
|
||||
#include <float.h>
|
||||
|
||||
#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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -269,12 +269,12 @@ make_data_class(mrb_state *mrb, mrb_value members, struct RClass *klass)
|
||||
* call-seq:
|
||||
* DataClass.define(arg, ...) -> obj
|
||||
*
|
||||
* <code>Data::define</code> returns a new <code>Class</code> 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
|
||||
* <code>ArgumentError</code>.
|
||||
* `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 <code>true</code> if <i>other_data</i> is
|
||||
* Equality---Returns `true` if *other_data* is
|
||||
* equal to this one: they must be of the same class as generated by
|
||||
* <code>Data::define</code>, and all values of must be equal
|
||||
* (according to <code>Object#==</code>).
|
||||
* `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 <code>Object#eql?</code>).
|
||||
* 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 <code>Data</code> 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 <code>Data</code> 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
|
||||
* "<i>Customer</i>Class," and we'll show an example instance of that
|
||||
* class as "<i>Customer</i>Inst."
|
||||
* "*Customer*Class," and we'll show an example instance of that
|
||||
* class as "*Customer*Inst."
|
||||
*
|
||||
* In the descriptions that follow, the parameter <i>symbol</i> refers
|
||||
* to a symbol (such as <code>:name</code>).
|
||||
* In the descriptions that follow, the parameter *symbol* refers
|
||||
* to a symbol (such as `:name`).
|
||||
*/
|
||||
void
|
||||
mrb_mruby_data_gem_init(mrb_state* mrb)
|
||||
|
||||
@@ -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 <mruby.h>
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <errno.h>
|
||||
#include <io.h> /* _findfirst and _findnext set errno iff they return -1 */
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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.
|
||||
|
||||
*/
|
||||
+242
-77
@@ -5,41 +5,21 @@
|
||||
*/
|
||||
|
||||
#include <mruby.h>
|
||||
#include <mruby/array.h>
|
||||
#include <mruby/class.h>
|
||||
#include <mruby/data.h>
|
||||
#include <mruby/error.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/presym.h>
|
||||
#include <sys/types.h>
|
||||
#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 <direct.h>
|
||||
#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 <sys/param.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include "dir_hal.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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 #=> #<Dir:testdir>
|
||||
* 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) #=> #<Dir:testdir>
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -5,42 +5,24 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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 <io.h>
|
||||
#include <direct.h>
|
||||
#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 <unistd.h>
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
#include <mruby.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/variable.h>
|
||||
#include "dir_hal.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <io.h>
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
# #=> "#<Enumerator::Chain: [1..3, [4, 5]]>"
|
||||
#
|
||||
def inspect
|
||||
"#<#{self.class}: #{@enums.inspect}>"
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ module Enumerable
|
||||
# call-seq:
|
||||
# enum.drop(n) -> array
|
||||
#
|
||||
# Drops first n elements from <i>enum</i>, 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 <i>enum</i>.
|
||||
# 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 <i>enum</i>
|
||||
# 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 <i>enum</i> using a set of keys generated by mapping the
|
||||
# values in <i>enum</i> 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 <code>nil</code>, 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
|
||||
# <em>block</em> once for every element in <i>enum</i>.
|
||||
# <em>block</em> 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 <i>enum</i> 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 <i>enum</i> 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 <code>Comparable</code>; the second uses the
|
||||
# objects implement `Comparable`; the second uses the
|
||||
# block to return <em>a <=> b</em>.
|
||||
#
|
||||
# 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
|
||||
# <i>enum</i> 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 <code>true</code> if the block never returns <code>true</code>
|
||||
# for all elements. If the block is not given, <code>none?</code> will return
|
||||
# <code>true</code> 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
|
||||
# <code>pattern === element</code> 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 <code>true</code> if the block returns <code>true</code>
|
||||
# exactly once. If the block is not given, <code>one?</code> will return
|
||||
# <code>true</code> 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
|
||||
# <code>pattern === element</code> 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 <code>true</code> if the block never returns
|
||||
# <code>false</code> or <code>nil</code>. If the block is not given,
|
||||
# Ruby adds an implicit block of <code>{ |obj| obj }</code> 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
|
||||
# <code>pattern === element</code> 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 <code>true</code> if the block ever returns a value other
|
||||
# than <code>false</code> or <code>nil</code>. If the block is not
|
||||
# given, Ruby adds an implicit block of <code>{ |obj| obj }</code> 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
|
||||
# <code>pattern === element</code> 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 <i>block</i> for each element of <i>enum</i> 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 <i>enum</i> 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 <i>enum</i> with <em>value</em> or passes
|
||||
# Compares each entry in *enum* with <em>value</em> or passes
|
||||
# to <em>block</em>. Returns the index for the first for which the
|
||||
# evaluated value is non-false. If no object matches, returns
|
||||
# <code>nil</code>
|
||||
# `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 <i>enum</i> and merges corresponding
|
||||
# elements from each <i>args</i>. This generates a sequence of
|
||||
# Takes one element from *enum* and merges corresponding
|
||||
# elements from each *args*. This generates a sequence of
|
||||
# <em>n</em>-element arrays, where <em>n</em> is one more than the
|
||||
# count of arguments. The length of the resulting sequence will be
|
||||
# <code>enum#size</code>. If the size of any argument is less than
|
||||
# <code>enum#size</code>, <code>nil</code> 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 <i>enum</i> as a list of
|
||||
# <tt>[key, value]</tt> 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}
|
||||
|
||||
@@ -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 }
|
||||
# #=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>: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
|
||||
|
||||
@@ -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 #=> "#<Enumerator: [1, 2, 3]:each>"
|
||||
# [1, 2, 3].map.inspect #=> "#<Enumerator: [1, 2, 3]:map>"
|
||||
#
|
||||
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 <code>yield</code> and <code>yield
|
||||
# nil</code>.
|
||||
# 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 <tt>:_alone</tt> 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 <tt>:_separator</tt> 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 <code>false</code>.
|
||||
# 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 <code>true</code> instead of <code>false</code>.
|
||||
# returns `true` instead of `false`.
|
||||
#
|
||||
def chunk_while(&block)
|
||||
enum = self
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#include <mruby.h>
|
||||
#include <mruby/error.h>
|
||||
|
||||
/* 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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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 <code>Fiber#resume</code> method.
|
||||
* resumed using `Fiber#resume` method.
|
||||
* The code running inside the fiber can give up control by calling
|
||||
* <code>Fiber.yield</code> in which case it yields control back to caller
|
||||
* (the caller of the <code>Fiber#resume</code>).
|
||||
* `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 <code>Fiber#resume</code> method accepts an arbitrary number of
|
||||
* parameters, if it is the first call to <code>resume</code> 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 <code>Fiber.yield</code>
|
||||
* 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 <code>Fiber.yield</code>
|
||||
* 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
|
||||
* <code>resume</code>. Arguments passed to resume will be the value of
|
||||
* the <code>Fiber.yield</code> expression or will be passed as block
|
||||
* parameters to the fiber's block if this is the first <code>resume</code>.
|
||||
* `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 <code>Fiber.yield</code> 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
|
||||
* <code>Fiber.yield</code>
|
||||
* `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 <code>resume</code> 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 <code>Fiber.yield</code> 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 <code>resume</code> is called next.
|
||||
* Any arguments passed to the next <code>resume</code> 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 <code>mrb_funcall()</code>.
|
||||
* Use <code>mrb_fiber_yield()</code> 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)
|
||||
|
||||
@@ -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 <code>KeyError</code> exception; if <i>default</i> 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 <i>hsh</i> for which <i>block</i>
|
||||
# evaluates to <code>true</code>.
|
||||
# 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
|
||||
# <i>level</i> 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 <i>hsh</i>'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 <i>hsh</i> for which <i>block</i>
|
||||
# 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 <code>true</code> if <i>hash</i> is subset of
|
||||
# <i>other</i>.
|
||||
# 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 <code>true</code> if <i>hash</i> is subset of
|
||||
# <i>other</i> or equals to <i>other</i>.
|
||||
# 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 <code>true</code> if <i>other</i> is subset of
|
||||
# <i>hash</i>.
|
||||
# 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 <code>true</code> if <i>other</i> is subset of
|
||||
# <i>hash</i> or equals to <i>hash</i>.
|
||||
# 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 <i>key</i>
|
||||
# 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 <i>hsh</i>, replacing it
|
||||
# with the new key returned by the block, and then returns <i>hsh</i>.
|
||||
# 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 <i>hsh</i>.
|
||||
# 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 <code>KeyError</code> when one of keys can't be found.
|
||||
# Also see <code>Hash#values_at</code> and <code>Hash#fetch</code>.
|
||||
# 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" }
|
||||
#
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* hsh.values_at(key, ...) -> array
|
||||
*
|
||||
* Return an array containing the values associated with the given keys.
|
||||
* Also see <code>Hash.select</code>.
|
||||
* 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 <code>nil</code>.
|
||||
* not found, returns `nil`.
|
||||
*
|
||||
* h = { "a" => 100, "b" => 200, "c" => 300, "d" => 300 }
|
||||
* h.key(200) #=> "b"
|
||||
|
||||
@@ -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 <mruby.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
@@ -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
|
||||
|
||||
@@ -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 #=> "#<File:testfile>"
|
||||
#
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user