Merge branch 'master' into manual-hooks

This commit is contained in:
John Bampton
2025-11-28 05:43:37 +10:00
committed by GitHub
199 changed files with 27207 additions and 12106 deletions
+2 -2
View File
@@ -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
-77
View File
@@ -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]')
-63
View File
@@ -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
+4 -4
View File
@@ -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"
+2 -2
View File
@@ -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/
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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: |
+2 -2
View File
@@ -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
View File
@@ -24,7 +24,7 @@ benchmark/**/*.dat
benchmark/*.pdf
benchmark/*.png
bin
build
/build
doc/api
doc/capi
compile_commands.json
+6 -8
View File
@@ -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
+6 -5
View File
@@ -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)
+9
View File
@@ -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
View File
@@ -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
+29 -22
View File
@@ -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}'
+5
View File
@@ -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
View File
@@ -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.
+21 -22
View File
@@ -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
View File
@@ -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
+6
View File
@@ -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
View File
@@ -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))
+1
View File
@@ -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
View File
@@ -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;
};
+11
View File
@@ -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
View File
@@ -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 */
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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 \
+9 -2
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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
+7
View File
@@ -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
+193
View File
@@ -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 */
}
+8
View File
@@ -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
+578
View File
@@ -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 */
}
+8
View File
@@ -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
+158
View File
@@ -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 */
}
+102
View File
@@ -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`
+8
View File
@@ -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
+231
View File
@@ -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 */
}
+7
View File
@@ -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
+249
View File
@@ -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.
*/
+11
View File
@@ -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
+641
View File
@@ -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 */
}
+12
View File
@@ -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
+177
View File
@@ -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 */
}
+109
View File
@@ -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.
+11
View File
@@ -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
+187
View File
@@ -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 */
}
+83 -84
View File
@@ -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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+7
View File
@@ -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) {
+3 -2
View File
@@ -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;
}
+7 -12
View File
@@ -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;
}
+6 -11
View File
@@ -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) {
+8
View File
@@ -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
View File
@@ -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
+3 -2
View File
@@ -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;
}
+18 -8
View File
@@ -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);
}
+22 -1
View File
@@ -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
+50
View File
@@ -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)
{
+2 -1
View File
@@ -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");
}
+39 -39
View File
@@ -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
+9 -9
View File
@@ -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
File diff suppressed because it is too large Load Diff
+597 -21
View File
@@ -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 */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+9 -7
View File
@@ -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__
+170
View File
@@ -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
+251 -77
View File
@@ -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());
}
+11
View File
@@ -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
+12 -12
View File
@@ -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)
+79
View File
@@ -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 */
+31
View File
@@ -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
+56 -18
View File
@@ -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
-140
View File
@@ -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
View File
@@ -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);
}
+29 -46
View File
@@ -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);
}
+87
View File
@@ -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
+49 -49
View File
@@ -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}
+187 -14
View File
@@ -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
+40 -21
View File
@@ -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
+37 -2
View File
@@ -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
+2 -4
View File
@@ -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);
+64 -2
View File
@@ -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)
+3 -2
View File
@@ -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);
+4 -8
View File
@@ -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));
+1 -1
View File
@@ -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.
*
+28 -22
View File
@@ -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)
+23 -23
View File
@@ -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" }
#
+13 -18
View File
@@ -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"
+451
View File
@@ -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 */
+26 -3
View File
@@ -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
+63 -95
View File
@@ -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
+11
View File
@@ -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