mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
Compare commits
340 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 735b94535c | |||
| 9982bc1223 | |||
| 24f428af5e | |||
| a6168fa111 | |||
| d8911416c1 | |||
| b0e7118e72 | |||
| 4073ad386c | |||
| c58b0b66ba | |||
| 6704a385b7 | |||
| 72e5ebed55 | |||
| 9d084b09b7 | |||
| f5ca906852 | |||
| 543bc325f3 | |||
| 828efa9b87 | |||
| 94bd329afe | |||
| be36b67a12 | |||
| a502be4f9f | |||
| f1232334c0 | |||
| dad7c0fa36 | |||
| e0d6d39ce9 | |||
| 564726c44f | |||
| 5e669560eb | |||
| 030a08092a | |||
| d2a1c43a5f | |||
| dc671f007a | |||
| c09196ca36 | |||
| dfc542dbc5 | |||
| 20d8e202a2 | |||
| 4bdde3a0a8 | |||
| ee82a7fcc6 | |||
| 819156e678 | |||
| d1289ac8f9 | |||
| f5a5bfcbf6 | |||
| 012691279d | |||
| ccb62ceb57 | |||
| 4f398f6126 | |||
| 19c857a773 | |||
| f6564c7db1 | |||
| 36dd9eab88 | |||
| b1adab4e87 | |||
| d21eceb286 | |||
| b9b8186f00 | |||
| cddaec7264 | |||
| 3aea2f7e67 | |||
| c0d8abd0a3 | |||
| 309b99efa5 | |||
| 66f438d8fe | |||
| 4507b4a633 | |||
| d30ddd4296 | |||
| a4e12dfc42 | |||
| 206b9f7477 | |||
| 6ac2c3dc56 | |||
| 72c117cb9e | |||
| 465e634d48 | |||
| 9a1c1eabf9 | |||
| 54c8427df2 | |||
| df38160d82 | |||
| 2c0bccab66 | |||
| 17d124b00d | |||
| 3e2ce88eab | |||
| 7378be58cb | |||
| ebe4a7aed0 | |||
| 6d12f1f331 | |||
| 6daa33c9a8 | |||
| ae29ab7db9 | |||
| 73255d3b70 | |||
| db2845aae0 | |||
| 5bb4a15086 | |||
| 3e676af568 | |||
| c4d205496d | |||
| 1317a8bf5d | |||
| a1a77e5acc | |||
| c9ae2524ee | |||
| 458078f10e | |||
| d1bffb902e | |||
| 17858cc5bd | |||
| aa25be69d7 | |||
| c8dd299ca8 | |||
| e7f5387f5d | |||
| 67f137e201 | |||
| 8b63ddbfd1 | |||
| 2668619143 | |||
| f22a991065 | |||
| 40264c9aad | |||
| b059f3df25 | |||
| e5c2479ee6 | |||
| 087da64f99 | |||
| 9eb8865a8a | |||
| aea2bbcbe5 | |||
| 3e5e84e391 | |||
| d1608ba72e | |||
| 6d2d31ac5f | |||
| 9e93337479 | |||
| 403b75fbeb | |||
| 27e14c16c4 | |||
| 82de0b0ee5 | |||
| 0eaae2824b | |||
| 478ada3bf4 | |||
| 16151a0daa | |||
| 322642364a | |||
| 3fc7dd1858 | |||
| 46151db893 | |||
| 9d8d41006b | |||
| 788ad33333 | |||
| 0a78ab986d | |||
| f91936b06e | |||
| 0d87198c92 | |||
| 3dbf5f6e27 | |||
| ad0ea8571f | |||
| 9bb40386ce | |||
| 89e81e9130 | |||
| fe17d66363 | |||
| bda1d2d5a5 | |||
| db29955b7a | |||
| 575609f3b9 | |||
| b4f6450509 | |||
| 2e429a034d | |||
| 81cfeb917e | |||
| 43c0b50cbd | |||
| 7d0d6fd413 | |||
| 8703a49fb4 | |||
| c0fb9a190c | |||
| e95c0fc683 | |||
| 274af06d06 | |||
| c8c8522519 | |||
| 61a5e290b3 | |||
| cf20687ee7 | |||
| 817ec1bd90 | |||
| e4662551a3 | |||
| 90a0fc5797 | |||
| 3f709f61dd | |||
| bf0915dd03 | |||
| df74d57857 | |||
| 91b60802db | |||
| ebfdf8ae43 | |||
| 28624ecfd8 | |||
| f267925d45 | |||
| ada9ca7236 | |||
| 428c9b5b03 | |||
| 3db11cef09 | |||
| e7a375d4be | |||
| c6836f494a | |||
| 3cc60d31c6 | |||
| 90ead4bdaf | |||
| a8c841433f | |||
| 3f321f09bc | |||
| ddcbd2dc90 | |||
| 479af5c1bd | |||
| c24d01c6fa | |||
| a18467da3f | |||
| 525ab7a800 | |||
| a98c6b62ea | |||
| 7dfd560df8 | |||
| 8a73faf61e | |||
| adb27caaa2 | |||
| 7579a70974 | |||
| 7e73392fc1 | |||
| 923c2e6a73 | |||
| 7c30c2f62b | |||
| fb41438cec | |||
| e4cd7e6daf | |||
| 16f1f4418a | |||
| d6556195ef | |||
| 1702b89c25 | |||
| 06d236d535 | |||
| baf4145fa1 | |||
| b70d160273 | |||
| 00e2c2fb29 | |||
| 550be1445f | |||
| 57ca531f76 | |||
| 7ae25febbb | |||
| a69d12aa54 | |||
| eed32b752b | |||
| e3dadc083c | |||
| 2ff6563484 | |||
| 3f27eac308 | |||
| 22df8d61be | |||
| ad8fc7d918 | |||
| 8e91554c6d | |||
| ec55e0d0f1 | |||
| 7e8d74793c | |||
| 81bcd4e931 | |||
| bbf7c43c37 | |||
| 449040400a | |||
| eb5480a6b3 | |||
| b3777110ab | |||
| 86e274f759 | |||
| d212c7be6c | |||
| 909f5a070f | |||
| ce4bc77aaf | |||
| 5931a96a17 | |||
| be6413f0d8 | |||
| d9e107c801 | |||
| 886bf270f0 | |||
| 9965f11cfe | |||
| a8c82b506f | |||
| 53a43e9d36 | |||
| b8eccc7a38 | |||
| 0e44db114c | |||
| 2b5a389de3 | |||
| bdc3a65601 | |||
| 0ff41e4e28 | |||
| 1ed52461e8 | |||
| 416793db3d | |||
| 6f202d6c2b | |||
| ad1fd9285c | |||
| 29f84030ca | |||
| b70c393039 | |||
| 7ae73ac7e4 | |||
| 966aa9512e | |||
| 726d8febf3 | |||
| 034a64346c | |||
| 8624764bca | |||
| 337b5906a2 | |||
| 466e4ad84a | |||
| 1641c8c06f | |||
| 355c68f487 | |||
| 13e63657a4 | |||
| 6edef4e7e6 | |||
| 1f0809aad3 | |||
| 2ef3a7c21e | |||
| 16b73ec67b | |||
| 3d8ccee7a8 | |||
| 101f8c69a1 | |||
| 4ded345ebb | |||
| 9d955ba75f | |||
| a2edda173b | |||
| 4579caa7d8 | |||
| 5a158d32a8 | |||
| 26dc5f76ea | |||
| 36a0f83db3 | |||
| 893cb4edc4 | |||
| 3f627c0d7d | |||
| 78d761addf | |||
| 0ca3192c9f | |||
| 4307461e58 | |||
| dab150007f | |||
| 4d81275083 | |||
| 117af56bc2 | |||
| 3c68e49178 | |||
| 7283560215 | |||
| 8d92379d7c | |||
| 23b2d24cf5 | |||
| 6deafd810f | |||
| 3bfb27b999 | |||
| 1cfa153ff3 | |||
| 30e41242ec | |||
| 49dff412b5 | |||
| f7d7bbef43 | |||
| 5364c4167e | |||
| 02bb943960 | |||
| e292d7a6c4 | |||
| cb64a0b4a4 | |||
| afc0753c1d | |||
| 3f8e13da6f | |||
| 65e24f4083 | |||
| 0b79c70935 | |||
| 16fbd56e4b | |||
| 0f47249963 | |||
| 3b85d48f89 | |||
| f3cd991771 | |||
| d2caa144be | |||
| f1a6274c34 | |||
| 6a6e2b48ac | |||
| 82bb954c23 | |||
| 309f450bab | |||
| 851da984b4 | |||
| 4d03f40204 | |||
| 878ecd9b09 | |||
| f0b3fdfb14 | |||
| 4b866a84da | |||
| 9a736d6609 | |||
| eb8c177824 | |||
| cfa422b872 | |||
| 60cde305c1 | |||
| 52fb294172 | |||
| 78658d67e1 | |||
| 95bfa86160 | |||
| 5148062516 | |||
| 3272955bf1 | |||
| 3d53864991 | |||
| 9ff1aa9d55 | |||
| 50bc8c6136 | |||
| d0d2c3072c | |||
| 7675601b92 | |||
| b2d935b6d3 | |||
| 36dd668f73 | |||
| 8996152365 | |||
| 7365ed526b | |||
| 7a14bad5ea | |||
| bfc1f37b91 | |||
| 7eab8302b0 | |||
| 05f7236586 | |||
| d359182b47 | |||
| ccd661b429 | |||
| aea5d584f0 | |||
| 08b6d2ecd0 | |||
| f469e7567a | |||
| 86940418c7 | |||
| 7546d53a3a | |||
| 0d00743a5f | |||
| 0112fe6659 | |||
| 605ef4919b | |||
| e1ed206cae | |||
| 82b91d15c5 | |||
| f80f825bcf | |||
| eb51c63196 | |||
| c2145c8642 | |||
| b82f1c2ec0 | |||
| a6a92bf95b | |||
| 10c9e83128 | |||
| 2f5a24ed9b | |||
| 984f23a94e | |||
| 2575735152 | |||
| eb87d59941 | |||
| 8b44a52176 | |||
| 617a55b775 | |||
| a33ccd67b1 | |||
| c43eba8979 | |||
| 6c06b4cd9d | |||
| 9ad94a50e7 | |||
| d8e0d24fea | |||
| a6283dbdf3 | |||
| 55f0228bf8 | |||
| d246ac6c8a | |||
| 5e3e982442 | |||
| 3a9ef9d27e | |||
| 95b74eefdf | |||
| be3212906a | |||
| 40371ebf01 | |||
| d230bb16d4 | |||
| 3f27133639 | |||
| f72a32ad87 | |||
| 1f7bc64845 | |||
| 627452e340 | |||
| 3c653fc0d4 | |||
| b667b052dd | |||
| ef763285e9 | |||
| 0bd391bc6f | |||
| 2249cf7c7f |
@@ -52,7 +52,7 @@ jobs:
|
|||||||
- name: Ruby version
|
- name: Ruby version
|
||||||
run: ruby -v
|
run: ruby -v
|
||||||
- name: Cache cosmocc
|
- name: Cache cosmocc
|
||||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||||
id: cache-cosmocc
|
id: cache-cosmocc
|
||||||
with:
|
with:
|
||||||
path: ~/cosmo
|
path: ~/cosmo
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v4
|
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v4
|
uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v4
|
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||||
with:
|
with:
|
||||||
category: "Security"
|
category: "Security"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
|
||||||
with:
|
with:
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: j178/prek-action@53276d8b0d10f8b6672aa85b4588c6921d0370cc # v2.0.1
|
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
|
||||||
with:
|
with:
|
||||||
install-only: true
|
install-only: true
|
||||||
- name: Run manual pre-commit hooks
|
- name: Run manual pre-commit hooks
|
||||||
|
|||||||
@@ -15,6 +15,6 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: j178/prek-action@53276d8b0d10f8b6672aa85b4588c6921d0370cc # v2.0.1
|
- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
|
||||||
with:
|
with:
|
||||||
extra-args: --all-files
|
extra-args: --all-files
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
mv .sha256 "$packagename.sha256"
|
mv .sha256 "$packagename.sha256"
|
||||||
)
|
)
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: ${{ contains(github.ref_name, '-rc') }}
|
prerelease: ${{ contains(github.ref_name, '-rc') }}
|
||||||
|
|||||||
+14
-2
@@ -15,6 +15,9 @@ repos:
|
|||||||
- id: check-hooks-apply
|
- id: check-hooks-apply
|
||||||
name: run check-hooks-apply
|
name: run check-hooks-apply
|
||||||
description: check hooks apply to the repository
|
description: check hooks apply to the repository
|
||||||
|
- id: check-useless-excludes
|
||||||
|
name: run check-useless-excludes
|
||||||
|
description: clean up unnecessary exclusion patterns
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
@@ -26,6 +29,15 @@ repos:
|
|||||||
additional_dependencies: ["prettier@3.7.4"]
|
additional_dependencies: ["prettier@3.7.4"]
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
stages: [manual]
|
stages: [manual]
|
||||||
|
- id: check-makefile-indentation
|
||||||
|
name: check Makefiles are indented with tabs
|
||||||
|
description: ensures that Makefiles are indented with tabs
|
||||||
|
entry: ./scripts/check_makefiles_for_tabs.sh
|
||||||
|
language: system
|
||||||
|
files: "(?i)^makefile$"
|
||||||
|
pass_filenames: true # <-- Crucial change: pass filenames to the script
|
||||||
|
types: [file] # Ensure only regular files are passed, not directories
|
||||||
|
stages: [manual]
|
||||||
- id: check-zip-file-is-not-committed
|
- id: check-zip-file-is-not-committed
|
||||||
name: disallow zip files
|
name: disallow zip files
|
||||||
description: Zip files are not allowed in the repository
|
description: Zip files are not allowed in the repository
|
||||||
@@ -41,7 +53,7 @@ repos:
|
|||||||
name: run gitleaks
|
name: run gitleaks
|
||||||
description: detect hardcoded secrets with gitleaks
|
description: detect hardcoded secrets with gitleaks
|
||||||
- repo: https://github.com/oxipng/oxipng
|
- repo: https://github.com/oxipng/oxipng
|
||||||
rev: v10.1.0
|
rev: v10.1.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: oxipng
|
- id: oxipng
|
||||||
name: run oxipng
|
name: run oxipng
|
||||||
@@ -106,7 +118,7 @@ repos:
|
|||||||
types: [markdown]
|
types: [markdown]
|
||||||
files: \.md$
|
files: \.md$
|
||||||
- repo: https://github.com/rubocop/rubocop
|
- repo: https://github.com/rubocop/rubocop
|
||||||
rev: v1.86.0
|
rev: v1.86.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: rubocop
|
- id: rubocop
|
||||||
name: run rubocop
|
name: run rubocop
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
# Authors of mruby (mruby developers)
|
# Authors of mruby (mruby developers)
|
||||||
|
|
||||||
## The List of Contributors sorted by number of commits (as of 2026-03-02 02877f0)
|
## The List of Contributors sorted by number of commits (as of 2026-05-31 9d084b0)
|
||||||
|
|
||||||
7532 Yukihiro "Matz" Matsumoto (@matz)*
|
7747 Yukihiro "Matz" Matsumoto (@matz)*
|
||||||
712 dearblue (@dearblue)*
|
749 dearblue (@dearblue)*
|
||||||
587 KOBAYASHI Shuji (@shuujii)
|
587 KOBAYASHI Shuji (@shuujii)
|
||||||
353 Daniel Bovensiepen (@bovi)*
|
353 Daniel Bovensiepen (@bovi)*
|
||||||
345 Takeshi Watanabe (@take-cheeze)*
|
345 Takeshi Watanabe (@take-cheeze)*
|
||||||
333 Masaki Muranaka (@monaka)
|
333 Masaki Muranaka (@monaka)
|
||||||
255 John Bampton (@jbampton)
|
266 John Bampton (@jbampton)
|
||||||
234 Jun Hiroe (@suzukaze)
|
234 Jun Hiroe (@suzukaze)
|
||||||
228 Tomoyuki Sahara (@tsahara)*
|
228 Tomoyuki Sahara (@tsahara)*
|
||||||
220 Cremno (@cremno)*
|
220 Cremno (@cremno)*
|
||||||
209 Yuki Kurihara (@ksss)+
|
209 Yuki Kurihara (@ksss)+
|
||||||
144 Yasuhiro Matsumoto (@mattn)*
|
146 Yasuhiro Matsumoto (@mattn)*
|
||||||
113 Carson McDonald (@carsonmcdonald)
|
113 Carson McDonald (@carsonmcdonald)
|
||||||
104 Tomasz Pędraszewski (@dabroz)*
|
104 Tomasz Pędraszewski (@dabroz)*
|
||||||
83 Akira Yumiyama (@akiray03)*
|
83 Akira Yumiyama (@akiray03)*
|
||||||
83 skandhas (@skandhas)
|
83 skandhas (@skandhas)
|
||||||
80 Masamitsu MURASE (@masamitsu-murase)
|
80 Masamitsu MURASE (@masamitsu-murase)
|
||||||
73 Hiroshi Mimaki (@mimaki)*
|
79 Hiroshi Mimaki (@mimaki)*
|
||||||
71 Tatsuhiko Kubo (@cubicdaiya)*
|
71 Tatsuhiko Kubo (@cubicdaiya)*
|
||||||
71 Yuichiro MASUI (@masuidrive)
|
71 Yuichiro MASUI (@masuidrive)
|
||||||
62 Yuichiro Kaneko (@yui-knk)+
|
62 Yuichiro Kaneko (@yui-knk)+
|
||||||
@@ -36,8 +36,10 @@
|
|||||||
32 Masayoshi Takahashi (@takahashim)+
|
32 Masayoshi Takahashi (@takahashim)+
|
||||||
31 MATSUMOTO Ryosuke (@matsumotory)*
|
31 MATSUMOTO Ryosuke (@matsumotory)*
|
||||||
30 Nobuyoshi Nakada (@nobu)
|
30 Nobuyoshi Nakada (@nobu)
|
||||||
|
29 HASUMI Hitoshi (@hasumikin)
|
||||||
26 Hoshiumi Arata (@hoshiumiarata)*
|
26 Hoshiumi Arata (@hoshiumiarata)*
|
||||||
25 Julian Aron Prenner (@furunkel)*
|
25 Julian Aron Prenner (@furunkel)*
|
||||||
|
23 leviongit (@leviongit)
|
||||||
22 Clayton Smith (@clayton-shopify)
|
22 Clayton Smith (@clayton-shopify)
|
||||||
22 Uchio Kondo (@udzura)*
|
22 Uchio Kondo (@udzura)*
|
||||||
22 Zachary Scott (@zzak)*
|
22 Zachary Scott (@zzak)*
|
||||||
@@ -50,10 +52,9 @@
|
|||||||
18 Corey Powell (@IceDragon200)
|
18 Corey Powell (@IceDragon200)
|
||||||
18 Hidetaka Takano (@TJ-Hidetaka-Takano)
|
18 Hidetaka Takano (@TJ-Hidetaka-Takano)
|
||||||
18 Jon Maken (@jonforums)+
|
18 Jon Maken (@jonforums)+
|
||||||
18 leviongit (@leviongit)
|
|
||||||
18 mirichi (@mirichi)
|
18 mirichi (@mirichi)
|
||||||
17 Mitchell Blank Jr (@mitchblank)*
|
17 Mitchell Blank Jr (@mitchblank)*
|
||||||
16 HASUMI Hitoshi (@hasumikin)
|
16 Hendrik (@Asmod4n)
|
||||||
16 bggd (@bggd)
|
16 bggd (@bggd)
|
||||||
16 kano4 (@kano4)
|
16 kano4 (@kano4)
|
||||||
15 Felix Jones (@felixjones)*
|
15 Felix Jones (@felixjones)*
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
11 RIZAL Reckordp (@Reckordp)+
|
11 RIZAL Reckordp (@Reckordp)+
|
||||||
11 Seeker (@SeekingMeaning)
|
11 Seeker (@SeekingMeaning)
|
||||||
11 takkaw (@takkaw)
|
11 takkaw (@takkaw)
|
||||||
10 Hendrik (@Asmod4n)
|
10 Chris Hasiński (@khasinski)
|
||||||
10 Miura Hideki (@miura1729)
|
10 Miura Hideki (@miura1729)
|
||||||
10 Narihiro Nakamura (@authorNari)
|
10 Narihiro Nakamura (@authorNari)
|
||||||
10 YAMAMOTO Masaya (pandax381)
|
10 YAMAMOTO Masaya (pandax381)
|
||||||
@@ -88,6 +89,7 @@
|
|||||||
8 Wataru Ashihara (@wataash)*
|
8 Wataru Ashihara (@wataash)*
|
||||||
7 Bhargava Shastry (@bshastry)*
|
7 Bhargava Shastry (@bshastry)*
|
||||||
7 Kouichi Nakanishi (@keizo042)
|
7 Kouichi Nakanishi (@keizo042)
|
||||||
|
7 Paweł Świątkowski (@katafrakt)
|
||||||
7 Rubyist (@expeditiousRubyist)
|
7 Rubyist (@expeditiousRubyist)
|
||||||
7 Simon Génier (@simon-shopify)
|
7 Simon Génier (@simon-shopify)
|
||||||
7 Terence Lee (@hone)
|
7 Terence Lee (@hone)
|
||||||
@@ -101,7 +103,6 @@
|
|||||||
6 INOUE Yasuyuki (@yasuyuki)
|
6 INOUE Yasuyuki (@yasuyuki)
|
||||||
6 Junji Sawada (@junjis0203)
|
6 Junji Sawada (@junjis0203)
|
||||||
6 Kenji Okimoto (@okkez)+
|
6 Kenji Okimoto (@okkez)+
|
||||||
6 Paweł Świątkowski (@katafrakt)
|
|
||||||
6 Selman ULUG (@selman)
|
6 Selman ULUG (@selman)
|
||||||
6 Yusuke Endoh (@mame)*
|
6 Yusuke Endoh (@mame)*
|
||||||
6 buty4649 (@buty4649)
|
6 buty4649 (@buty4649)
|
||||||
@@ -120,7 +121,6 @@
|
|||||||
5 dreamedge (@dreamedge)
|
5 dreamedge (@dreamedge)
|
||||||
5 nkshigeru (@nkshigeru)
|
5 nkshigeru (@nkshigeru)
|
||||||
5 xuejianqing (@joans321)
|
5 xuejianqing (@joans321)
|
||||||
4 Chris Hasiński (@khasinski)
|
|
||||||
4 Dante Catalfamo (@dantecatalfamo)
|
4 Dante Catalfamo (@dantecatalfamo)
|
||||||
4 Goro Kikuchi (@gorogit)
|
4 Goro Kikuchi (@gorogit)
|
||||||
4 Herwin Weststrate (@herwinw)
|
4 Herwin Weststrate (@herwinw)
|
||||||
@@ -140,6 +140,7 @@
|
|||||||
4 Yuji Yamano (@yyamano)
|
4 Yuji Yamano (@yyamano)
|
||||||
4 kurodash (@kurodash)*
|
4 kurodash (@kurodash)*
|
||||||
4 wanabe (@wanabe)*
|
4 wanabe (@wanabe)*
|
||||||
|
2 0x1eef (@0x1eef)
|
||||||
3 Anton Davydov (@davydovanton)
|
3 Anton Davydov (@davydovanton)
|
||||||
3 Aurora Nockert (@auroranockert)
|
3 Aurora Nockert (@auroranockert)
|
||||||
3 Carlo Prelz (@asfluido)*
|
3 Carlo Prelz (@asfluido)*
|
||||||
@@ -192,6 +193,7 @@
|
|||||||
2 Masahiro Wakame (@vvkame)+
|
2 Masahiro Wakame (@vvkame)+
|
||||||
2 Minao Yamamoto (@tarosay)+
|
2 Minao Yamamoto (@tarosay)+
|
||||||
2 Nihad Abbasov (@NARKOZ)
|
2 Nihad Abbasov (@NARKOZ)
|
||||||
|
2 Pete Kinnecom (@petekinnecom)
|
||||||
2 Robert Mosolgo (@rmosolgo)
|
2 Robert Mosolgo (@rmosolgo)
|
||||||
2 Russel Hunter Yukawa (@rhykw)+
|
2 Russel Hunter Yukawa (@rhykw)+
|
||||||
2 Ryunosuke SATO (@tricknotes)
|
2 Ryunosuke SATO (@tricknotes)
|
||||||
@@ -220,6 +222,7 @@
|
|||||||
1 Colin MacKenzie IV (@sinisterchipmunk)
|
1 Colin MacKenzie IV (@sinisterchipmunk)
|
||||||
1 Daehyub Kim (@lateau)
|
1 Daehyub Kim (@lateau)
|
||||||
1 Daniel Varga (@vargad)
|
1 Daniel Varga (@vargad)
|
||||||
|
1 David Korczynski (@DavidKorczynski)
|
||||||
1 Diamond Rivero (@diamant3)
|
1 Diamond Rivero (@diamant3)
|
||||||
1 Edgar Boda-Majer (@eboda)
|
1 Edgar Boda-Majer (@eboda)
|
||||||
1 Fangrui Song (@MaskRay)
|
1 Fangrui Song (@MaskRay)
|
||||||
@@ -274,7 +277,6 @@
|
|||||||
1 Patrick Ellis (@pje)
|
1 Patrick Ellis (@pje)
|
||||||
1 Patrick Pokatilo (@SHyx0rmZ)
|
1 Patrick Pokatilo (@SHyx0rmZ)
|
||||||
1 Pavel Evstigneev (@Paxa)+
|
1 Pavel Evstigneev (@Paxa)+
|
||||||
1 Pete Kinnecom (@petekinnecom)
|
|
||||||
1 Piotr Usewicz (@pusewicz)
|
1 Piotr Usewicz (@pusewicz)
|
||||||
1 Prayag Verma (@pra85)
|
1 Prayag Verma (@pra85)
|
||||||
1 Ranmocy (@ranmocy)
|
1 Ranmocy (@ranmocy)
|
||||||
@@ -282,6 +284,7 @@
|
|||||||
1 Ryan Scott Lewis (@RyanScottLewis)
|
1 Ryan Scott Lewis (@RyanScottLewis)
|
||||||
1 Ryo Okubo (@syucream)
|
1 Ryo Okubo (@syucream)
|
||||||
1 SAkira a.k.a. Akira Suzuki (@sakisakira)
|
1 SAkira a.k.a. Akira Suzuki (@sakisakira)
|
||||||
|
1 SaekiMototsune (@saeki-mototsune)
|
||||||
1 Santiago Rodriguez (@sanrodari)
|
1 Santiago Rodriguez (@sanrodari)
|
||||||
1 Satoh, Hiroh (@cho45)+
|
1 Satoh, Hiroh (@cho45)+
|
||||||
1 Satoru Naba (@snaba)+
|
1 Satoru Naba (@snaba)+
|
||||||
@@ -325,6 +328,7 @@
|
|||||||
1 sbsoftware (@sbsoftware)
|
1 sbsoftware (@sbsoftware)
|
||||||
1 ssmallkirby (@smallkirby)
|
1 ssmallkirby (@smallkirby)
|
||||||
1 taku toyama (@tsuichu)
|
1 taku toyama (@tsuichu)
|
||||||
|
1 vobloeb (@vobloeb)
|
||||||
|
|
||||||
`*` - Entries unified according to names and addresses
|
`*` - Entries unified according to names and addresses
|
||||||
`+` - Entries with names different from commits
|
`+` - Entries with names different from commits
|
||||||
|
|||||||
+2
-2
@@ -2,8 +2,8 @@ GEM
|
|||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
rake (13.3.1)
|
rake (13.4.2)
|
||||||
yard (0.9.38)
|
yard (0.9.44)
|
||||||
yard-coderay (0.1.0)
|
yard-coderay (0.1.0)
|
||||||
coderay
|
coderay
|
||||||
yard
|
yard
|
||||||
|
|||||||
@@ -292,9 +292,7 @@ class Scene
|
|||||||
r = rad.x / nsfs
|
r = rad.x / nsfs
|
||||||
g = rad.y / nsfs
|
g = rad.y / nsfs
|
||||||
b = rad.z / nsfs
|
b = rad.z / nsfs
|
||||||
printf("%c", clamp(r))
|
print([clamp(r), clamp(g), clamp(b)].pack("CCC"))
|
||||||
printf("%c", clamp(g))
|
|
||||||
printf("%c", clamp(b))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -303,7 +301,7 @@ end
|
|||||||
# File.open("ao.ppm", "w") do |fp|
|
# File.open("ao.ppm", "w") do |fp|
|
||||||
printf("P6\n")
|
printf("P6\n")
|
||||||
printf("%d %d\n", IMAGE_WIDTH, IMAGE_HEIGHT)
|
printf("%d %d\n", IMAGE_WIDTH, IMAGE_HEIGHT)
|
||||||
printf("255\n", IMAGE_WIDTH, IMAGE_HEIGHT)
|
printf("255\n")
|
||||||
Scene.new.render(IMAGE_WIDTH, IMAGE_HEIGHT, NSUBSAMPLES)
|
Scene.new.render(IMAGE_WIDTH, IMAGE_HEIGHT, NSUBSAMPLES)
|
||||||
# Scene.new.render(256, 256, 2)
|
# Scene.new.render(256, 256, 2)
|
||||||
# end
|
# end
|
||||||
|
|||||||
@@ -64,10 +64,8 @@ MRuby::Build.new do |conf|
|
|||||||
# APE binaries use .com extension
|
# APE binaries use .com extension
|
||||||
conf.exts.executable = '.com'
|
conf.exts.executable = '.com'
|
||||||
|
|
||||||
# Cosmopolitan provides POSIX compatibility, explicitly select POSIX HALs
|
# Cosmopolitan provides POSIX compatibility
|
||||||
conf.gem core: 'hal-posix-io'
|
conf.ports :posix
|
||||||
conf.gem core: 'hal-posix-socket'
|
|
||||||
conf.gem core: 'hal-posix-dir'
|
|
||||||
|
|
||||||
# Standard library
|
# Standard library
|
||||||
conf.gembox 'stdlib'
|
conf.gembox 'stdlib'
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
MRuby::Build.new do |conf|
|
||||||
|
# load specific toolchain settings
|
||||||
|
conf.toolchain
|
||||||
|
|
||||||
|
# Use mrbgems
|
||||||
|
# conf.gem 'examples/mrbgems/ruby_extension_example'
|
||||||
|
# conf.gem 'examples/mrbgems/c_extension_example' do |g|
|
||||||
|
# g.cc.flags << '-g' # append cflags in this gem
|
||||||
|
# end
|
||||||
|
# conf.gem 'examples/mrbgems/c_and_ruby_extension_example'
|
||||||
|
# conf.gem :core => 'mruby-eval'
|
||||||
|
# conf.gem :mgem => 'mruby-onig-regexp'
|
||||||
|
# conf.gem :github => 'mattn/mruby-onig-regexp'
|
||||||
|
# conf.gem :git => 'git@github.com:mattn/mruby-onig-regexp.git', :branch => 'master', :options => '-v'
|
||||||
|
|
||||||
|
# include the GEM box
|
||||||
|
#conf.gembox 'default'
|
||||||
|
|
||||||
|
# C compiler settings
|
||||||
|
# conf.cc do |cc|
|
||||||
|
# cc.command = ENV['CC'] || 'gcc'
|
||||||
|
# cc.flags = [ENV['CFLAGS'] || %w()]
|
||||||
|
# cc.include_paths = ["#{root}/include"]
|
||||||
|
# cc.defines = %w()
|
||||||
|
# cc.option_include_path = %q[-I"%s"]
|
||||||
|
# cc.option_define = '-D%s'
|
||||||
|
# cc.compile_options = %Q[%{flags} -MMD -o "%{outfile}" -c "%{infile}"]
|
||||||
|
# end
|
||||||
|
|
||||||
|
# mrbc settings
|
||||||
|
# conf.mrbc do |mrbc|
|
||||||
|
# mrbc.compile_options = "-g -B%{funcname} -o-" # The -g option is required for line numbers
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Linker settings
|
||||||
|
# conf.linker do |linker|
|
||||||
|
# linker.command = ENV['LD'] || 'gcc'
|
||||||
|
# linker.flags = [ENV['LDFLAGS'] || []]
|
||||||
|
# linker.flags_before_libraries = []
|
||||||
|
# linker.libraries = %w()
|
||||||
|
# linker.flags_after_libraries = []
|
||||||
|
# linker.library_paths = []
|
||||||
|
# linker.option_library = '-l%s'
|
||||||
|
# linker.option_library_path = '-L%s'
|
||||||
|
# linker.link_options = %Q[%{flags} -o "%{outfile}" %{objs} %{libs}]
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Archiver settings
|
||||||
|
# conf.archiver do |archiver|
|
||||||
|
# archiver.command = ENV['AR'] || 'ar'
|
||||||
|
# archiver.archive_options = 'rs "%{outfile}" %{objs}'
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Parser generator settings
|
||||||
|
# conf.yacc do |yacc|
|
||||||
|
# yacc.command = ENV['YACC'] || 'bison'
|
||||||
|
# yacc.compile_options = %q[-o "%{outfile}" "%{infile}"]
|
||||||
|
# end
|
||||||
|
|
||||||
|
# gperf settings
|
||||||
|
# conf.gperf do |gperf|
|
||||||
|
# gperf.command = 'gperf'
|
||||||
|
# gperf.compile_options = %q[-L ANSI-C -C -j1 -i 1 -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"]
|
||||||
|
# end
|
||||||
|
|
||||||
|
# file extensions
|
||||||
|
# conf.exts do |exts|
|
||||||
|
# exts.object = '.o'
|
||||||
|
# exts.executable = '' # '.exe' if Windows
|
||||||
|
# exts.library = '.a'
|
||||||
|
# end
|
||||||
|
|
||||||
|
# file separator
|
||||||
|
# conf.file_separator = '/'
|
||||||
|
|
||||||
|
# change library directory name from the default "lib" if necessary
|
||||||
|
# conf.libdir_name = 'lib64'
|
||||||
|
|
||||||
|
# Turn on `enable_debug` for better debugging
|
||||||
|
conf.enable_sanitizer 'address,undefined'
|
||||||
|
conf.enable_debug
|
||||||
|
conf.enable_bintest
|
||||||
|
conf.enable_test
|
||||||
|
conf.ports :glib
|
||||||
|
conf.cc.defines << 'MRB_TASK_BUILD_DEMO'
|
||||||
|
conf.gem core: 'mruby-task'
|
||||||
|
conf.gem core: 'mruby-compiler'
|
||||||
|
end
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
MRuby::Build.new do |conf|
|
MRuby::Build.new('host-cxx') do |conf|
|
||||||
conf.toolchain
|
conf.toolchain
|
||||||
|
|
||||||
# include the default GEMs
|
# include the default GEMs
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ MRuby::Build.new('host') do |conf|
|
|||||||
# Generate mruby debugger command (require mruby-eval)
|
# Generate mruby debugger command (require mruby-eval)
|
||||||
conf.gem :core => "mruby-bin-debugger"
|
conf.gem :core => "mruby-bin-debugger"
|
||||||
|
|
||||||
|
# Regexp is included via stdlib.gembox
|
||||||
|
|
||||||
# test
|
# test
|
||||||
conf.enable_test
|
conf.enable_test
|
||||||
# bintest
|
# bintest
|
||||||
|
|||||||
+78
-20
@@ -1,36 +1,94 @@
|
|||||||
# NOTE: Currently, this configuration file does not support VisualC++!
|
# Build mruby with a shared libmruby.so (in addition to the usual
|
||||||
# Your help is needed!
|
# libmruby.a / executables).
|
||||||
|
#
|
||||||
|
# Produces (in build/host/lib/):
|
||||||
|
# libmruby.a the static archive (as in the default build)
|
||||||
|
# libmruby.so the shared library, with SONAME=libmruby.so.<MAJOR>.<MINOR>
|
||||||
|
# libmruby.so.<MAJOR>.<MINOR> symlink to libmruby.so (matches SONAME)
|
||||||
|
# libmruby.map linker version script (MRUBY_<RELEASE_NO>)
|
||||||
|
#
|
||||||
|
# Also produces the matching libmruby_core.so + symlink for completeness.
|
||||||
|
#
|
||||||
|
# Symbol versioning ties to MRUBY_RELEASE_NO (e.g. MRUBY_40000 for 4.0.0).
|
||||||
|
# mruby has historically had ABI breaks between TEENY versions, so the
|
||||||
|
# version tag uses the full release number rather than just MAJOR.MINOR.
|
||||||
|
#
|
||||||
|
# The shared library is built FROM the static archive via
|
||||||
|
# `-Wl,--whole-archive`, so the existing static-build pipeline (including
|
||||||
|
# the test infrastructure) is unaffected. Executables in build/host/bin
|
||||||
|
# remain statically linked; distros that want dynamically-linked
|
||||||
|
# executables can rebuild them against the .so.
|
||||||
|
#
|
||||||
|
# NOTE: gcc/clang only — VisualC++ support requires a separate config.
|
||||||
|
|
||||||
|
require "mruby/source"
|
||||||
|
|
||||||
MRuby::Build.new do |conf|
|
MRuby::Build.new do |conf|
|
||||||
# load specific toolchain settings
|
|
||||||
conf.toolchain
|
conf.toolchain
|
||||||
|
|
||||||
# include the GEM box
|
# include the GEM box
|
||||||
conf.gembox 'default'
|
conf.gembox 'default'
|
||||||
|
|
||||||
# C compiler settings
|
# -fPIC so the static archive's contents can be linked into the .so.
|
||||||
conf.compilers.each do |cc|
|
conf.compilers.each do |cc|
|
||||||
cc.flags << '-fPIC'
|
cc.flags << '-fPIC'
|
||||||
end
|
end
|
||||||
|
|
||||||
conf.archiver do |archiver|
|
|
||||||
archiver.command = cc.command
|
|
||||||
archiver.archive_options = '-shared -o %{outfile} %{objs}'
|
|
||||||
end
|
|
||||||
|
|
||||||
# file extensions
|
|
||||||
conf.exts do |exts|
|
|
||||||
exts.library = '.so'
|
|
||||||
end
|
|
||||||
|
|
||||||
# file separator
|
|
||||||
# conf.file_separator = '/'
|
|
||||||
|
|
||||||
# enable this if better compatibility with C++ is desired
|
|
||||||
#conf.enable_cxx_exception
|
|
||||||
|
|
||||||
# Turn on `enable_debug` for better debugging
|
# Turn on `enable_debug` for better debugging
|
||||||
conf.enable_debug
|
conf.enable_debug
|
||||||
conf.enable_bintest
|
conf.enable_bintest
|
||||||
conf.enable_test
|
conf.enable_test
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Add the shared-library targets as a post-build pass, so the default
|
||||||
|
# static-build pipeline remains untouched.
|
||||||
|
MRuby.each_target do
|
||||||
|
next unless name == "host"
|
||||||
|
|
||||||
|
libdir = File.join(build_dir, libdir_name)
|
||||||
|
vermap = File.join(build_dir, "libmruby.map")
|
||||||
|
vertag = "MRUBY_#{MRuby::Source::MRUBY_RELEASE_NO}"
|
||||||
|
|
||||||
|
# Generate the version script eagerly — it has no .o dependencies and is
|
||||||
|
# tiny enough that lazy generation isn't worth the rake plumbing.
|
||||||
|
mkdir_p File.dirname(vermap)
|
||||||
|
File.write(vermap, <<~MAP)
|
||||||
|
#{vertag} {
|
||||||
|
global: *;
|
||||||
|
local: *;
|
||||||
|
};
|
||||||
|
MAP
|
||||||
|
|
||||||
|
major = MRuby::Source::MRUBY_RELEASE_MAJOR
|
||||||
|
minor = MRuby::Source::MRUBY_RELEASE_MINOR
|
||||||
|
|
||||||
|
[
|
||||||
|
[libmruby_static, "libmruby"],
|
||||||
|
[libmruby_core_static, "libmruby_core"],
|
||||||
|
].each do |archive, basename|
|
||||||
|
so = File.join(libdir, "#{basename}.so")
|
||||||
|
symlink = "#{so}.#{major}.#{minor}"
|
||||||
|
soname = "#{basename}.so.#{major}.#{minor}"
|
||||||
|
|
||||||
|
# Build .so from the static archive via --whole-archive.
|
||||||
|
file so => [archive, vermap] do |t|
|
||||||
|
_pp "LD", so.relative_path
|
||||||
|
sh "#{cc.command} -shared -fPIC -o #{so}" \
|
||||||
|
" -Wl,-soname,#{soname}" \
|
||||||
|
" -Wl,--version-script=#{vermap}" \
|
||||||
|
" -Wl,--whole-archive #{archive} -Wl,--no-whole-archive" \
|
||||||
|
" -lm"
|
||||||
|
end
|
||||||
|
products << so
|
||||||
|
|
||||||
|
# SONAME-matching symlink: needed so executables linked with -lmruby
|
||||||
|
# (which embeds the SONAME as DT_NEEDED) can find the library at
|
||||||
|
# runtime via standard search paths.
|
||||||
|
file symlink => so do |t|
|
||||||
|
_pp "LN", "#{symlink.relative_path} -> #{File.basename(so)}"
|
||||||
|
rm_f symlink
|
||||||
|
File.symlink(File.basename(so), symlink)
|
||||||
|
end
|
||||||
|
products << symlink
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -72,7 +72,9 @@ MRuby::CrossBuild.new("playstationportable") do |conf|
|
|||||||
conf.gem :core => "mruby-os-memsize"
|
conf.gem :core => "mruby-os-memsize"
|
||||||
conf.gem :core => "mruby-proc-binding"
|
conf.gem :core => "mruby-proc-binding"
|
||||||
conf.gem :core => "mruby-sleep"
|
conf.gem :core => "mruby-sleep"
|
||||||
conf.gem :core => "mruby-io"
|
# Disabled until PSP-specific HALs are available; the POSIX HALs depend on
|
||||||
conf.gem :core => "mruby-dir"
|
# APIs that the PSP SDK does not fully provide.
|
||||||
#conf.gem :core => "mruby-socket" unsupported
|
# conf.gem :core => "mruby-io"
|
||||||
|
# conf.gem :core => "mruby-dir"
|
||||||
|
# conf.gem :core => "mruby-socket"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -73,14 +73,14 @@ The following gems work with amalgamation:
|
|||||||
- `mruby-enum-ext`, `mruby-compar-ext`
|
- `mruby-enum-ext`, `mruby-compar-ext`
|
||||||
- `mruby-error`, `mruby-math`, `mruby-struct`
|
- `mruby-error`, `mruby-math`, `mruby-struct`
|
||||||
- `mruby-bigint`, `mruby-rational`, `mruby-complex`
|
- `mruby-bigint`, `mruby-rational`, `mruby-complex`
|
||||||
- `mruby-io` (with `hal-posix-io`)
|
- `mruby-io` (with the active `ports/<name>/` HAL)
|
||||||
- `mruby-task` (with `hal-posix-task`)
|
- `mruby-task` (with the active `ports/<name>/` HAL)
|
||||||
|
|
||||||
### Platform-Dependent Gems
|
### Platform-Dependent Gems
|
||||||
|
|
||||||
Gems that use a HAL (Hardware Abstraction Layer) include
|
Gems that use a HAL (Hardware Abstraction Layer) include
|
||||||
platform-specific code in the amalgamation. For example, if
|
platform-specific code in the amalgamation. For example, if
|
||||||
`mruby-io` selects `hal-posix-io` on Linux, the generated `mruby.c`
|
`mruby-io` selects its POSIX port on Linux, the generated `mruby.c`
|
||||||
contains POSIX-specific code and cannot be compiled on Windows.
|
contains POSIX-specific code and cannot be compiled on Windows.
|
||||||
|
|
||||||
If you need amalgamated files for multiple platforms, generate them
|
If you need amalgamated files for multiple platforms, generate them
|
||||||
|
|||||||
@@ -381,6 +381,13 @@ mrb_define_method(mrb, point, "initialize", point_init, MRB_ARGS_REQ(2));
|
|||||||
mrb_define_method(mrb, point, "x", point_x, MRB_ARGS_NONE());
|
mrb_define_method(mrb, point, "x", point_x, MRB_ARGS_NONE());
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Do not call into mruby from a `dfree` handler.** The handler runs
|
||||||
|
from inside GC sweep; allocating Ruby objects, calling
|
||||||
|
`mrb_funcall`, `mrb_yield`, raising exceptions, or otherwise
|
||||||
|
re-entering the VM can trigger a recursive GC that revisits the
|
||||||
|
same object and causes double-free. Keep `dfree` to `mrb_free` /
|
||||||
|
plain C cleanup of the wrapped data only.
|
||||||
|
|
||||||
## Exception Handling
|
## Exception Handling
|
||||||
|
|
||||||
### Raising Exceptions
|
### Raising Exceptions
|
||||||
|
|||||||
@@ -172,6 +172,8 @@ The maximal GEM structure looks like this:
|
|||||||
|
|
|
|
||||||
+- src/ <- Source for C extension
|
+- src/ <- Source for C extension
|
||||||
|
|
|
|
||||||
|
+- ports/<name>/ <- Platform-specific C sources (see Platform Ports)
|
||||||
|
|
|
||||||
+- tools/ <- Source for Executable (in C)
|
+- tools/ <- Source for Executable (in C)
|
||||||
|
|
|
|
||||||
+- test/ <- Test code (Ruby)
|
+- test/ <- Test code (Ruby)
|
||||||
@@ -182,6 +184,8 @@ contains C/C++ files to extend mruby. The `include` directory contains C/C++ hea
|
|||||||
files. The `test` directory contains C/C++ and pure Ruby files for testing purposes
|
files. The `test` directory contains C/C++ and pure Ruby files for testing purposes
|
||||||
which will be used by `mrbtest`. `mrbgem.rake` contains the specification
|
which will be used by `mrbtest`. `mrbgem.rake` contains the specification
|
||||||
to compile C and Ruby files. `README.md` is a short description of your GEM.
|
to compile C and Ruby files. `README.md` is a short description of your GEM.
|
||||||
|
The optional `ports/<name>/` directories hold platform-specific C sources
|
||||||
|
selected at build time; see [Platform Ports](#platform-ports-ports) below.
|
||||||
|
|
||||||
## Build process
|
## Build process
|
||||||
|
|
||||||
@@ -332,6 +336,64 @@ end
|
|||||||
**NOTE**: Using the `build_settings` method will cause GEM's all build command settings
|
**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.
|
directly written in the block passed to `MRuby::Gem::Specification.new` to be ignored.
|
||||||
|
|
||||||
|
## Platform Ports (ports/)
|
||||||
|
|
||||||
|
A gem may ship platform-specific C sources under `ports/<name>/`
|
||||||
|
subdirectories. The build configuration selects which port name(s)
|
||||||
|
are active via `conf.ports`, and each gem compiles the sources of
|
||||||
|
the first matching `ports/<name>/` it ships:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
MRuby::Build.new do |conf|
|
||||||
|
conf.toolchain
|
||||||
|
conf.ports :posix # selects ports/posix/ across all gems
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
`conf.ports` accepts multiple names as a fallback chain. Each gem
|
||||||
|
picks the first directory in the list that exists on its side:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
conf.ports :rp2040, :posix # try rp2040 per-gem, else posix
|
||||||
|
```
|
||||||
|
|
||||||
|
Host builds auto-detect `:posix` or `:win` when `conf.ports` is
|
||||||
|
not set. Sources outside `ports/` (i.e. `src/`) are always
|
||||||
|
compiled regardless of the port selection.
|
||||||
|
|
||||||
|
### External HAL Provider Gems
|
||||||
|
|
||||||
|
A third-party gem may replace another gem's bundled port at build
|
||||||
|
time. A gem whose name matches `hal-<short>-<conf>` is recognized
|
||||||
|
as the external HAL provider for the target gem whose name's last
|
||||||
|
`-`-separated segment is `<short>`. For example, `hal-task-glib`
|
||||||
|
overrides the HAL of `mruby-task`; `hal-io-uring` would override
|
||||||
|
`mruby-io`. The HAL provider must depend on its target so it can
|
||||||
|
`#include` the target's HAL header:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
MRuby::Gem::Specification.new('hal-task-glib') do |spec|
|
||||||
|
spec.license = 'MIT'
|
||||||
|
spec.author = 'Your Name'
|
||||||
|
spec.summary = 'GLib HAL for mruby-task'
|
||||||
|
spec.add_dependency 'mruby-task', core: 'mruby-task'
|
||||||
|
# src/ contains the HAL implementation
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
When a matching HAL provider gem is present in the build, the
|
||||||
|
target gem's `ports/<conf.ports>/` sources are dropped from the
|
||||||
|
build automatically. The HAL provider's own sources supply the
|
||||||
|
implementation instead, avoiding duplicate symbol errors at link
|
||||||
|
time. Loading two gems that match the same `hal-<short>-*`
|
||||||
|
prefix is a build error.
|
||||||
|
|
||||||
|
The naming convention is the only signal -- no spec attribute,
|
||||||
|
no `add_dependency` flag is required. A gem author who wants to
|
||||||
|
contribute an additional bundled port upstream sends a PR adding
|
||||||
|
`<target-gem>/ports/<name>/`; a gem author who prefers to ship
|
||||||
|
out of tree publishes a `hal-<short>-<conf>` gem instead.
|
||||||
|
|
||||||
## C Extension
|
## C Extension
|
||||||
|
|
||||||
mruby can be extended with C. This is possible by using the C API to
|
mruby can be extended with C. This is possible by using the C API to
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ Defined in `include/mruby/class.h`:
|
|||||||
|
|
||||||
```c
|
```c
|
||||||
union mrb_mt_ptr {
|
union mrb_mt_ptr {
|
||||||
|
mrb_func_t func; /* first member: see MRB_MT_ENTRY note */
|
||||||
const struct RProc *proc;
|
const struct RProc *proc;
|
||||||
mrb_func_t func;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct mrb_mt_entry {
|
typedef struct mrb_mt_entry {
|
||||||
@@ -139,9 +139,12 @@ typedef struct mrb_mt_tbl {
|
|||||||
|
|
||||||
```c
|
```c
|
||||||
/* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with
|
/* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with
|
||||||
MRB_MT_PRIVATE. The macro OR's in MRB_MT_FUNC automatically. */
|
MRB_MT_PRIVATE. The macro OR's in MRB_MT_FUNC automatically.
|
||||||
|
`func` must be the first member of `union mrb_mt_ptr` so that
|
||||||
|
positional initialization works on legacy C++ compilers that do
|
||||||
|
not accept C99 designated initializers. */
|
||||||
#define MRB_MT_ENTRY(fn, sym, flags) \
|
#define MRB_MT_ENTRY(fn, sym, flags) \
|
||||||
{ { .func = (fn) }, (sym), (flags) | MRB_MT_FUNC }
|
{ { (fn) }, (sym), (flags) | MRB_MT_FUNC }
|
||||||
|
|
||||||
/* Extract aspec from combined flags */
|
/* Extract aspec from combined flags */
|
||||||
#define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff))
|
#define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff))
|
||||||
|
|||||||
+144
-14
@@ -131,7 +131,9 @@ limit = (GC_STEP_SIZE / 100) * step_ratio
|
|||||||
```
|
```
|
||||||
|
|
||||||
With default `step_ratio = 200` and `GC_STEP_SIZE = 1024`, the
|
With default `step_ratio = 200` and `GC_STEP_SIZE = 1024`, the
|
||||||
limit is 2048 objects per step.
|
limit is 2048 objects per step. After each step, `gc_debt` is
|
||||||
|
decremented by the actual number of objects processed, so larger
|
||||||
|
steps repay more debt.
|
||||||
|
|
||||||
When the gray stack is exhausted, the final marking phase re-marks
|
When the gray stack is exhausted, the final marking phase re-marks
|
||||||
the arena and global variables to catch objects created during
|
the arena and global variables to catch objects created during
|
||||||
@@ -273,7 +275,7 @@ From Ruby: `GC.generational_mode = true/false`.
|
|||||||
`mrb_obj_alloc()` is the core allocation function:
|
`mrb_obj_alloc()` is the core allocation function:
|
||||||
|
|
||||||
1. If `MRB_GC_STRESS` is defined, run a full GC
|
1. If `MRB_GC_STRESS` is defined, run a full GC
|
||||||
2. If `gc->live >= gc->threshold`, run `mrb_incremental_gc()`
|
2. Increment `gc->gc_debt`; if positive, run `mrb_incremental_gc()`
|
||||||
3. Ensure arena has space (`gc_arena_keep`)
|
3. Ensure arena has space (`gc_arena_keep`)
|
||||||
4. Pop an object from the freelist of `gc->free_heaps`
|
4. Pop an object from the freelist of `gc->free_heaps`
|
||||||
5. If no free pages, allocate a new page (`add_heap`)
|
5. If no free pages, allocate a new page (`add_heap`)
|
||||||
@@ -299,18 +301,43 @@ The object's type is set to `MRB_TT_FREE` after freeing.
|
|||||||
|
|
||||||
## Triggering GC
|
## Triggering GC
|
||||||
|
|
||||||
### Automatic
|
### Debt Model
|
||||||
|
|
||||||
GC runs automatically when `gc->live >= gc->threshold` during
|
GC uses a **debt-based feedback model** to balance allocation
|
||||||
object allocation. After each cycle:
|
rate against collection work. The key field is `gc->gc_debt`
|
||||||
|
(signed integer):
|
||||||
|
|
||||||
|
- **Negative** = credit (GC is ahead, no collection needed)
|
||||||
|
- **Zero** = balanced
|
||||||
|
- **Positive** = debt (allocation outpacing collection, GC runs)
|
||||||
|
|
||||||
|
Each object allocation increments `gc_debt` by 1. When debt
|
||||||
|
goes positive, `mrb_incremental_gc()` runs. Each incremental
|
||||||
|
step decrements debt by `GC_STEP_SIZE` (1024), giving credit
|
||||||
|
for many future allocations.
|
||||||
|
|
||||||
|
When a GC cycle completes, credit is calculated from
|
||||||
|
`interval_ratio`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
threshold = (live_after_mark / 100) * interval_ratio
|
credit = (live_after_mark / 100) * interval_ratio - live_after_mark
|
||||||
minimum: GC_STEP_SIZE (1024)
|
minimum: GC_STEP_SIZE (1024)
|
||||||
|
gc_debt = -credit
|
||||||
```
|
```
|
||||||
|
|
||||||
With default `interval_ratio = 200`, GC triggers when live objects
|
With default `interval_ratio = 200` and 1000 live objects:
|
||||||
roughly double.
|
`credit = (1000/100)*200 - 1000 = 1000`, so approximately 1000
|
||||||
|
allocations can occur before the next GC cycle begins.
|
||||||
|
|
||||||
|
### Malloc Pressure
|
||||||
|
|
||||||
|
When `gc->malloc_threshold` is set (non-zero), the GC also
|
||||||
|
tracks bytes allocated through `mrb_realloc_simple()` in
|
||||||
|
`gc->malloc_increase`. When `malloc_increase` exceeds
|
||||||
|
`malloc_threshold`, the counter resets and an incremental GC
|
||||||
|
step runs. This captures memory pressure from large buffers
|
||||||
|
(e.g., long strings) that would otherwise be invisible to the
|
||||||
|
object-count-based debt model.
|
||||||
|
|
||||||
### Manual
|
### Manual
|
||||||
|
|
||||||
@@ -333,6 +360,7 @@ From Ruby: `GC.start`.
|
|||||||
| `MRB_GC_FIXED_ARENA` | off | Use fixed-size arena |
|
| `MRB_GC_FIXED_ARENA` | off | Use fixed-size arena |
|
||||||
| `MRB_GC_TURN_OFF_GENERATIONAL` | off | Disable generational mode |
|
| `MRB_GC_TURN_OFF_GENERATIONAL` | off | Disable generational mode |
|
||||||
| `MRB_GC_STRESS` | off | Full GC on every allocation (debug) |
|
| `MRB_GC_STRESS` | off | Full GC on every allocation (debug) |
|
||||||
|
| `MRB_GC_STATS` | off | Enable GC statistics counters |
|
||||||
| `MRB_USE_MALLOC_TRIM` | off | Call `malloc_trim()` after full GC |
|
| `MRB_USE_MALLOC_TRIM` | off | Call `malloc_trim()` after full GC |
|
||||||
|
|
||||||
### Runtime
|
### Runtime
|
||||||
@@ -340,18 +368,120 @@ From Ruby: `GC.start`.
|
|||||||
From Ruby code:
|
From Ruby code:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
GC.interval_ratio = 200 # threshold = live * ratio / 100
|
GC.interval_ratio = 200 # controls debt credit after GC cycle
|
||||||
GC.step_ratio = 200 # objects per incremental step
|
GC.step_ratio = 200 # objects per incremental step
|
||||||
|
GC.step_limit = 0 # 0=unlimited, >0=absolute step cap
|
||||||
|
GC.malloc_threshold = 0 # 0=disabled, >0=bytes to trigger GC
|
||||||
GC.generational_mode = true
|
GC.generational_mode = true
|
||||||
GC.start # force full GC
|
GC.start # force full GC
|
||||||
GC.enable # re-enable GC
|
GC.enable # re-enable GC
|
||||||
GC.disable # disable GC
|
GC.disable # disable GC
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### GC Statistics
|
||||||
|
|
||||||
|
`GC.stat` returns a Hash with GC state and statistics:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
GC.stat
|
||||||
|
# => {
|
||||||
|
# :live => 5432, # live object count
|
||||||
|
# :debt => -1024, # GC debt (negative=credit, positive=behind)
|
||||||
|
# :state => 0, # 0=root, 1=marking, 2=sweeping
|
||||||
|
# :generational => true, # generational mode enabled
|
||||||
|
# :full => false, # major GC in progress
|
||||||
|
# :step_limit => 0, # current step limit setting
|
||||||
|
# :malloc_increase => 8192, # malloc bytes since last cycle
|
||||||
|
# :malloc_threshold => 0, # current malloc threshold setting
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
With `MRB_GC_STATS` enabled, additional keys are available:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# :total => 15, # total GC invocations
|
||||||
|
# :minor => 12, # minor GC count
|
||||||
|
# :major => 3, # major GC count
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tuning Guide
|
||||||
|
|
||||||
|
**`interval_ratio`** (default 200): Controls how many allocations
|
||||||
|
occur between GC cycles. Higher values reduce GC frequency but
|
||||||
|
increase peak memory. The debt credit after each cycle is
|
||||||
|
`(live_after_mark / 100) * interval_ratio - live_after_mark`.
|
||||||
|
|
||||||
|
**`step_ratio`** (default 200): Controls how much work each
|
||||||
|
incremental step performs. Higher values make each step larger,
|
||||||
|
reducing total GC overhead but increasing individual pause times.
|
||||||
|
|
||||||
|
**`step_limit`** (default 0, unlimited): Caps the maximum work
|
||||||
|
per incremental step regardless of `step_ratio`. Useful for
|
||||||
|
real-time applications that need bounded pause times. The
|
||||||
|
effective step size is `min(step_ratio calculation, step_limit)`.
|
||||||
|
|
||||||
|
**`malloc_threshold`** (default 0, disabled): Triggers GC when
|
||||||
|
cumulative `malloc`/`realloc` bytes exceed this threshold. Useful
|
||||||
|
when applications allocate large buffers (strings, data objects)
|
||||||
|
that create memory pressure without proportional object count
|
||||||
|
increase.
|
||||||
|
|
||||||
|
### Practical Tuning Examples
|
||||||
|
|
||||||
|
**Allocation-heavy workloads** (many short-lived Procs, closures,
|
||||||
|
blocks): GC sweep dominates because of high object churn. Increase
|
||||||
|
`interval_ratio` to reduce GC frequency:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
GC.interval_ratio = 400 # ~12% faster than default (200)
|
||||||
|
```
|
||||||
|
|
||||||
|
Higher values (400-600) reduce sweep overhead at the cost of more
|
||||||
|
dead objects accumulating before collection. Values above 600 show
|
||||||
|
diminishing returns. Peak memory usage increases temporarily, but
|
||||||
|
live object count after GC remains the same.
|
||||||
|
|
||||||
|
**CPU-intensive workloads** (numeric computation, recursive methods
|
||||||
|
with no object allocation): GC parameters have negligible impact
|
||||||
|
because GC rarely runs. No tuning needed.
|
||||||
|
|
||||||
|
**Real-time or latency-sensitive** applications: Use `step_limit`
|
||||||
|
to bound pause times:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
GC.step_limit = 256 # cap incremental step to 256 objects
|
||||||
|
```
|
||||||
|
|
||||||
|
This makes GC pauses more predictable but increases total GC
|
||||||
|
overhead (more steps needed per cycle).
|
||||||
|
|
||||||
|
**Large buffer workloads** (reading files, building long strings):
|
||||||
|
Set `malloc_threshold` to trigger GC when buffer allocations
|
||||||
|
accumulate, even if object count is low:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
GC.malloc_threshold = 1024 * 1024 # trigger GC per ~1MB allocated
|
||||||
|
```
|
||||||
|
|
||||||
|
### Diagnosing GC Overhead
|
||||||
|
|
||||||
|
Use `GC.stat` to monitor GC behavior at runtime:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
s = GC.stat
|
||||||
|
puts "live objects: #{s[:live]}"
|
||||||
|
puts "GC debt: #{s[:debt]}" # positive = GC is behind
|
||||||
|
puts "GC state: #{s[:state]}" # 0=idle, 1=marking, 2=sweeping
|
||||||
|
```
|
||||||
|
|
||||||
|
If `debt` is frequently positive during performance-critical
|
||||||
|
sections, increase `interval_ratio`. If memory usage is too high,
|
||||||
|
decrease it.
|
||||||
|
|
||||||
## Source Files
|
## Source Files
|
||||||
|
|
||||||
| File | Contents |
|
| File | Contents |
|
||||||
| -------------------- | --------------------------------- |
|
| -------------------- | --------------------------------- |
|
||||||
| `src/gc.c` | GC implementation (~1400 lines) |
|
| `src/gc.c` | GC implementation |
|
||||||
| `include/mruby/gc.h` | `mrb_gc` structure, public GC API |
|
| `include/mruby/gc.h` | `mrb_gc` structure, public GC API |
|
||||||
| `include/mruby.h` | Arena save/restore macros |
|
| `include/mruby.h` | Arena save/restore macros |
|
||||||
|
|||||||
+165
-6
@@ -15,6 +15,23 @@ This document is collecting these limitations.
|
|||||||
This document does not contain a complete list of limitations.
|
This document does not contain a complete list of limitations.
|
||||||
Please help to improve it by submitting your findings.
|
Please help to improve it by submitting your findings.
|
||||||
|
|
||||||
|
## Features provided by mrbgems
|
||||||
|
|
||||||
|
Many Ruby features that CRuby builds into its core are provided by
|
||||||
|
mrbgems in mruby. Which features are actually available depends on
|
||||||
|
which mrbgems are linked into the build. The `default.gembox` and
|
||||||
|
`stdlib.gembox` cover the common cases, but a minimal build can omit
|
||||||
|
familiar features such as `Kernel#binding` (provided by
|
||||||
|
`mruby-binding`), `Kernel#catch`/`throw` (by `mruby-catch`),
|
||||||
|
`Enumerable` extensions, `Comparable`, IO, regular expressions, and
|
||||||
|
many more.
|
||||||
|
|
||||||
|
This is by design rather than a limitation per se. When porting Ruby
|
||||||
|
code to mruby, a `NoMethodError` or `NameError` often means "the gem
|
||||||
|
providing this feature is not linked in" rather than "mruby does not
|
||||||
|
support it." Adding the relevant gem to the build configuration is
|
||||||
|
usually enough.
|
||||||
|
|
||||||
## `Kernel.raise` in rescue clause
|
## `Kernel.raise` in rescue clause
|
||||||
|
|
||||||
`Kernel.raise` without arguments does not raise the current exception within
|
`Kernel.raise` without arguments does not raise the current exception within
|
||||||
@@ -133,12 +150,6 @@ The re-defined `+` operator does not accept any arguments.
|
|||||||
`'ab'`
|
`'ab'`
|
||||||
Behavior of the operator wasn't changed.
|
Behavior of the operator wasn't changed.
|
||||||
|
|
||||||
## `Kernel#binding` is not supported without mruby-binding gem
|
|
||||||
|
|
||||||
`Kernel#binding` method requires the `mruby-binding` gem (included
|
|
||||||
in the `metaprog` gembox). Without this gem, `binding` is not
|
|
||||||
available.
|
|
||||||
|
|
||||||
## `nil?` redefinition in conditional expressions
|
## `nil?` redefinition in conditional expressions
|
||||||
|
|
||||||
Redefinition of `nil?` is ignored in conditional expressions.
|
Redefinition of `nil?` is ignored in conditional expressions.
|
||||||
@@ -290,3 +301,151 @@ arbitrary-precision integers when included.
|
|||||||
(included in the `stdlib` gembox). Even with the gem,
|
(included in the `stdlib` gembox). Even with the gem,
|
||||||
`ObjectSpace.each_object` has limited functionality compared
|
`ObjectSpace.each_object` has limited functionality compared
|
||||||
to CRuby.
|
to CRuby.
|
||||||
|
|
||||||
|
## No Implicit Type Conversion (`to_int`, `to_str`, `to_ary`, ...)
|
||||||
|
|
||||||
|
mruby does not perform implicit type conversion through methods
|
||||||
|
like `to_int`, `to_str`, `to_ary`, or `to_hash`. CRuby uses these
|
||||||
|
to let user-defined classes duck-type as built-in types — for
|
||||||
|
example `Array#[]` calls `to_int` on its argument, `String#+` calls
|
||||||
|
`to_str`, and multiple assignment calls `to_ary` on its right-hand
|
||||||
|
side. mruby's built-in operations require the actual built-in type
|
||||||
|
and do not consult these conversion methods.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class MyInt; def to_int; 42; end; end
|
||||||
|
class MyStr; def to_str; "x"; end; end
|
||||||
|
class MyAry; def to_ary; [1,2,3]; end; end
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CRuby
|
||||||
|
|
||||||
|
```
|
||||||
|
[1,2,3][MyInt.new] # => nil (to_int called -> ary[42])
|
||||||
|
"a" + MyStr.new # => "ax" (to_str called)
|
||||||
|
a, b, c = MyAry.new # => a=1, b=2, c=3 (to_ary called)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### mruby
|
||||||
|
|
||||||
|
```
|
||||||
|
[1,2,3][MyInt.new] # TypeError
|
||||||
|
"a" + MyStr.new # TypeError
|
||||||
|
a, b, c = MyAry.new # a=<MyAry obj>, b=nil, c=nil (treated as single value)
|
||||||
|
```
|
||||||
|
|
||||||
|
Identity versions of `to_int`, `to_str`, `to_sym`, and `to_hash`
|
||||||
|
remain defined on the corresponding built-in types so that
|
||||||
|
`respond_to?(:to_str)`-style checks work for built-in instances.
|
||||||
|
`Float#to_int` and `Array#to_ary` are intentionally not defined.
|
||||||
|
|
||||||
|
Explicit conversion methods (`to_i`, `to_s`, `to_a`) work as in
|
||||||
|
CRuby and are called by features such as string interpolation and
|
||||||
|
the splat operator (`*obj`).
|
||||||
|
|
||||||
|
This is a deliberate trade-off: implicit conversion forces every
|
||||||
|
coercion site to go through method dispatch and can silently mask
|
||||||
|
type-mismatch bugs.
|
||||||
|
|
||||||
|
## Nested `def` in Singleton-Method Context
|
||||||
|
|
||||||
|
`def` written inside a singleton method (`def self.foo`) is placed
|
||||||
|
on a different class in mruby than in CRuby. CRuby registers the
|
||||||
|
inner method as an instance method of the lexical enclosing class.
|
||||||
|
mruby registers it as a method of the enclosing receiver's
|
||||||
|
singleton class, which makes it visible as a class method of the
|
||||||
|
enclosing class.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class SomeClass
|
||||||
|
def self.class_method
|
||||||
|
def nested; 'nested!'; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
SomeClass.class_method
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CRuby
|
||||||
|
|
||||||
|
```
|
||||||
|
SomeClass.nested # NoMethodError
|
||||||
|
SomeClass.new.nested # => "nested!" (instance method)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### mruby
|
||||||
|
|
||||||
|
```
|
||||||
|
SomeClass.nested # => "nested!" (class method)
|
||||||
|
SomeClass.new.nested # NoMethodError
|
||||||
|
```
|
||||||
|
|
||||||
|
Writing nested `def` like this is unusual; this difference rarely
|
||||||
|
surfaces in practical code.
|
||||||
|
|
||||||
|
## `Proc#dup` / `Proc#clone` is Always Orphan
|
||||||
|
|
||||||
|
A `dup` or `clone` of a block given to a method is always treated as
|
||||||
|
an orphan block in mruby — calling it raises `LocalJumpError` if the
|
||||||
|
block contains `break` or `return`. CRuby is finer-grained: the copy
|
||||||
|
inherits the orphan status of its original, so the copy only becomes
|
||||||
|
orphan once the original yielding method returns.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
def m(&b)
|
||||||
|
b.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
x = m { break 1 }
|
||||||
|
x.call
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CRuby
|
||||||
|
|
||||||
|
```
|
||||||
|
LocalJumpError # raised only after m returns; if called inside m,
|
||||||
|
# the dup is still a live block
|
||||||
|
```
|
||||||
|
|
||||||
|
#### mruby
|
||||||
|
|
||||||
|
```
|
||||||
|
LocalJumpError # always raised — the dup is orphan from the moment
|
||||||
|
# it is created
|
||||||
|
```
|
||||||
|
|
||||||
|
mruby's stricter rule keeps `RProc` from needing a back-pointer to
|
||||||
|
the original block (which would also enlarge the GC mark set).
|
||||||
|
|
||||||
|
## `Class#initialize` Can Be Re-Invoked
|
||||||
|
|
||||||
|
CRuby raises `TypeError: already initialized class` when `initialize`
|
||||||
|
is invoked on a class that has already been set up. mruby's
|
||||||
|
`Class#initialize` has no such guard — invoking it on an existing
|
||||||
|
class through `__send__`, `send`, or `UnboundMethod#bind_call`
|
||||||
|
silently succeeds. The superclass argument is ignored in this case,
|
||||||
|
so the call cannot rewrite the class hierarchy; only the block (if
|
||||||
|
any) is evaluated with the class as receiver.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Klass = Class.new
|
||||||
|
Klass.__send__(:initialize) {}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CRuby
|
||||||
|
|
||||||
|
```
|
||||||
|
TypeError: already initialized class
|
||||||
|
```
|
||||||
|
|
||||||
|
#### mruby
|
||||||
|
|
||||||
|
```
|
||||||
|
The block is evaluated in the context of Klass; no error is raised.
|
||||||
|
The superclass is not changed even when one is passed as an argument.
|
||||||
|
```
|
||||||
|
|
||||||
|
`Module#initialize` is re-callable in both implementations, so this
|
||||||
|
divergence is `Class`-specific. Adding the CRuby check would require
|
||||||
|
an additional flag bit on every `RClass`; mruby leaves the bit
|
||||||
|
unspent because no destructive side effects are possible through
|
||||||
|
this path.
|
||||||
|
|||||||
@@ -172,6 +172,13 @@
|
|||||||
#define MRB_SYMBOL_LINEAR_THRESHOLD 256
|
#define MRB_SYMBOL_LINEAR_THRESHOLD 256
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Maximum number of dynamic symbols (created at runtime via to_sym etc.)
|
||||||
|
Presyms, inline symbols, and mrb_intern_static symbols are excluded.
|
||||||
|
Set to 0 to disable the limit. */
|
||||||
|
#ifndef MRB_SYMBOL_MAX
|
||||||
|
#define MRB_SYMBOL_MAX 4096
|
||||||
|
#endif
|
||||||
|
|
||||||
/* obsolete configurations */
|
/* obsolete configurations */
|
||||||
#if defined(DISABLE_STDIO) || defined(MRB_DISABLE_STDIO)
|
#if defined(DISABLE_STDIO) || defined(MRB_DISABLE_STDIO)
|
||||||
# define MRB_NO_STDIO
|
# define MRB_NO_STDIO
|
||||||
|
|||||||
+54
-9
@@ -113,6 +113,8 @@
|
|||||||
|
|
||||||
#include "mrbconf.h"
|
#include "mrbconf.h"
|
||||||
|
|
||||||
|
typedef struct mrb_state mrb_state;
|
||||||
|
|
||||||
#include <mruby/common.h>
|
#include <mruby/common.h>
|
||||||
#include <mruby/value.h>
|
#include <mruby/value.h>
|
||||||
#include <mruby/gc.h>
|
#include <mruby/gc.h>
|
||||||
@@ -156,8 +158,6 @@ typedef uint32_t mrb_aspec;
|
|||||||
|
|
||||||
typedef struct mrb_irep mrb_irep;
|
typedef struct mrb_irep mrb_irep;
|
||||||
|
|
||||||
struct mrb_state;
|
|
||||||
|
|
||||||
#ifndef MRB_FIXED_STATE_ATEXIT_STACK_SIZE
|
#ifndef MRB_FIXED_STATE_ATEXIT_STACK_SIZE
|
||||||
#define MRB_FIXED_STATE_ATEXIT_STACK_SIZE 5
|
#define MRB_FIXED_STATE_ATEXIT_STACK_SIZE 5
|
||||||
#endif
|
#endif
|
||||||
@@ -224,7 +224,7 @@ mrb_static_assert_powerof2(MRB_METHOD_CACHE_SIZE);
|
|||||||
* @param self The self object
|
* @param self The self object
|
||||||
* @return [mrb_value] The function's return value
|
* @return [mrb_value] The function's return value
|
||||||
*/
|
*/
|
||||||
typedef mrb_value (*mrb_func_t)(struct mrb_state *mrb, mrb_value self);
|
typedef mrb_value (*mrb_func_t)(mrb_state *mrb, mrb_value self);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t flags; /* method flags (no symbol packed) */
|
uint32_t flags; /* method flags (no symbol packed) */
|
||||||
@@ -243,9 +243,26 @@ struct mrb_cache_entry {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef MRB_CONST_CACHE_SIZE
|
||||||
|
# undef MRB_NO_CONST_CACHE
|
||||||
|
mrb_static_assert_powerof2(MRB_CONST_CACHE_SIZE);
|
||||||
|
#else
|
||||||
|
/* default constant cache size: 64 */
|
||||||
|
/* cache size needs to be power of 2 */
|
||||||
|
# define MRB_CONST_CACHE_SIZE (1<<6)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MRB_NO_CONST_CACHE
|
||||||
|
struct mrb_const_cache_entry {
|
||||||
|
const struct mrb_irep *irep;
|
||||||
|
mrb_sym sym;
|
||||||
|
mrb_value value;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
struct mrb_jmpbuf;
|
struct mrb_jmpbuf;
|
||||||
|
|
||||||
typedef void (*mrb_atexit_func)(struct mrb_state*);
|
typedef void (*mrb_atexit_func)(mrb_state*);
|
||||||
|
|
||||||
#ifdef MRB_USE_TASK_SCHEDULER
|
#ifdef MRB_USE_TASK_SCHEDULER
|
||||||
struct mrb_task;
|
struct mrb_task;
|
||||||
@@ -257,10 +274,12 @@ typedef struct mrb_task_state {
|
|||||||
volatile mrb_bool switching; /* Context switch pending flag */
|
volatile mrb_bool switching; /* Context switch pending flag */
|
||||||
struct mrb_task *main_task; /* Main task wrapper for root context */
|
struct mrb_task *main_task; /* Main task wrapper for root context */
|
||||||
uint8_t scheduler_lock; /* Lock counter for synchronous execution */
|
uint8_t scheduler_lock; /* Lock counter for synchronous execution */
|
||||||
|
mrb_bool loop_running; /* Active mrb_task_run loop flag */
|
||||||
|
mrb_bool exception_as_result; /* Return unhandled task exceptions as values */
|
||||||
} mrb_task_state;
|
} mrb_task_state;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct mrb_state {
|
struct mrb_state {
|
||||||
struct mrb_jmpbuf *jmp;
|
struct mrb_jmpbuf *jmp;
|
||||||
|
|
||||||
struct mrb_context *c;
|
struct mrb_context *c;
|
||||||
@@ -297,22 +316,28 @@ typedef struct mrb_state {
|
|||||||
struct mrb_cache_entry cache[MRB_METHOD_CACHE_SIZE];
|
struct mrb_cache_entry cache[MRB_METHOD_CACHE_SIZE];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef MRB_NO_CONST_CACHE
|
||||||
|
struct mrb_const_cache_entry const_cache[MRB_CONST_CACHE_SIZE];
|
||||||
|
#endif
|
||||||
|
|
||||||
mrb_sym symidx;
|
mrb_sym symidx;
|
||||||
const char **symtbl;
|
const char **symtbl;
|
||||||
|
uint8_t *sym_flags; /* per-symbol flags (SYM_FL_*) */
|
||||||
size_t symcapa;
|
size_t symcapa;
|
||||||
struct mrb_sym_hash_table *symhash;
|
struct mrb_sym_hash_table *symhash;
|
||||||
void *sym_pool;
|
void *sym_pool;
|
||||||
|
mrb_sym dynamic_sym_count; /* count of dynamic (GC-candidate) symbols */
|
||||||
#ifndef MRB_USE_ALL_SYMBOLS
|
#ifndef MRB_USE_ALL_SYMBOLS
|
||||||
char symbuf[8]; /* buffer for small symbol names */
|
char symbuf[8]; /* buffer for small symbol names */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MRB_USE_DEBUG_HOOK
|
#ifdef MRB_USE_DEBUG_HOOK
|
||||||
void (*code_fetch_hook)(struct mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs);
|
void (*code_fetch_hook)(mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs);
|
||||||
void (*debug_op_hook)(struct mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs);
|
void (*debug_op_hook)(mrb_state* mrb, const struct mrb_irep *irep, const mrb_code *pc, mrb_value *regs);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef MRB_BYTECODE_DECODE_OPTION
|
#ifdef MRB_BYTECODE_DECODE_OPTION
|
||||||
mrb_code (*bytecode_decoder)(struct mrb_state* mrb, mrb_code code);
|
mrb_code (*bytecode_decoder)(mrb_state* mrb, mrb_code code);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct RClass *eException_class;
|
struct RClass *eException_class;
|
||||||
@@ -339,7 +364,7 @@ typedef struct mrb_state {
|
|||||||
#ifdef MRB_USE_TASK_SCHEDULER
|
#ifdef MRB_USE_TASK_SCHEDULER
|
||||||
mrb_task_state task; /* Task scheduler state */
|
mrb_task_state task; /* Task scheduler state */
|
||||||
#endif
|
#endif
|
||||||
} mrb_state;
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a new class.
|
* Defines a new class.
|
||||||
@@ -1159,6 +1184,21 @@ MRB_API mrb_value mrb_funcall_id(mrb_state *mrb, mrb_value val, mrb_sym mid, mrb
|
|||||||
* @see mrb_funcall
|
* @see mrb_funcall
|
||||||
*/
|
*/
|
||||||
MRB_API mrb_value mrb_funcall_argv(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_int argc, const mrb_value *argv);
|
MRB_API mrb_value mrb_funcall_argv(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_int argc, const mrb_value *argv);
|
||||||
|
/*
|
||||||
|
* Convenience wrappers for `mrb_funcall_argv` with a fixed argument count.
|
||||||
|
* Avoids the 16-slot fixed argv buffer used by the variadic `mrb_funcall_id`.
|
||||||
|
*/
|
||||||
|
MRB_INLINE mrb_value
|
||||||
|
mrb_funcall_argv1(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_value a1)
|
||||||
|
{
|
||||||
|
return mrb_funcall_argv(mrb, val, name, 1, &a1);
|
||||||
|
}
|
||||||
|
MRB_INLINE mrb_value
|
||||||
|
mrb_funcall_argv2(mrb_state *mrb, mrb_value val, mrb_sym name, mrb_value a1, mrb_value a2)
|
||||||
|
{
|
||||||
|
const mrb_value argv[] = { a1, a2 };
|
||||||
|
return mrb_funcall_argv(mrb, val, name, 2, argv);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Call existing Ruby functions with a block.
|
* Call existing Ruby functions with a block.
|
||||||
*/
|
*/
|
||||||
@@ -1284,6 +1324,11 @@ MRB_API void mrb_method_cache_clear(mrb_state *mrb);
|
|||||||
#else
|
#else
|
||||||
#define mrb_method_cache_clear(mrb) ((void)0)
|
#define mrb_method_cache_clear(mrb) ((void)0)
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef MRB_NO_CONST_CACHE
|
||||||
|
MRB_API void mrb_const_cache_clear(mrb_state *mrb);
|
||||||
|
#else
|
||||||
|
#define mrb_const_cache_clear(mrb) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if mrb_open() failed
|
* Check if mrb_open() failed
|
||||||
|
|||||||
+24
-2
@@ -20,7 +20,17 @@ typedef struct mrb_shared_array {
|
|||||||
mrb_value *ptr;
|
mrb_value *ptr;
|
||||||
} mrb_shared_array;
|
} mrb_shared_array;
|
||||||
|
|
||||||
#if defined(MRB_32BIT) && defined(MRB_NO_BOXING) && (!defined(MRB_USE_FLOAT32) || defined(MRB_INT64)) && !defined(MRB_ARY_NO_EMBED)
|
/* On 32-bit platforms whose ABI gives 8-byte members 8-byte alignment
|
||||||
|
(ARM, MIPS, xtensa, ...), an embedded mrb_value array forces 8-byte
|
||||||
|
alignment of the inner union, padding the heap-form layout and
|
||||||
|
inflating struct size past the 5-word RVALUE limit. Disable embedding
|
||||||
|
whenever mrb_value contains an 8-byte aligned member: nan-boxing
|
||||||
|
(uint64_t), or no-boxing with int64_t/double inside the union. */
|
||||||
|
#if defined(MRB_32BIT) && \
|
||||||
|
(defined(MRB_NAN_BOXING) || \
|
||||||
|
(defined(MRB_NO_BOXING) && \
|
||||||
|
(!defined(MRB_USE_FLOAT32) || defined(MRB_INT64)))) && \
|
||||||
|
!defined(MRB_ARY_NO_EMBED)
|
||||||
# define MRB_ARY_NO_EMBED
|
# define MRB_ARY_NO_EMBED
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -57,7 +67,7 @@ struct RArray {
|
|||||||
#define ARY_UNSET_EMBED_FLAG(a) (void)0
|
#define ARY_UNSET_EMBED_FLAG(a) (void)0
|
||||||
#define ARY_EMBED_LEN(a) 0
|
#define ARY_EMBED_LEN(a) 0
|
||||||
#define ARY_SET_EMBED_LEN(a,len) (void)0
|
#define ARY_SET_EMBED_LEN(a,len) (void)0
|
||||||
#define ARY_EMBED_PTR(a) 0
|
#define ARY_EMBED_PTR(a) ((mrb_value*)NULL)
|
||||||
#else
|
#else
|
||||||
#define MRB_ARY_EMBED_MASK 7
|
#define MRB_ARY_EMBED_MASK 7
|
||||||
#define ARY_EMBED_P(a) ((a)->flags & MRB_ARY_EMBED_MASK)
|
#define ARY_EMBED_P(a) ((a)->flags & MRB_ARY_EMBED_MASK)
|
||||||
@@ -71,6 +81,7 @@ struct RArray {
|
|||||||
#define ARY_PTR(a) (ARY_EMBED_P(a)?ARY_EMBED_PTR(a):(a)->as.heap.ptr)
|
#define ARY_PTR(a) (ARY_EMBED_P(a)?ARY_EMBED_PTR(a):(a)->as.heap.ptr)
|
||||||
#define RARRAY_LEN(a) ARY_LEN(RARRAY(a))
|
#define RARRAY_LEN(a) ARY_LEN(RARRAY(a))
|
||||||
#define RARRAY_PTR(a) ARY_PTR(RARRAY(a))
|
#define RARRAY_PTR(a) ARY_PTR(RARRAY(a))
|
||||||
|
#define RARRAY_GETMEM(a, ptr, len) ARY_GETMEM(RARRAY(a), ptr, len)
|
||||||
#define ARY_SET_LEN(a,n) do {\
|
#define ARY_SET_LEN(a,n) do {\
|
||||||
if (ARY_EMBED_P(a)) {\
|
if (ARY_EMBED_P(a)) {\
|
||||||
mrb_assert((n) <= MRB_ARY_EMBED_LEN_MAX); \
|
mrb_assert((n) <= MRB_ARY_EMBED_LEN_MAX); \
|
||||||
@@ -84,6 +95,17 @@ struct RArray {
|
|||||||
#define ARY_SHARED_P(a) ((a)->flags & MRB_ARY_SHARED)
|
#define ARY_SHARED_P(a) ((a)->flags & MRB_ARY_SHARED)
|
||||||
#define ARY_SET_SHARED_FLAG(a) ((a)->flags |= MRB_ARY_SHARED)
|
#define ARY_SET_SHARED_FLAG(a) ((a)->flags |= MRB_ARY_SHARED)
|
||||||
#define ARY_UNSET_SHARED_FLAG(a) ((a)->flags &= ~MRB_ARY_SHARED)
|
#define ARY_UNSET_SHARED_FLAG(a) ((a)->flags &= ~MRB_ARY_SHARED)
|
||||||
|
#define ARY_GETMEM(a, ptr, len) do { \
|
||||||
|
struct RArray *MRB_UNIQNAME(_a_) = (a); \
|
||||||
|
if (ARY_EMBED_P(MRB_UNIQNAME(_a_))) { \
|
||||||
|
(len) = ARY_EMBED_LEN(MRB_UNIQNAME(_a_)); \
|
||||||
|
(ptr) = ARY_EMBED_PTR(MRB_UNIQNAME(_a_)); \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
(len) = MRB_UNIQNAME(_a_)->as.heap.len; \
|
||||||
|
(ptr) = MRB_UNIQNAME(_a_)->as.heap.ptr; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
MRB_API void mrb_ary_modify(mrb_state*, struct RArray*);
|
MRB_API 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_dup(mrb_state*, mrb_value ary);
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ mrb_unboxed_type(mrb_value o)
|
|||||||
{
|
{
|
||||||
if (!mrb_float_p(o) && mrb_nb_tt(o) == MRB_NANBOX_TT_OBJECT && o.u != 0) {
|
if (!mrb_float_p(o) && mrb_nb_tt(o) == MRB_NANBOX_TT_OBJECT && o.u != 0) {
|
||||||
return ((struct RBasic*)(uintptr_t)o.u)->tt;
|
return ((struct RBasic*)(uintptr_t)o.u)->tt;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return MRB_TT_FALSE;
|
return MRB_TT_FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +151,7 @@ mrb_nan_boxing_value_int(mrb_value v)
|
|||||||
#define SET_TRUE_VALUE(r) NANBOX_SET_MISC_VALUE(r, MRB_TT_TRUE, 1)
|
#define SET_TRUE_VALUE(r) NANBOX_SET_MISC_VALUE(r, MRB_TT_TRUE, 1)
|
||||||
#define SET_BOOL_VALUE(r,b) NANBOX_SET_MISC_VALUE(r, (b) ? MRB_TT_TRUE : MRB_TT_FALSE, 1)
|
#define SET_BOOL_VALUE(r,b) NANBOX_SET_MISC_VALUE(r, (b) ? MRB_TT_TRUE : MRB_TT_FALSE, 1)
|
||||||
#ifdef MRB_INT64
|
#ifdef MRB_INT64
|
||||||
MRB_API mrb_value mrb_boxing_int_value(struct mrb_state*, mrb_int);
|
MRB_API mrb_value mrb_boxing_int_value(mrb_state*, mrb_int);
|
||||||
#define SET_INT_VALUE(mrb, r, n) ((r) = mrb_boxing_int_value(mrb, n))
|
#define SET_INT_VALUE(mrb, r, n) ((r) = mrb_boxing_int_value(mrb, n))
|
||||||
#else
|
#else
|
||||||
#define SET_INT_VALUE(mrb, r, n) SET_FIXNUM_VALUE(r, n)
|
#define SET_INT_VALUE(mrb, r, n) SET_FIXNUM_VALUE(r, n)
|
||||||
|
|||||||
@@ -165,11 +165,11 @@ mrb_val_union(mrb_value v)
|
|||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
MRB_API mrb_value mrb_word_boxing_cptr_value(struct mrb_state*, void*);
|
MRB_API mrb_value mrb_word_boxing_cptr_value(mrb_state*, void*);
|
||||||
#ifndef MRB_NO_FLOAT
|
#ifndef MRB_NO_FLOAT
|
||||||
MRB_API mrb_value mrb_word_boxing_float_value(struct mrb_state*, mrb_float);
|
MRB_API mrb_value mrb_word_boxing_float_value(mrb_state*, mrb_float);
|
||||||
#endif
|
#endif
|
||||||
MRB_API mrb_value mrb_boxing_int_value(struct mrb_state*, mrb_int);
|
MRB_API mrb_value mrb_boxing_int_value(mrb_state*, mrb_int);
|
||||||
|
|
||||||
#if WORDBOX_IMMEDIATE_MASK == 0x3
|
#if WORDBOX_IMMEDIATE_MASK == 0x3
|
||||||
#define mrb_immediate_p(o) ((o).w & WORDBOX_IMMEDIATE_MASK || (o).w <= MRB_Qundef)
|
#define mrb_immediate_p(o) ((o).w & WORDBOX_IMMEDIATE_MASK || (o).w <= MRB_Qundef)
|
||||||
@@ -266,9 +266,11 @@ mrb_unboxed_type(mrb_value o)
|
|||||||
{
|
{
|
||||||
if (mrb_nil_p(o)) {
|
if (mrb_nil_p(o)) {
|
||||||
return MRB_TT_FALSE;
|
return MRB_TT_FALSE;
|
||||||
} else if ((o.w & WORDBOX_IMMEDIATE_MASK) == 0) {
|
}
|
||||||
|
else if ((o.w & WORDBOX_IMMEDIATE_MASK) == 0) {
|
||||||
return mrb_val_union(o).bp->tt;
|
return mrb_val_union(o).bp->tt;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return MRB_TT_FALSE;
|
return MRB_TT_FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,10 +100,13 @@ void mrb_mc_clear_by_class(mrb_state *mrb, struct RClass* c);
|
|||||||
typedef int (mrb_mt_foreach_func)(mrb_state*,mrb_sym,mrb_method_t,void*);
|
typedef int (mrb_mt_foreach_func)(mrb_state*,mrb_sym,mrb_method_t,void*);
|
||||||
MRB_API void mrb_mt_foreach(mrb_state*, struct RClass*, mrb_mt_foreach_func*, void*);
|
MRB_API void mrb_mt_foreach(mrb_state*, struct RClass*, mrb_mt_foreach_func*, void*);
|
||||||
|
|
||||||
/* ROM method table types for static method registration */
|
/* ROM method table types for static method registration.
|
||||||
|
* NOTE: `func` is kept as the first union member so that positional
|
||||||
|
* aggregate initialization in MRB_MT_ENTRY works without C99
|
||||||
|
* designated initializers (required for legacy C++ compilers). */
|
||||||
union mrb_mt_ptr {
|
union mrb_mt_ptr {
|
||||||
const struct RProc *proc;
|
|
||||||
mrb_func_t func;
|
mrb_func_t func;
|
||||||
|
const struct RProc *proc;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* entry combining function pointer, symbol key, and flags */
|
/* entry combining function pointer, symbol key, and flags */
|
||||||
@@ -128,7 +131,7 @@ typedef struct mrb_mt_tbl {
|
|||||||
|
|
||||||
/* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with MRB_MT_PRIVATE. */
|
/* ROM table entry: 3rd param is MRB_ARGS_*() optionally OR'd with MRB_MT_PRIVATE. */
|
||||||
#define MRB_MT_ENTRY(fn, sym, flags) \
|
#define MRB_MT_ENTRY(fn, sym, flags) \
|
||||||
{ { .func = (fn) }, (sym), (flags) | MRB_MT_FUNC }
|
{ { (fn) }, (sym), (flags) | MRB_MT_FUNC }
|
||||||
#define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff))
|
#define MRB_MT_ASPEC(flags) ((mrb_aspec)((flags) & 0xffffff))
|
||||||
|
|
||||||
/* "removed" tombstone: MRB_MT_FUNC flag set with NULL function pointer.
|
/* "removed" tombstone: MRB_MT_FUNC flag set with NULL function pointer.
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ struct mrb_parser_state {
|
|||||||
mrb_sym* filename_table;
|
mrb_sym* filename_table;
|
||||||
uint16_t filename_table_length;
|
uint16_t filename_table_length;
|
||||||
uint16_t current_filename_index;
|
uint16_t current_filename_index;
|
||||||
|
uint16_t prev_file_lineno; /* saved lineno before partial_hook file switch */
|
||||||
|
|
||||||
/* Variable-sized node management */
|
/* Variable-sized node management */
|
||||||
mrb_ast_node *nvars;
|
mrb_ast_node *nvars;
|
||||||
|
|||||||
+27
-6
@@ -37,7 +37,14 @@ MRB_API mrb_value mrb_exc_new_str(mrb_state *mrb, struct RClass* c, mrb_value st
|
|||||||
#define mrb_exc_new_lit(mrb, c, lit) mrb_exc_new_str(mrb, c, mrb_str_new_lit(mrb, lit))
|
#define mrb_exc_new_lit(mrb, c, lit) mrb_exc_new_str(mrb, c, mrb_str_new_lit(mrb, lit))
|
||||||
MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_value args, const char *fmt, ...);
|
MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_value args, const char *fmt, ...);
|
||||||
|
|
||||||
#if defined(MRB_NAN_BOXING) || defined(MRB_WORD_BOXING) || defined(MRB_64BIT)
|
/* On 32-bit platforms whose ABI gives uint64_t/double 8-byte alignment
|
||||||
|
(ARM, MIPS, PowerPC, xtensa, ...), embedding mrb_value directly in
|
||||||
|
RBreak forces 8-byte alignment that pushes the struct past the 5-word
|
||||||
|
RVALUE budget via padding. Store the value bits as a uint32_t array
|
||||||
|
(4-byte aligned) to dodge that padding. Word-boxing's mrb_value is
|
||||||
|
just a uintptr_t with no over-alignment, and 64-bit platforms have no
|
||||||
|
alignment gap to begin with, so neither needs the workaround. */
|
||||||
|
#if defined(MRB_64BIT) || defined(MRB_WORD_BOXING)
|
||||||
#undef MRB_USE_RBREAK_VALUE_UNION
|
#undef MRB_USE_RBREAK_VALUE_UNION
|
||||||
#else
|
#else
|
||||||
#define MRB_USE_RBREAK_VALUE_UNION 1
|
#define MRB_USE_RBREAK_VALUE_UNION 1
|
||||||
@@ -45,7 +52,8 @@ MRB_API mrb_noreturn void mrb_no_method_error(mrb_state *mrb, mrb_sym id, mrb_va
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* flags:
|
* flags:
|
||||||
* 0..7: enum mrb_vtype (only when defined MRB_USE_RBREAK_VALUE_UNION)
|
* 0..7: enum mrb_vtype (only when MRB_USE_RBREAK_VALUE_UNION and
|
||||||
|
* !MRB_NAN_BOXING; nan-boxing encodes the type in the bits)
|
||||||
* 8..10: RBREAK_TAGs in src/vm.c (otherwise, set to 0)
|
* 8..10: RBREAK_TAGs in src/vm.c (otherwise, set to 0)
|
||||||
*/
|
*/
|
||||||
struct RBreak {
|
struct RBreak {
|
||||||
@@ -53,11 +61,11 @@ struct RBreak {
|
|||||||
uintptr_t ci_break_index; // The top-level ci index to break. One before the return destination.
|
uintptr_t ci_break_index; // The top-level ci index to break. One before the return destination.
|
||||||
#ifndef MRB_USE_RBREAK_VALUE_UNION
|
#ifndef MRB_USE_RBREAK_VALUE_UNION
|
||||||
mrb_value val;
|
mrb_value val;
|
||||||
|
#elif defined(MRB_NAN_BOXING)
|
||||||
|
/* nan-boxing: mrb_value is a single 64-bit word (type encoded in NaN bits) */
|
||||||
|
uint32_t value[sizeof(mrb_value) / sizeof(uint32_t)];
|
||||||
#else
|
#else
|
||||||
/* Store value as uint32_t words instead of union mrb_value_union
|
/* no-boxing: store only the union bits; tt goes in flags */
|
||||||
to avoid 8-byte alignment of int64_t/double on 32-bit platforms
|
|
||||||
(e.g., ARM, MIPS, PowerPC) which would inflate struct size beyond
|
|
||||||
the 5-word RVALUE limit due to padding. */
|
|
||||||
uint32_t value[sizeof(union mrb_value_union) / sizeof(uint32_t)];
|
uint32_t value[sizeof(union mrb_value_union) / sizeof(uint32_t)];
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
@@ -65,6 +73,19 @@ struct RBreak {
|
|||||||
#ifndef MRB_USE_RBREAK_VALUE_UNION
|
#ifndef MRB_USE_RBREAK_VALUE_UNION
|
||||||
#define mrb_break_value_get(brk) ((brk)->val)
|
#define mrb_break_value_get(brk) ((brk)->val)
|
||||||
#define mrb_break_value_set(brk, v) ((brk)->val = v)
|
#define mrb_break_value_set(brk, v) ((brk)->val = v)
|
||||||
|
#elif defined(MRB_NAN_BOXING)
|
||||||
|
static inline mrb_value
|
||||||
|
mrb_break_value_get(struct RBreak *brk)
|
||||||
|
{
|
||||||
|
mrb_value val;
|
||||||
|
memcpy(&val, brk->value, sizeof(val));
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
static inline void
|
||||||
|
mrb_break_value_set(struct RBreak *brk, mrb_value val)
|
||||||
|
{
|
||||||
|
memcpy(brk->value, &val, sizeof(val));
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
#define RBREAK_VALUE_TT_MASK ((1 << 8) - 1)
|
#define RBREAK_VALUE_TT_MASK ((1 << 8) - 1)
|
||||||
static inline mrb_value
|
static inline mrb_value
|
||||||
|
|||||||
+15
-9
@@ -14,15 +14,12 @@
|
|||||||
*/
|
*/
|
||||||
MRB_BEGIN_DECL
|
MRB_BEGIN_DECL
|
||||||
|
|
||||||
|
|
||||||
struct mrb_state;
|
|
||||||
|
|
||||||
#define MRB_EACH_OBJ_OK 0
|
#define MRB_EACH_OBJ_OK 0
|
||||||
#define MRB_EACH_OBJ_BREAK 1
|
#define MRB_EACH_OBJ_BREAK 1
|
||||||
typedef int (mrb_each_object_callback)(struct mrb_state *mrb, struct RBasic *obj, void *data);
|
typedef int (mrb_each_object_callback)(mrb_state *mrb, struct RBasic *obj, void *data);
|
||||||
void mrb_objspace_each_objects(struct mrb_state *mrb, mrb_each_object_callback *callback, void *data);
|
void mrb_objspace_each_objects(mrb_state *mrb, mrb_each_object_callback *callback, void *data);
|
||||||
size_t mrb_objspace_page_slot_size(void);
|
size_t mrb_objspace_page_slot_size(void);
|
||||||
MRB_API void mrb_free_context(struct mrb_state *mrb, struct mrb_context *c);
|
MRB_API void mrb_free_context(mrb_state *mrb, struct mrb_context *c);
|
||||||
|
|
||||||
#ifndef MRB_GC_ARENA_SIZE
|
#ifndef MRB_GC_ARENA_SIZE
|
||||||
#define MRB_GC_ARENA_SIZE 100
|
#define MRB_GC_ARENA_SIZE 100
|
||||||
@@ -48,7 +45,7 @@ typedef struct mrb_gc {
|
|||||||
mrb_bool gray_overflow:1; /* gray stack overflowed; needs heap rescan */
|
mrb_bool gray_overflow:1; /* gray stack overflowed; needs heap rescan */
|
||||||
size_t live; /* count of live objects */
|
size_t live; /* count of live objects */
|
||||||
size_t live_after_mark; /* old generation objects */
|
size_t live_after_mark; /* old generation objects */
|
||||||
size_t threshold; /* threshold to start GC */
|
mrb_int gc_debt; /* <0:credit, >0:needs GC */
|
||||||
size_t oldgen_threshold; /* threshold to kick major GC */
|
size_t oldgen_threshold; /* threshold to kick major GC */
|
||||||
mrb_gc_state state; /* current state of gc */
|
mrb_gc_state state; /* current state of gc */
|
||||||
int interval_ratio;
|
int interval_ratio;
|
||||||
@@ -59,6 +56,9 @@ typedef struct mrb_gc {
|
|||||||
mrb_bool generational :1; /* generational GC mode */
|
mrb_bool generational :1; /* generational GC mode */
|
||||||
mrb_bool full :1; /* major GC mode */
|
mrb_bool full :1; /* major GC mode */
|
||||||
mrb_bool out_of_memory :1; /* out-of-memory error occurred */
|
mrb_bool out_of_memory :1; /* out-of-memory error occurred */
|
||||||
|
size_t step_limit; /* 0=unlimited, >0=absolute step cap */
|
||||||
|
size_t malloc_increase; /* malloc bytes since last GC cycle */
|
||||||
|
size_t malloc_threshold; /* 0=disabled, >0=bytes to trigger GC */
|
||||||
|
|
||||||
#ifdef MRB_GC_FIXED_ARENA
|
#ifdef MRB_GC_FIXED_ARENA
|
||||||
struct RBasic *arena[MRB_GC_ARENA_SIZE]; /* GC protection array */
|
struct RBasic *arena[MRB_GC_ARENA_SIZE]; /* GC protection array */
|
||||||
@@ -67,10 +67,16 @@ typedef struct mrb_gc {
|
|||||||
int arena_capa; /* size of protection array */
|
int arena_capa; /* size of protection array */
|
||||||
#endif
|
#endif
|
||||||
int arena_idx;
|
int arena_idx;
|
||||||
|
|
||||||
|
#ifdef MRB_GC_STATS
|
||||||
|
uint32_t gc_total_count; /* total GC invocations */
|
||||||
|
uint32_t minor_gc_count; /* minor GC count */
|
||||||
|
uint32_t major_gc_count; /* major GC count */
|
||||||
|
#endif
|
||||||
} mrb_gc;
|
} mrb_gc;
|
||||||
|
|
||||||
MRB_API mrb_bool mrb_object_dead_p(struct mrb_state *mrb, struct RBasic *object);
|
MRB_API mrb_bool mrb_object_dead_p(mrb_state *mrb, struct RBasic *object);
|
||||||
MRB_API int mrb_gc_add_region(struct mrb_state *mrb, void *start, size_t size);
|
MRB_API int mrb_gc_add_region(mrb_state *mrb, void *start, size_t size);
|
||||||
|
|
||||||
#define MRB_GC_RED 7
|
#define MRB_GC_RED 7
|
||||||
|
|
||||||
|
|||||||
@@ -279,4 +279,7 @@ mrb_value mrb_bint_abs(mrb_state *mrb, mrb_value x);
|
|||||||
void mrb_task_mark_all(mrb_state *mrb);
|
void mrb_task_mark_all(mrb_state *mrb);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Internal object allocation without type validation (gc.c) */
|
||||||
|
struct RBasic* mrb_obj_alloc_core(mrb_state*, enum mrb_vtype, struct RClass*);
|
||||||
|
|
||||||
#endif /* MRUBY_INTERNAL_H */
|
#endif /* MRUBY_INTERNAL_H */
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ enum irep_pool_type {
|
|||||||
IREP_TT_SSTR = 2, /* string (static) */
|
IREP_TT_SSTR = 2, /* string (static) */
|
||||||
IREP_TT_INT32 = 1, /* 32-bit integer */
|
IREP_TT_INT32 = 1, /* 32-bit integer */
|
||||||
IREP_TT_INT64 = 3, /* 64-bit integer */
|
IREP_TT_INT64 = 3, /* 64-bit integer */
|
||||||
IREP_TT_BIGINT = 7, /* big integer (not yet supported) */
|
IREP_TT_BIGINT = 7, /* big integer */
|
||||||
IREP_TT_FLOAT = 5, /* float (double/float) */
|
IREP_TT_FLOAT = 5, /* float (double/float) */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,60 @@ struct RProc {
|
|||||||
#define MRB_PROC_ALIAS 8192
|
#define MRB_PROC_ALIAS 8192
|
||||||
#define MRB_PROC_ALIAS_P(p) (((p)->flags & MRB_PROC_ALIAS) != 0)
|
#define MRB_PROC_ALIAS_P(p) (((p)->flags & MRB_PROC_ALIAS) != 0)
|
||||||
|
|
||||||
|
/* Compressed aspec for cfunc procs (13 bits in RProc.flags).
|
||||||
|
* Uses free bits 0-6 and 14-19 to store a compressed argument spec.
|
||||||
|
* Layout: block(0) kdict(1) key(2-3) post(4-5) rest(6) opt(14-16) req(17-19)
|
||||||
|
* Field widths are smaller than the full 24-bit aspec: req/opt max 7, post/key max 3.
|
||||||
|
* Values exceeding the compressed range are clamped and rest is forced to 1. */
|
||||||
|
#define MRB_PROC_CASPEC_MASK 0xfc07fu /* bits 0-6 and 14-19 */
|
||||||
|
|
||||||
|
static inline uint32_t
|
||||||
|
mrb_proc_compress_aspec(mrb_aspec aspec)
|
||||||
|
{
|
||||||
|
uint32_t req = MRB_ASPEC_REQ(aspec);
|
||||||
|
uint32_t opt = MRB_ASPEC_OPT(aspec);
|
||||||
|
uint32_t rest = MRB_ASPEC_REST(aspec);
|
||||||
|
uint32_t post = MRB_ASPEC_POST(aspec);
|
||||||
|
uint32_t key = MRB_ASPEC_KEY(aspec);
|
||||||
|
uint32_t kdict = MRB_ASPEC_KDICT(aspec);
|
||||||
|
uint32_t block = MRB_ASPEC_BLOCK(aspec);
|
||||||
|
|
||||||
|
if (req > 7 || opt > 7 || post > 3 || key > 3) {
|
||||||
|
if (req > 7) req = 7;
|
||||||
|
if (opt > 7) opt = 7;
|
||||||
|
if (post > 3) post = 3;
|
||||||
|
if (key > 3) key = 3;
|
||||||
|
rest = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return block | (kdict << 1) | (key << 2) | (post << 4) | (rest << 6)
|
||||||
|
| (opt << 14) | (req << 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline mrb_aspec
|
||||||
|
mrb_proc_decompress_caspec(uint32_t flags)
|
||||||
|
{
|
||||||
|
return (((flags >> 17) & 0x7) << 18) /* req */
|
||||||
|
| (((flags >> 14) & 0x7) << 13) /* opt */
|
||||||
|
| (((flags >> 6) & 0x1) << 12) /* rest */
|
||||||
|
| (((flags >> 4) & 0x3) << 7) /* post */
|
||||||
|
| (((flags >> 2) & 0x3) << 2) /* key */
|
||||||
|
| (((flags >> 1) & 0x1) << 1) /* kdict */
|
||||||
|
| (flags & 0x1); /* block */
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
mrb_proc_set_cfunc_aspec(struct RProc *p, mrb_aspec aspec)
|
||||||
|
{
|
||||||
|
p->flags &= ~(MRB_PROC_NOARG | MRB_PROC_CASPEC_MASK);
|
||||||
|
if (aspec == 0) {
|
||||||
|
p->flags |= MRB_PROC_NOARG;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
p->flags |= mrb_proc_compress_aspec(aspec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#define mrb_proc_ptr(v) ((struct RProc*)(mrb_ptr(v)))
|
#define mrb_proc_ptr(v) ((struct RProc*)(mrb_ptr(v)))
|
||||||
|
|
||||||
struct RProc *mrb_proc_new(mrb_state*, const mrb_irep*);
|
struct RProc *mrb_proc_new(mrb_state*, const mrb_irep*);
|
||||||
|
|||||||
@@ -342,6 +342,16 @@ MRB_API const char *mrb_string_value_cstr(mrb_state *mrb, mrb_value *str);
|
|||||||
*/
|
*/
|
||||||
MRB_API mrb_value mrb_str_dup(mrb_state *mrb, mrb_value str);
|
MRB_API mrb_value mrb_str_dup(mrb_state *mrb, mrb_value str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a frozen string object.
|
||||||
|
* The string will be duplicated and frozen if it is not already frozen.
|
||||||
|
*
|
||||||
|
* @param mrb The current mruby state.
|
||||||
|
* @param str An original Ruby string.
|
||||||
|
* @return [mrb_value] Ruby frozen string.
|
||||||
|
*/
|
||||||
|
MRB_API mrb_value mrb_str_dup_frozen(mrb_state *mrb, mrb_value str);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a symbol from a passed in Ruby string.
|
* Returns a symbol from a passed in Ruby string.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ typedef uint8_t mrb_bool;
|
|||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct mrb_state;
|
|
||||||
|
|
||||||
#if defined _MSC_VER && _MSC_VER < 1800
|
#if defined _MSC_VER && _MSC_VER < 1800
|
||||||
# define PRIo64 "llo"
|
# define PRIo64 "llo"
|
||||||
# define PRId64 "lld"
|
# define PRId64 "lld"
|
||||||
@@ -330,7 +328,7 @@ struct RCptr {
|
|||||||
*/
|
*/
|
||||||
#ifndef MRB_NO_FLOAT
|
#ifndef MRB_NO_FLOAT
|
||||||
MRB_INLINE mrb_value
|
MRB_INLINE mrb_value
|
||||||
mrb_float_value(struct mrb_state *mrb, mrb_float f)
|
mrb_float_value(mrb_state *mrb, mrb_float f)
|
||||||
{
|
{
|
||||||
mrb_value v;
|
mrb_value v;
|
||||||
(void) mrb;
|
(void) mrb;
|
||||||
@@ -340,7 +338,7 @@ mrb_float_value(struct mrb_state *mrb, mrb_float f)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
MRB_INLINE mrb_value
|
MRB_INLINE mrb_value
|
||||||
mrb_cptr_value(struct mrb_state *mrb, void *p)
|
mrb_cptr_value(mrb_state *mrb, void *p)
|
||||||
{
|
{
|
||||||
mrb_value v;
|
mrb_value v;
|
||||||
(void) mrb;
|
(void) mrb;
|
||||||
@@ -352,7 +350,7 @@ mrb_cptr_value(struct mrb_state *mrb, void *p)
|
|||||||
* Returns an integer in Ruby.
|
* Returns an integer in Ruby.
|
||||||
*/
|
*/
|
||||||
MRB_INLINE mrb_value
|
MRB_INLINE mrb_value
|
||||||
mrb_int_value(struct mrb_state *mrb, mrb_int i)
|
mrb_int_value(mrb_state *mrb, mrb_int i)
|
||||||
{
|
{
|
||||||
mrb_value v;
|
mrb_value v;
|
||||||
SET_INT_VALUE(mrb, v, i);
|
SET_INT_VALUE(mrb, v, i);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ module MRuby
|
|||||||
allocf.c
|
allocf.c
|
||||||
readnum.c
|
readnum.c
|
||||||
readint.c
|
readint.c
|
||||||
readfloat.c
|
fp_uscale.c
|
||||||
state.c
|
state.c
|
||||||
symbol.c
|
symbol.c
|
||||||
class.c
|
class.c
|
||||||
@@ -66,7 +66,6 @@ module MRuby
|
|||||||
cdump.c
|
cdump.c
|
||||||
codedump.c
|
codedump.c
|
||||||
print.c
|
print.c
|
||||||
fmt_fp.c
|
|
||||||
debug.c
|
debug.c
|
||||||
etc.c
|
etc.c
|
||||||
version.c
|
version.c
|
||||||
|
|||||||
+26
-1
@@ -81,7 +81,7 @@ module MRuby
|
|||||||
include LoadGems
|
include LoadGems
|
||||||
attr_accessor :name, :bins, :exts, :file_separator, :build_dir, :gem_clone_dir, :defines, :libdir_name
|
attr_accessor :name, :bins, :exts, :file_separator, :build_dir, :gem_clone_dir, :defines, :libdir_name
|
||||||
attr_reader :products, :libmruby_core_objs, :libmruby_objs, :gems, :toolchains, :presym, :mrbc_build, :gem_dir_to_repo_url
|
attr_reader :products, :libmruby_core_objs, :libmruby_objs, :gems, :toolchains, :presym, :mrbc_build, :gem_dir_to_repo_url
|
||||||
attr_reader :install_excludes
|
attr_reader :install_excludes, :port_names
|
||||||
|
|
||||||
alias libmruby libmruby_objs
|
alias libmruby libmruby_objs
|
||||||
|
|
||||||
@@ -138,6 +138,7 @@ module MRuby
|
|||||||
@mrbcfile_external = false
|
@mrbcfile_external = false
|
||||||
@internal = internal
|
@internal = internal
|
||||||
@toolchains = []
|
@toolchains = []
|
||||||
|
@port_names = nil
|
||||||
@gem_dir_to_repo_url = {}
|
@gem_dir_to_repo_url = {}
|
||||||
|
|
||||||
# Add lambda instead of string because libdir_name or lib may be changed by user configuration
|
# Add lambda instead of string because libdir_name or lib may be changed by user configuration
|
||||||
@@ -183,6 +184,30 @@ module MRuby
|
|||||||
@enable_debug = true
|
@enable_debug = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Set target port names for this build.
|
||||||
|
# Each gem compiles the first matching ports/<name>/ directory;
|
||||||
|
# later names in the list act as fallbacks for gems that don't
|
||||||
|
# ship a port for the earlier names.
|
||||||
|
# conf.ports :esp32
|
||||||
|
# conf.ports :rp2040, :posix # use rp2040 if available, else posix
|
||||||
|
def ports(*names)
|
||||||
|
@port_names = names.map { |n| n.to_s }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the effective port names for this build.
|
||||||
|
# If not explicitly set, auto-detects :posix or :win for host builds.
|
||||||
|
def effective_ports
|
||||||
|
return @port_names if @port_names
|
||||||
|
if kind_of?(MRuby::CrossBuild)
|
||||||
|
[]
|
||||||
|
elsif ENV['OS'] == 'Windows_NT' ||
|
||||||
|
('A'..'Z').any? { |v| Dir.exist?("#{v}:") }
|
||||||
|
['win']
|
||||||
|
else
|
||||||
|
['posix']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def disable_lock
|
def disable_lock
|
||||||
@enable_lock = false
|
@enable_lock = false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -343,16 +343,21 @@ module MRuby
|
|||||||
opt = @compile_options % {funcname: funcname}
|
opt = @compile_options % {funcname: funcname}
|
||||||
opt << " -S" if cdump
|
opt << " -S" if cdump
|
||||||
opt << " -s" if static
|
opt << " -s" if static
|
||||||
|
# Have mrbc write to a private tempfile (-o) instead of stdout (-o-)
|
||||||
|
# to avoid pipe-inheritance races with parallel rake on Windows MinGW,
|
||||||
|
# where unrelated _pp build-progress lines from sibling workers can
|
||||||
|
# leak into the captured stdout and corrupt the generated C file.
|
||||||
|
tmpout = "#{out.path}.#{funcname}.mrbcout"
|
||||||
|
opt = opt.sub(/\s-o-(?=\s|\z)/, %Q[ -o "#{filename tmpout}"])
|
||||||
cmd = %["#{filename @command}" #{opt} #{filename(infiles).map{|f| %["#{f}"]}.join(' ')}]
|
cmd = %["#{filename @command}" #{opt} #{filename(infiles).map{|f| %["#{f}"]}.join(' ')}]
|
||||||
puts cmd if Rake.verbose
|
puts cmd if Rake.verbose
|
||||||
IO.popen(cmd, 'r') do |io|
|
unless system(cmd)
|
||||||
out.puts io.read
|
rm_f tmpout
|
||||||
end
|
|
||||||
# if mrbc execution fail, drop the file
|
|
||||||
unless $?.success?
|
|
||||||
rm_f out.path
|
rm_f out.path
|
||||||
fail "Command failed with status (#{$?.exitstatus}): [#{cmd[0,42]}...]"
|
fail "Command failed with status (#{$?.exitstatus}): [#{cmd[0,42]}...]"
|
||||||
end
|
end
|
||||||
|
out.write File.binread(tmpout)
|
||||||
|
rm_f tmpout
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ module MRuby
|
|||||||
alias :author= :authors=
|
alias :author= :authors=
|
||||||
|
|
||||||
attr_accessor :rbfiles, :objs
|
attr_accessor :rbfiles, :objs
|
||||||
|
attr_reader :port_objs
|
||||||
attr_writer :test_objs, :test_rbfiles
|
attr_writer :test_objs, :test_rbfiles
|
||||||
attr_accessor :test_args, :test_preload
|
attr_accessor :test_args, :test_preload
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ module MRuby
|
|||||||
def initialize(name, &block)
|
def initialize(name, &block)
|
||||||
@name = name
|
@name = name
|
||||||
@initializer = block
|
@initializer = block
|
||||||
|
@post_user_config = nil
|
||||||
@version = "0.0.0"
|
@version = "0.0.0"
|
||||||
@dependencies = []
|
@dependencies = []
|
||||||
@conflicts = []
|
@conflicts = []
|
||||||
@@ -59,6 +61,22 @@ module MRuby
|
|||||||
@rbfiles = Dir.glob("#{@dir}/mrblib/**/*.rb").sort
|
@rbfiles = Dir.glob("#{@dir}/mrblib/**/*.rb").sort
|
||||||
@objs = srcs_to_objs("src")
|
@objs = srcs_to_objs("src")
|
||||||
|
|
||||||
|
# Add platform-specific sources from the first matching
|
||||||
|
# ports/<name>/ directory. effective_ports is a fallback
|
||||||
|
# chain: later names act as defaults for gems that don't ship
|
||||||
|
# a port for the earlier names. These objs are tracked
|
||||||
|
# separately so List#resolve_external_hal! can drop them when
|
||||||
|
# an external HAL provider (gem named hal-<short>-*) is loaded.
|
||||||
|
@port_objs = []
|
||||||
|
build.effective_ports.each do |port|
|
||||||
|
port_dir = "#{@dir}/ports/#{port}"
|
||||||
|
if File.directory?(port_dir)
|
||||||
|
@port_objs = srcs_to_objs("ports/#{port}")
|
||||||
|
@objs += @port_objs
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@test_preload = nil # 'test/assert.rb'
|
@test_preload = nil # 'test/assert.rb'
|
||||||
@test_args = {}
|
@test_args = {}
|
||||||
@skip_test = false
|
@skip_test = false
|
||||||
@@ -85,6 +103,7 @@ module MRuby
|
|||||||
build.libmruby_objs << @objs
|
build.libmruby_objs << @objs
|
||||||
|
|
||||||
instance_eval(&@build_config_initializer) if @build_config_initializer
|
instance_eval(&@build_config_initializer) if @build_config_initializer
|
||||||
|
instance_eval(&@post_user_config) if @post_user_config
|
||||||
|
|
||||||
repo_url = build.gem_dir_to_repo_url[dir]
|
repo_url = build.gem_dir_to_repo_url[dir]
|
||||||
build.locks[repo_url]['version'] = version if repo_url
|
build.locks[repo_url]['version'] = version if repo_url
|
||||||
@@ -194,6 +213,19 @@ module MRuby
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Register a block that runs after the user's `build.gem` block has
|
||||||
|
# been processed. Intended for gem authors to fill in defaults that
|
||||||
|
# depend on user-supplied configuration (e.g. auto-detect a library
|
||||||
|
# only if the user didn't specify which to use).
|
||||||
|
#
|
||||||
|
# Initialization order:
|
||||||
|
# 1. block in `MRuby::Gem::Specification.new` (gem author)
|
||||||
|
# 2. block in `build.gem` (user's build_config)
|
||||||
|
# 3. block in `post_user_config` (gem author, this hook)
|
||||||
|
def post_user_config(&block)
|
||||||
|
@post_user_config = block
|
||||||
|
end
|
||||||
|
|
||||||
def build_settings(&blk)
|
def build_settings(&blk)
|
||||||
@build_settings = blk
|
@build_settings = blk
|
||||||
end
|
end
|
||||||
@@ -430,6 +462,28 @@ module MRuby
|
|||||||
self.each(&:setup)
|
self.each(&:setup)
|
||||||
gemset = self.setup_dependencies(build).keys.sort
|
gemset = self.setup_dependencies(build).keys.sort
|
||||||
end until gemset == gemset_prev
|
end until gemset == gemset_prev
|
||||||
|
resolve_external_hal!
|
||||||
|
end
|
||||||
|
|
||||||
|
# A gem named `hal-<short>-<conf>` is treated as the external
|
||||||
|
# HAL provider for the gem whose name's last `-`-separated
|
||||||
|
# segment is <short> (e.g., hal-task-glib provides the HAL for
|
||||||
|
# mruby-task). The target gem's ports/* sources are dropped
|
||||||
|
# from its object list -- the matching gem supplies the
|
||||||
|
# implementation. Two or more matches is a build error.
|
||||||
|
def resolve_external_hal!
|
||||||
|
each do |target|
|
||||||
|
next if target.port_objs.nil? || target.port_objs.empty?
|
||||||
|
short = target.name.split('-').last
|
||||||
|
pattern = /\Ahal-#{Regexp.escape(short)}-.+\z/
|
||||||
|
overriders = select { |g| g != target && g.name =~ pattern }
|
||||||
|
next if overriders.empty?
|
||||||
|
if overriders.size > 1
|
||||||
|
fail "Multiple HAL providers for '#{target.name}': " +
|
||||||
|
overriders.map(&:name).join(", ")
|
||||||
|
end
|
||||||
|
target.objs.reject! { |o| target.port_objs.include?(o) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_build
|
def setup_build
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
MRuby::Gem::Specification.new('hal-posix-dir') do |spec|
|
|
||||||
spec.license = 'MIT'
|
|
||||||
spec.authors = 'mruby developers'
|
|
||||||
spec.summary = 'POSIX HAL for mruby-dir (Linux, macOS, BSD, Unix)'
|
|
||||||
|
|
||||||
spec.add_dependency 'mruby-dir', core: 'mruby-dir'
|
|
||||||
end
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
MRuby::Gem::Specification.new('hal-posix-io') do |spec|
|
|
||||||
spec.license = 'MIT'
|
|
||||||
spec.author = 'mruby developers'
|
|
||||||
spec.summary = 'POSIX HAL for mruby-io (Linux, macOS, BSD, Unix)'
|
|
||||||
|
|
||||||
# HAL gem depends on feature gem - brings in mruby-io automatically
|
|
||||||
spec.add_dependency 'mruby-io', core: 'mruby-io'
|
|
||||||
end
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
MRuby::Gem::Specification.new('hal-posix-socket') do |spec|
|
|
||||||
spec.license = 'MIT'
|
|
||||||
spec.author = 'mruby developers'
|
|
||||||
spec.summary = 'POSIX HAL for mruby-socket (Linux, macOS, BSD, Unix)'
|
|
||||||
|
|
||||||
# HAL gem depends on feature gem - brings in mruby-socket automatically
|
|
||||||
spec.add_dependency 'mruby-socket', core: 'mruby-socket'
|
|
||||||
end
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
# hal-posix-task
|
|
||||||
|
|
||||||
POSIX Hardware Abstraction Layer (HAL) implementation for mruby-task.
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
Provides timer and interrupt support for the mruby-task cooperative scheduler on POSIX-compliant platforms. Uses `SIGALRM` and `setitimer()` for periodic timer ticks, and `sigprocmask()` for interrupt protection.
|
|
||||||
|
|
||||||
## Supported Platforms
|
|
||||||
|
|
||||||
- Linux
|
|
||||||
- macOS
|
|
||||||
- BSD (FreeBSD, OpenBSD, NetBSD)
|
|
||||||
- Other POSIX-compliant Unix systems
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- POSIX-compliant operating system
|
|
||||||
- Signal support (`SIGALRM`, `sigaction`, `sigprocmask`)
|
|
||||||
- Timer support (`setitimer`, `ITIMER_REAL`)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Explicit HAL Selection (Recommended)
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
MRuby::Build.new do |conf|
|
|
||||||
# ... other configuration ...
|
|
||||||
|
|
||||||
# Specify POSIX HAL - automatically brings in mruby-task
|
|
||||||
conf.gem core: 'hal-posix-task'
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Auto-detection (Development)
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
MRuby::Build.new do |conf|
|
|
||||||
# ... other configuration ...
|
|
||||||
|
|
||||||
# Auto-detects and selects hal-posix-task on POSIX platforms
|
|
||||||
conf.gem core: 'mruby-task'
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Details
|
|
||||||
|
|
||||||
### Timer Mechanism
|
|
||||||
|
|
||||||
- Uses `setitimer(ITIMER_REAL, ...)` to generate periodic `SIGALRM` signals
|
|
||||||
- Timer interval configured by `MRB_TICK_UNIT` (default: 4ms)
|
|
||||||
- Signal handler calls `mrb_tick()` for all registered VM instances
|
|
||||||
|
|
||||||
### Interrupt Protection
|
|
||||||
|
|
||||||
- Critical sections protected using `sigprocmask()` to block `SIGALRM`
|
|
||||||
- Prevents race conditions during task queue modifications
|
|
||||||
- Supports nested critical sections through signal masking
|
|
||||||
|
|
||||||
### Multi-VM Support
|
|
||||||
|
|
||||||
- Supports up to `MRB_TASK_MAX_VMS` concurrent mruby VM instances (default: 8)
|
|
||||||
- Single shared timer ticks all registered VMs
|
|
||||||
- Per-VM task counters optimize timer usage (timer disabled when idle)
|
|
||||||
|
|
||||||
### Timer Optimization
|
|
||||||
|
|
||||||
The implementation dynamically enables/disables the timer based on task state:
|
|
||||||
|
|
||||||
- **Timer enabled** when: Multiple ready tasks OR any waiting tasks exist
|
|
||||||
- **Timer disabled** when: Single task or all tasks dormant/suspended
|
|
||||||
- Reduces CPU usage and power consumption when scheduler is idle
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Override these macros in your build config if needed:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
conf.gem core: 'hal-posix-task' do |spec|
|
|
||||||
# Custom tick interval (10ms instead of default 4ms)
|
|
||||||
spec.build.defines << 'MRB_TICK_UNIT=10'
|
|
||||||
|
|
||||||
# Custom timeslice (5 ticks instead of default 3)
|
|
||||||
spec.build.defines << 'MRB_TIMESLICE_TICK_COUNT=5'
|
|
||||||
|
|
||||||
# More concurrent VMs (16 instead of default 8)
|
|
||||||
spec.build.defines << 'MRB_TASK_MAX_VMS=16'
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Known Limitations
|
|
||||||
|
|
||||||
- `SIGALRM` conflicts with other code using the same signal
|
|
||||||
- Timer resolution limited by platform (typically 1-10ms)
|
|
||||||
- Signal delivery may be delayed under heavy system load
|
|
||||||
- Not suitable for hard real-time requirements
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `mruby-task` - Core task scheduler
|
|
||||||
- `hal-win-task` - Windows HAL implementation
|
|
||||||
- Task scheduler documentation: `mrbgems/mruby-task/README.md`
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
MRuby::Gem::Specification.new('hal-posix-task') do |spec|
|
|
||||||
spec.license = 'MIT'
|
|
||||||
spec.authors = 'mruby developers'
|
|
||||||
spec.summary = 'POSIX HAL for mruby-task (Linux, macOS, BSD, Unix)'
|
|
||||||
|
|
||||||
# HAL gem depends on feature gem - brings in mruby-task automatically
|
|
||||||
spec.add_dependency 'mruby-task', core: 'mruby-task'
|
|
||||||
end
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
MRuby::Gem::Specification.new('hal-win-dir') do |spec|
|
|
||||||
spec.license = 'MIT and MIT-like license'
|
|
||||||
spec.authors = ['mruby developers', 'Kevlin Henney']
|
|
||||||
spec.summary = 'Windows HAL for mruby-dir'
|
|
||||||
|
|
||||||
spec.add_dependency 'mruby-dir', core: 'mruby-dir'
|
|
||||||
end
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
MRuby::Gem::Specification.new('hal-win-io') do |spec|
|
|
||||||
spec.license = 'MIT'
|
|
||||||
spec.author = 'mruby developers'
|
|
||||||
spec.summary = 'Windows HAL for mruby-io (Windows, MinGW)'
|
|
||||||
|
|
||||||
# HAL gem depends on feature gem - brings in mruby-io automatically
|
|
||||||
spec.add_dependency 'mruby-io', core: 'mruby-io'
|
|
||||||
|
|
||||||
# Link Windows socket library
|
|
||||||
spec.linker.libraries << "ws2_32"
|
|
||||||
end
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
MRuby::Gem::Specification.new('hal-win-socket') do |spec|
|
|
||||||
spec.license = 'MIT'
|
|
||||||
spec.author = 'mruby developers'
|
|
||||||
spec.summary = 'Windows HAL for mruby-socket (Windows, MinGW)'
|
|
||||||
|
|
||||||
# HAL gem depends on feature gem - brings in mruby-socket automatically
|
|
||||||
spec.add_dependency 'mruby-socket', core: 'mruby-socket'
|
|
||||||
|
|
||||||
# Link Windows socket libraries
|
|
||||||
spec.linker.libraries << "wsock32"
|
|
||||||
spec.linker.libraries << "ws2_32"
|
|
||||||
end
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
/*
|
|
||||||
** socket_hal.c - Windows HAL implementation for mruby-socket
|
|
||||||
**
|
|
||||||
** See Copyright Notice in mruby.h
|
|
||||||
**
|
|
||||||
** Windows implementation for socket operations using Winsock APIs.
|
|
||||||
** Supported platforms: Windows, MinGW
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _WIN32_WINNT
|
|
||||||
#define _WIN32_WINNT 0x0501 // need Windows XP or later
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <mruby.h>
|
|
||||||
#include <mruby/string.h>
|
|
||||||
#include <mruby/class.h>
|
|
||||||
#include <mruby/error.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 */
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
# hal-win-task
|
|
||||||
|
|
||||||
Windows Hardware Abstraction Layer (HAL) implementation for mruby-task.
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
Provides timer and interrupt support for the mruby-task cooperative scheduler on Windows platforms. Uses multimedia timer (`timeSetEvent`/`timeKillEvent`) for periodic timer ticks, and `CRITICAL_SECTION` for interrupt protection.
|
|
||||||
|
|
||||||
## Supported Platforms
|
|
||||||
|
|
||||||
- Windows 7 and later
|
|
||||||
- Windows Server 2008 R2 and later
|
|
||||||
- All versions with multimedia timer support
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- Windows operating system
|
|
||||||
- Multimedia timer API (`winmm.lib`)
|
|
||||||
- Visual C++, MinGW, or compatible compiler
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Explicit HAL Selection (Recommended)
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
MRuby::Build.new do |conf|
|
|
||||||
# ... other configuration ...
|
|
||||||
|
|
||||||
# Specify Windows HAL - automatically brings in mruby-task
|
|
||||||
conf.gem core: 'hal-win-task'
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Auto-detection (Development)
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
MRuby::Build.new do |conf|
|
|
||||||
# ... other configuration ...
|
|
||||||
|
|
||||||
# Auto-detects and selects hal-win-task on Windows platforms
|
|
||||||
conf.gem core: 'mruby-task'
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Details
|
|
||||||
|
|
||||||
### Timer Mechanism
|
|
||||||
|
|
||||||
- Uses Windows multimedia timer (`timeSetEvent`) for periodic callbacks
|
|
||||||
- Timer interval configured by `MRB_TICK_UNIT` (default: 4ms)
|
|
||||||
- `TIME_KILL_SYNCHRONOUS` flag ensures clean timer shutdown
|
|
||||||
- Requests 1ms timer resolution via `timeBeginPeriod(1)`
|
|
||||||
|
|
||||||
### Interrupt Protection
|
|
||||||
|
|
||||||
- Critical sections protected using `CRITICAL_SECTION` objects
|
|
||||||
- Prevents race conditions during task queue modifications
|
|
||||||
- `EnterCriticalSection`/`LeaveCriticalSection` used for mutual exclusion
|
|
||||||
- Supports nested critical sections (automatic lock counting)
|
|
||||||
|
|
||||||
### Multi-VM Support
|
|
||||||
|
|
||||||
- Supports up to `MRB_TASK_MAX_VMS` concurrent mruby VM instances (default: 8)
|
|
||||||
- Single shared timer ticks all registered VMs
|
|
||||||
- Per-VM task counters optimize timer usage (timer disabled when idle)
|
|
||||||
- Interlocked operations (`InterlockedIncrement`/`InterlockedDecrement`) for thread safety
|
|
||||||
|
|
||||||
### Timer Optimization
|
|
||||||
|
|
||||||
The implementation dynamically enables/disables the timer based on task state:
|
|
||||||
|
|
||||||
- **Timer enabled** when: Multiple ready tasks OR any waiting tasks exist
|
|
||||||
- **Timer disabled** when: Single task or all tasks dormant/suspended
|
|
||||||
- Reduces CPU usage and power consumption when scheduler is idle
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Override these macros in your build config if needed:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
conf.gem core: 'hal-win-task' do |spec|
|
|
||||||
# Custom tick interval (10ms instead of default 4ms)
|
|
||||||
spec.build.defines << 'MRB_TICK_UNIT=10'
|
|
||||||
|
|
||||||
# Custom timeslice (5 ticks instead of default 3)
|
|
||||||
spec.build.defines << 'MRB_TIMESLICE_TICK_COUNT=5'
|
|
||||||
|
|
||||||
# More concurrent VMs (16 instead of default 8)
|
|
||||||
spec.build.defines << 'MRB_TASK_MAX_VMS=16'
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Known Limitations
|
|
||||||
|
|
||||||
- Timer resolution typically limited to 1-2ms even with `timeBeginPeriod(1)`
|
|
||||||
- Multimedia timers consume system resources (kernel timer objects)
|
|
||||||
- Timer callbacks execute in separate thread context (handled internally)
|
|
||||||
- Not suitable for hard real-time requirements
|
|
||||||
- May interfere with other multimedia applications requesting different timer resolutions
|
|
||||||
|
|
||||||
## See Also
|
|
||||||
|
|
||||||
- `mruby-task` - Core task scheduler
|
|
||||||
- `hal-posix-task` - POSIX/Unix HAL implementation
|
|
||||||
- Task scheduler documentation: `mrbgems/mruby-task/README.md`
|
|
||||||
|
|
||||||
## Build Notes
|
|
||||||
|
|
||||||
The `winmm` library is automatically linked by the gem specification. No additional linker configuration is needed.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
MRuby::Gem::Specification.new('hal-win-task') do |spec|
|
|
||||||
spec.license = 'MIT'
|
|
||||||
spec.authors = 'mruby developers'
|
|
||||||
spec.summary = 'Windows HAL for mruby-task'
|
|
||||||
|
|
||||||
# HAL gem depends on feature gem - brings in mruby-task automatically
|
|
||||||
spec.add_dependency 'mruby-task', core: 'mruby-task'
|
|
||||||
|
|
||||||
# Windows multimedia timer library
|
|
||||||
spec.linker.libraries << 'winmm'
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# hw-adc - ADC peripheral interface for mruby
|
||||||
|
|
||||||
|
This gem provides the `ADC` class for reading analog input from
|
||||||
|
mruby. It is designed for embedded platforms such as ESP32 and
|
||||||
|
RP2040.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Platform-specific HAL implementations are in `ports/` directories:
|
||||||
|
|
||||||
|
- `ports/esp32/` - ESP32 using ESP-IDF ADC oneshot driver
|
||||||
|
- `ports/rp2040/` - RP2040 using Pico SDK ADC hardware
|
||||||
|
|
||||||
|
## Build Configuration
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
MRuby::CrossBuild.new('esp32') do |conf|
|
||||||
|
conf.ports :esp32
|
||||||
|
conf.gem core: 'hw-adc'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ruby API
|
||||||
|
|
||||||
|
### ADC.new
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
adc = ADC.new(pin)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `pin` - ADC-capable GPIO pin number (Integer)
|
||||||
|
- Raises `ArgumentError` if the pin is not valid for ADC
|
||||||
|
|
||||||
|
### ADC#read / ADC#read_voltage
|
||||||
|
|
||||||
|
Read the analog value as voltage (Float).
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
voltage = adc.read # => 1.65
|
||||||
|
voltage = adc.read_voltage # same
|
||||||
|
```
|
||||||
|
|
||||||
|
### ADC#read_raw
|
||||||
|
|
||||||
|
Read the raw ADC value (Integer, typically 0-4095 for 12-bit).
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
raw = adc.read_raw # => 2048
|
||||||
|
```
|
||||||
|
|
||||||
|
### ADC#input
|
||||||
|
|
||||||
|
Returns the ADC input channel number assigned during initialization.
|
||||||
|
|
||||||
|
## HAL Interface
|
||||||
|
|
||||||
|
To add support for a new platform, create a `ports/<name>/`
|
||||||
|
directory and implement the following C functions declared in
|
||||||
|
`<mruby/adc.h>`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int mrb_adc_init(uint8_t pin);
|
||||||
|
uint32_t mrb_adc_read_raw(uint8_t input);
|
||||||
|
float mrb_adc_read_voltage(uint8_t input);
|
||||||
|
```
|
||||||
|
|
||||||
|
- `mrb_adc_init` returns the input channel number (>= 0) on
|
||||||
|
success, negative on error
|
||||||
|
- `input` parameter for read functions is the value returned by
|
||||||
|
`mrb_adc_init`
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef MRUBY_ADC_H
|
||||||
|
#define MRUBY_ADC_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* HAL functions - implemented in ports/<platform>/adc.c */
|
||||||
|
int mrb_adc_init(uint8_t pin);
|
||||||
|
uint32_t mrb_adc_read_raw(uint8_t input);
|
||||||
|
float mrb_adc_read_voltage(uint8_t input);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MRUBY_ADC_H */
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
MRuby::Gem::Specification.new('hw-adc') do |spec|
|
||||||
|
spec.license = 'MIT'
|
||||||
|
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
|
||||||
|
spec.summary = 'ADC peripheral interface'
|
||||||
|
end
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
class ADC
|
||||||
|
def initialize(pin)
|
||||||
|
@input = __init(pin)
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :input
|
||||||
|
end
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include "esp_adc/adc_oneshot.h"
|
||||||
|
#include <mruby/adc.h>
|
||||||
|
|
||||||
|
#define VOLTAGE_MAX 3.3f
|
||||||
|
#define RESOLUTION 4095
|
||||||
|
#define UNIT_NUM 2
|
||||||
|
|
||||||
|
static adc_oneshot_unit_handle_t adc_handles[UNIT_NUM];
|
||||||
|
static bool adc_initialized;
|
||||||
|
|
||||||
|
static adc_unit_t
|
||||||
|
pin_to_unit(uint8_t pin)
|
||||||
|
{
|
||||||
|
switch (pin) {
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
case 0: case 1: case 2: case 3: case 4:
|
||||||
|
return ADC_UNIT_1;
|
||||||
|
case 5:
|
||||||
|
return ADC_UNIT_2;
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32
|
||||||
|
case 32: case 33: case 34: case 35: case 36: case 39:
|
||||||
|
return ADC_UNIT_1;
|
||||||
|
case 0: case 2: case 4: case 12: case 13: case 14: case 15:
|
||||||
|
case 25: case 26: case 27:
|
||||||
|
return ADC_UNIT_2;
|
||||||
|
#elif (CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2)
|
||||||
|
case 1: case 2: case 3: case 4: case 5:
|
||||||
|
case 6: case 7: case 8: case 9: case 10:
|
||||||
|
return ADC_UNIT_1;
|
||||||
|
case 11: case 12: case 13: case 14: case 15:
|
||||||
|
case 16: case 17: case 18: case 19: case 20:
|
||||||
|
return ADC_UNIT_2;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static adc_channel_t
|
||||||
|
pin_to_channel(uint8_t pin)
|
||||||
|
{
|
||||||
|
switch (pin) {
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
case 0: return ADC_CHANNEL_0;
|
||||||
|
case 1: return ADC_CHANNEL_1;
|
||||||
|
case 2: return ADC_CHANNEL_2;
|
||||||
|
case 3: return ADC_CHANNEL_3;
|
||||||
|
case 4: return ADC_CHANNEL_4;
|
||||||
|
case 5: return ADC_CHANNEL_0;
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32
|
||||||
|
case 36: return ADC_CHANNEL_0;
|
||||||
|
case 39: return ADC_CHANNEL_3;
|
||||||
|
case 32: return ADC_CHANNEL_4;
|
||||||
|
case 33: return ADC_CHANNEL_5;
|
||||||
|
case 34: return ADC_CHANNEL_6;
|
||||||
|
case 35: return ADC_CHANNEL_7;
|
||||||
|
case 4: return ADC_CHANNEL_0;
|
||||||
|
case 0: return ADC_CHANNEL_1;
|
||||||
|
case 2: return ADC_CHANNEL_2;
|
||||||
|
case 15: return ADC_CHANNEL_3;
|
||||||
|
case 13: return ADC_CHANNEL_4;
|
||||||
|
case 12: return ADC_CHANNEL_5;
|
||||||
|
case 14: return ADC_CHANNEL_6;
|
||||||
|
case 27: return ADC_CHANNEL_7;
|
||||||
|
case 25: return ADC_CHANNEL_8;
|
||||||
|
case 26: return ADC_CHANNEL_9;
|
||||||
|
#elif (CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32S2)
|
||||||
|
case 1: return ADC_CHANNEL_0;
|
||||||
|
case 2: return ADC_CHANNEL_1;
|
||||||
|
case 3: return ADC_CHANNEL_2;
|
||||||
|
case 4: return ADC_CHANNEL_3;
|
||||||
|
case 5: return ADC_CHANNEL_4;
|
||||||
|
case 6: return ADC_CHANNEL_5;
|
||||||
|
case 7: return ADC_CHANNEL_6;
|
||||||
|
case 8: return ADC_CHANNEL_7;
|
||||||
|
case 9: return ADC_CHANNEL_8;
|
||||||
|
case 10: return ADC_CHANNEL_9;
|
||||||
|
case 11: return ADC_CHANNEL_0;
|
||||||
|
case 12: return ADC_CHANNEL_1;
|
||||||
|
case 13: return ADC_CHANNEL_2;
|
||||||
|
case 14: return ADC_CHANNEL_3;
|
||||||
|
case 15: return ADC_CHANNEL_4;
|
||||||
|
case 16: return ADC_CHANNEL_5;
|
||||||
|
case 17: return ADC_CHANNEL_6;
|
||||||
|
case 18: return ADC_CHANNEL_7;
|
||||||
|
case 19: return ADC_CHANNEL_8;
|
||||||
|
case 20: return ADC_CHANNEL_9;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
init_units(void)
|
||||||
|
{
|
||||||
|
adc_unit_t units[] = { ADC_UNIT_1, ADC_UNIT_2 };
|
||||||
|
for (int i = 0; i < UNIT_NUM; i++) {
|
||||||
|
adc_oneshot_unit_init_cfg_t cfg = {
|
||||||
|
.unit_id = units[i],
|
||||||
|
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||||
|
};
|
||||||
|
if (adc_oneshot_new_unit(&cfg, &adc_handles[i]) != ESP_OK)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_adc_init(uint8_t pin)
|
||||||
|
{
|
||||||
|
if (!adc_initialized) {
|
||||||
|
if (init_units() != 0) return -1;
|
||||||
|
adc_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ch = pin_to_channel(pin);
|
||||||
|
if (ch < 0) return -1;
|
||||||
|
|
||||||
|
adc_unit_t unit = pin_to_unit(pin);
|
||||||
|
if (unit < 0 || unit >= UNIT_NUM) return -1;
|
||||||
|
|
||||||
|
adc_oneshot_chan_cfg_t cfg = {
|
||||||
|
.atten = ADC_ATTEN_DB_12,
|
||||||
|
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||||
|
};
|
||||||
|
if (adc_oneshot_config_channel(adc_handles[unit], ch, &cfg) != ESP_OK)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return (int)pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
mrb_adc_read_raw(uint8_t input)
|
||||||
|
{
|
||||||
|
adc_unit_t unit = pin_to_unit(input);
|
||||||
|
int ch = pin_to_channel(input);
|
||||||
|
int raw = 0;
|
||||||
|
if (unit >= 0 && unit < UNIT_NUM)
|
||||||
|
adc_oneshot_read(adc_handles[unit], ch, &raw);
|
||||||
|
return (uint32_t)raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
float
|
||||||
|
mrb_adc_read_voltage(uint8_t input)
|
||||||
|
{
|
||||||
|
return (float)mrb_adc_read_raw(input) * VOLTAGE_MAX / RESOLUTION;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include "hardware/adc.h"
|
||||||
|
#include <mruby/adc.h>
|
||||||
|
|
||||||
|
#define VOLTAGE_MAX 3.3f
|
||||||
|
#define RESOLUTION 4095
|
||||||
|
#define TEMP_INPUT 4
|
||||||
|
|
||||||
|
static bool adc_initialized;
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_adc_init(uint8_t pin)
|
||||||
|
{
|
||||||
|
if (!adc_initialized) {
|
||||||
|
adc_init();
|
||||||
|
adc_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint input;
|
||||||
|
switch (pin) {
|
||||||
|
case 26: input = 0; break;
|
||||||
|
case 27: input = 1; break;
|
||||||
|
case 28: input = 2; break;
|
||||||
|
case 29: input = 3; break;
|
||||||
|
default: return -1;
|
||||||
|
}
|
||||||
|
adc_gpio_init(pin);
|
||||||
|
return (int)input;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
mrb_adc_read_raw(uint8_t input)
|
||||||
|
{
|
||||||
|
adc_select_input(input);
|
||||||
|
return (uint32_t)adc_read();
|
||||||
|
}
|
||||||
|
|
||||||
|
float
|
||||||
|
mrb_adc_read_voltage(uint8_t input)
|
||||||
|
{
|
||||||
|
adc_select_input(input);
|
||||||
|
return (float)adc_read() * VOLTAGE_MAX / RESOLUTION;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include <mruby.h>
|
||||||
|
#include <mruby/presym.h>
|
||||||
|
#include <mruby/variable.h>
|
||||||
|
#include <mruby/adc.h>
|
||||||
|
|
||||||
|
/* ADC#__init(pin) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_adc_m_init(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int pin;
|
||||||
|
mrb_get_args(mrb, "i", &pin);
|
||||||
|
int input = mrb_adc_init((uint8_t)pin);
|
||||||
|
if (input < 0) {
|
||||||
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid ADC pin");
|
||||||
|
}
|
||||||
|
return mrb_fixnum_value(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ADC#read_raw */
|
||||||
|
static mrb_value
|
||||||
|
mrb_adc_m_read_raw(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int input = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(input)));
|
||||||
|
return mrb_fixnum_value(mrb_adc_read_raw((uint8_t)input));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ADC#read_voltage (also aliased as read) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_adc_m_read_voltage(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int input = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(input)));
|
||||||
|
return mrb_float_value(mrb, (mrb_float)mrb_adc_read_voltage((uint8_t)input));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_adc_gem_init(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(ADC), mrb->object_class);
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_adc_m_init, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(read_raw), mrb_adc_m_read_raw, MRB_ARGS_NONE());
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(read_voltage), mrb_adc_m_read_voltage, MRB_ARGS_NONE());
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_adc_m_read_voltage, MRB_ARGS_NONE());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_adc_gem_final(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
# hw-gpio - GPIO peripheral interface for mruby
|
||||||
|
|
||||||
|
This gem provides the `GPIO` class for controlling general-purpose
|
||||||
|
input/output pins from mruby.
|
||||||
|
It is designed for embedded platforms such as ESP32 and RP2040.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Platform-specific HAL implementations are in `ports/` directories:
|
||||||
|
|
||||||
|
- `ports/esp32/` - ESP32 using ESP-IDF GPIO driver
|
||||||
|
- `ports/rp2040/` - RP2040 using Pico SDK
|
||||||
|
|
||||||
|
The build system automatically compiles matching port sources based
|
||||||
|
on `conf.ports` setting.
|
||||||
|
|
||||||
|
## Build Configuration
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# For ESP32
|
||||||
|
MRuby::CrossBuild.new('esp32') do |conf|
|
||||||
|
conf.ports :esp32
|
||||||
|
conf.gem core: 'hw-gpio'
|
||||||
|
end
|
||||||
|
|
||||||
|
# For RP2040
|
||||||
|
MRuby::CrossBuild.new('rp2040') do |conf|
|
||||||
|
conf.ports :rp2040
|
||||||
|
conf.gem core: 'hw-gpio'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ruby API
|
||||||
|
|
||||||
|
### Constants (direction/mode flags)
|
||||||
|
|
||||||
|
These flags can be combined with bitwise OR.
|
||||||
|
|
||||||
|
| Flag | Value | Description |
|
||||||
|
| ------------------ | ------ | -------------------------- |
|
||||||
|
| `GPIO::IN` | `0x01` | Input mode |
|
||||||
|
| `GPIO::OUT` | `0x02` | Output mode |
|
||||||
|
| `GPIO::HIGH_Z` | `0x04` | High-impedance (tri-state) |
|
||||||
|
| `GPIO::PULL_UP` | `0x08` | Enable pull-up resistor |
|
||||||
|
| `GPIO::PULL_DOWN` | `0x10` | Enable pull-down resistor |
|
||||||
|
| `GPIO::OPEN_DRAIN` | `0x20` | Enable open-drain output |
|
||||||
|
|
||||||
|
### GPIO.new
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gpio = GPIO.new(pin, flags)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `pin` - GPIO pin number (Integer)
|
||||||
|
- `flags` - direction and mode flags (combined with `|`)
|
||||||
|
- Exactly one of `IN`, `OUT`, or `HIGH_Z` must be specified
|
||||||
|
- `PULL_UP` and `PULL_DOWN` are mutually exclusive
|
||||||
|
- Raises `ArgumentError` on invalid flag combinations
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Input with pull-up
|
||||||
|
button = GPIO.new(2, GPIO::IN | GPIO::PULL_UP)
|
||||||
|
|
||||||
|
# Output
|
||||||
|
led = GPIO.new(25, GPIO::OUT)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Instance Methods
|
||||||
|
|
||||||
|
#### GPIO#read
|
||||||
|
|
||||||
|
Read the current pin value.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
val = gpio.read # => 0 or 1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GPIO#write
|
||||||
|
|
||||||
|
Set the pin output value.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gpio.write(1) # set high
|
||||||
|
gpio.write(0) # set low
|
||||||
|
```
|
||||||
|
|
||||||
|
- Raises `ArgumentError` if value is not 0 or 1
|
||||||
|
|
||||||
|
#### GPIO#high? / GPIO#low?
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gpio.high? # => true if read != 0
|
||||||
|
gpio.low? # => true if read == 0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GPIO#pin
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gpio.pin # => pin number passed to new
|
||||||
|
```
|
||||||
|
|
||||||
|
#### GPIO#setmode
|
||||||
|
|
||||||
|
Reconfigure pin direction and mode flags after initialization.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gpio.setmode(GPIO::OUT)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Class Methods
|
||||||
|
|
||||||
|
These operate directly on pin numbers without creating an instance.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
GPIO.read_at(pin) # => 0 or 1
|
||||||
|
GPIO.write_at(pin, val) # set pin output (0 or 1)
|
||||||
|
GPIO.set_dir_at(pin, flags) # set direction (IN/OUT/HIGH_Z)
|
||||||
|
GPIO.pull_up_at(pin) # enable pull-up
|
||||||
|
GPIO.pull_down_at(pin) # enable pull-down
|
||||||
|
GPIO.open_drain_at(pin) # enable open-drain
|
||||||
|
```
|
||||||
|
|
||||||
|
## HAL Interface
|
||||||
|
|
||||||
|
To add support for a new platform, create a `ports/<name>/`
|
||||||
|
directory and implement the following C functions declared in
|
||||||
|
`<mruby/gpio.h>`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
void mrb_gpio_init(uint8_t pin);
|
||||||
|
void mrb_gpio_set_dir(uint8_t pin, uint8_t flags);
|
||||||
|
void mrb_gpio_pull_up(uint8_t pin);
|
||||||
|
void mrb_gpio_pull_down(uint8_t pin);
|
||||||
|
void mrb_gpio_open_drain(uint8_t pin);
|
||||||
|
int mrb_gpio_read(uint8_t pin);
|
||||||
|
void mrb_gpio_write(uint8_t pin, uint8_t val);
|
||||||
|
```
|
||||||
|
|
||||||
|
The port sources are compiled automatically when the build
|
||||||
|
configuration includes a matching `conf.ports` tag.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef MRUBY_GPIO_H
|
||||||
|
#define MRUBY_GPIO_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* direction/mode flags (bitmask) */
|
||||||
|
#define MRB_GPIO_IN 0x01
|
||||||
|
#define MRB_GPIO_OUT 0x02
|
||||||
|
#define MRB_GPIO_HIGH_Z 0x04
|
||||||
|
#define MRB_GPIO_PULL_UP 0x08
|
||||||
|
#define MRB_GPIO_PULL_DOWN 0x10
|
||||||
|
#define MRB_GPIO_OPEN_DRAIN 0x20
|
||||||
|
|
||||||
|
/* HAL functions - implemented by hw-<platform>-gpio gems */
|
||||||
|
void mrb_gpio_init(uint8_t pin);
|
||||||
|
void mrb_gpio_set_dir(uint8_t pin, uint8_t flags);
|
||||||
|
void mrb_gpio_pull_up(uint8_t pin);
|
||||||
|
void mrb_gpio_pull_down(uint8_t pin);
|
||||||
|
void mrb_gpio_open_drain(uint8_t pin);
|
||||||
|
int mrb_gpio_read(uint8_t pin);
|
||||||
|
void mrb_gpio_write(uint8_t pin, uint8_t val);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MRUBY_GPIO_H */
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
MRuby::Gem::Specification.new('hw-gpio') do |spec|
|
||||||
|
spec.license = 'MIT'
|
||||||
|
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
|
||||||
|
spec.summary = 'GPIO peripheral interface'
|
||||||
|
end
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
class GPIO
|
||||||
|
IN = 0x01
|
||||||
|
OUT = 0x02
|
||||||
|
HIGH_Z = 0x04
|
||||||
|
PULL_UP = 0x08
|
||||||
|
PULL_DOWN = 0x10
|
||||||
|
OPEN_DRAIN = 0x20
|
||||||
|
|
||||||
|
attr_reader :pin
|
||||||
|
|
||||||
|
def initialize(pin, flags)
|
||||||
|
@pin = pin
|
||||||
|
__init(pin)
|
||||||
|
setmode(flags)
|
||||||
|
end
|
||||||
|
|
||||||
|
def setmode(flags)
|
||||||
|
dir = flags & (IN | OUT | HIGH_Z)
|
||||||
|
n = (flags & IN != 0 ? 1 : 0) + (flags & OUT != 0 ? 1 : 0) + (flags & HIGH_Z != 0 ? 1 : 0)
|
||||||
|
if n == 0
|
||||||
|
raise ArgumentError, "specify one of IN, OUT, or HIGH_Z"
|
||||||
|
elsif n > 1
|
||||||
|
raise ArgumentError, "IN, OUT, and HIGH_Z are exclusive"
|
||||||
|
end
|
||||||
|
GPIO.set_dir_at(@pin, dir)
|
||||||
|
|
||||||
|
pull = flags & (PULL_UP | PULL_DOWN)
|
||||||
|
if pull == (PULL_UP | PULL_DOWN)
|
||||||
|
raise ArgumentError, "PULL_UP and PULL_DOWN are exclusive"
|
||||||
|
end
|
||||||
|
GPIO.pull_up_at(@pin) if pull == PULL_UP
|
||||||
|
GPIO.pull_down_at(@pin) if pull == PULL_DOWN
|
||||||
|
GPIO.open_drain_at(@pin) if flags & OPEN_DRAIN != 0
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def high?
|
||||||
|
read != 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def low?
|
||||||
|
read == 0
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#include "driver/gpio.h"
|
||||||
|
#include <mruby/gpio.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_init(uint8_t pin)
|
||||||
|
{
|
||||||
|
gpio_reset_pin(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_set_dir(uint8_t pin, uint8_t flags)
|
||||||
|
{
|
||||||
|
if (flags & MRB_GPIO_IN) {
|
||||||
|
gpio_set_direction(pin, GPIO_MODE_INPUT);
|
||||||
|
}
|
||||||
|
else if (flags & MRB_GPIO_OUT) {
|
||||||
|
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
|
||||||
|
}
|
||||||
|
/* HIGH_Z: not yet implemented */
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_pull_up(uint8_t pin)
|
||||||
|
{
|
||||||
|
gpio_pullup_en(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_pull_down(uint8_t pin)
|
||||||
|
{
|
||||||
|
gpio_pulldown_en(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_open_drain(uint8_t pin)
|
||||||
|
{
|
||||||
|
/* not yet implemented */
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_gpio_read(uint8_t pin)
|
||||||
|
{
|
||||||
|
return gpio_get_level(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_write(uint8_t pin, uint8_t val)
|
||||||
|
{
|
||||||
|
gpio_set_level(pin, val);
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include <mruby/gpio.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_init(uint8_t pin)
|
||||||
|
{
|
||||||
|
gpio_init(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_set_dir(uint8_t pin, uint8_t flags)
|
||||||
|
{
|
||||||
|
if (flags & MRB_GPIO_IN) {
|
||||||
|
gpio_set_dir(pin, false);
|
||||||
|
}
|
||||||
|
else if (flags & MRB_GPIO_OUT) {
|
||||||
|
gpio_set_dir(pin, true);
|
||||||
|
}
|
||||||
|
/* HIGH_Z: not yet implemented */
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_pull_up(uint8_t pin)
|
||||||
|
{
|
||||||
|
gpio_pull_up(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_pull_down(uint8_t pin)
|
||||||
|
{
|
||||||
|
gpio_pull_down(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_open_drain(uint8_t pin)
|
||||||
|
{
|
||||||
|
/* not yet implemented */
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_gpio_read(uint8_t pin)
|
||||||
|
{
|
||||||
|
return gpio_get(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_gpio_write(uint8_t pin, uint8_t val)
|
||||||
|
{
|
||||||
|
gpio_put(pin, val == 1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#include <mruby.h>
|
||||||
|
#include <mruby/presym.h>
|
||||||
|
#include <mruby/variable.h>
|
||||||
|
#include <mruby/gpio.h>
|
||||||
|
|
||||||
|
static mrb_value
|
||||||
|
mrb_gpio_m_init(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int pin;
|
||||||
|
mrb_get_args(mrb, "i", &pin);
|
||||||
|
mrb_gpio_init((uint8_t)pin);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPIO.set_dir_at(pin, flags) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_gpio_s_set_dir_at(mrb_state *mrb, mrb_value klass)
|
||||||
|
{
|
||||||
|
mrb_int pin, flags;
|
||||||
|
mrb_get_args(mrb, "ii", &pin, &flags);
|
||||||
|
mrb_gpio_set_dir((uint8_t)pin, (uint8_t)flags);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPIO.pull_up_at(pin) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_gpio_s_pull_up_at(mrb_state *mrb, mrb_value klass)
|
||||||
|
{
|
||||||
|
mrb_int pin;
|
||||||
|
mrb_get_args(mrb, "i", &pin);
|
||||||
|
mrb_gpio_pull_up((uint8_t)pin);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPIO.pull_down_at(pin) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_gpio_s_pull_down_at(mrb_state *mrb, mrb_value klass)
|
||||||
|
{
|
||||||
|
mrb_int pin;
|
||||||
|
mrb_get_args(mrb, "i", &pin);
|
||||||
|
mrb_gpio_pull_down((uint8_t)pin);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPIO.open_drain_at(pin) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_gpio_s_open_drain_at(mrb_state *mrb, mrb_value klass)
|
||||||
|
{
|
||||||
|
mrb_int pin;
|
||||||
|
mrb_get_args(mrb, "i", &pin);
|
||||||
|
mrb_gpio_open_drain((uint8_t)pin);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPIO.read_at(pin) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_gpio_s_read_at(mrb_state *mrb, mrb_value klass)
|
||||||
|
{
|
||||||
|
mrb_int pin;
|
||||||
|
mrb_get_args(mrb, "i", &pin);
|
||||||
|
return mrb_fixnum_value(mrb_gpio_read((uint8_t)pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPIO.write_at(pin, val) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_gpio_s_write_at(mrb_state *mrb, mrb_value klass)
|
||||||
|
{
|
||||||
|
mrb_int pin, val;
|
||||||
|
mrb_get_args(mrb, "ii", &pin, &val);
|
||||||
|
if (val != 0 && val != 1) {
|
||||||
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be 0 or 1");
|
||||||
|
}
|
||||||
|
mrb_gpio_write((uint8_t)pin, (uint8_t)val);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPIO#read */
|
||||||
|
static mrb_value
|
||||||
|
mrb_gpio_m_read(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int pin = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(pin)));
|
||||||
|
return mrb_fixnum_value(mrb_gpio_read((uint8_t)pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPIO#write(val) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_gpio_m_write(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int val;
|
||||||
|
mrb_get_args(mrb, "i", &val);
|
||||||
|
if (val != 0 && val != 1) {
|
||||||
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "value must be 0 or 1");
|
||||||
|
}
|
||||||
|
mrb_int pin = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(pin)));
|
||||||
|
mrb_gpio_write((uint8_t)pin, (uint8_t)val);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_gpio_gem_init(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(GPIO), mrb->object_class);
|
||||||
|
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_gpio_m_init, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_gpio_m_read, MRB_ARGS_NONE());
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_gpio_m_write, MRB_ARGS_REQ(1));
|
||||||
|
|
||||||
|
mrb_define_class_method_id(mrb, cls, MRB_SYM(set_dir_at), mrb_gpio_s_set_dir_at, MRB_ARGS_REQ(2));
|
||||||
|
mrb_define_class_method_id(mrb, cls, MRB_SYM(pull_up_at), mrb_gpio_s_pull_up_at, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_class_method_id(mrb, cls, MRB_SYM(pull_down_at), mrb_gpio_s_pull_down_at, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_class_method_id(mrb, cls, MRB_SYM(open_drain_at), mrb_gpio_s_open_drain_at, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_class_method_id(mrb, cls, MRB_SYM(read_at), mrb_gpio_s_read_at, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_class_method_id(mrb, cls, MRB_SYM(write_at), mrb_gpio_s_write_at, MRB_ARGS_REQ(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_gpio_gem_final(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
# hw-i2c - I2C peripheral interface for mruby
|
||||||
|
|
||||||
|
This gem provides the `I2C` class for communicating with I2C devices from mruby. It is designed for embedded platforms such as ESP32 and RP2040.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Platform-specific HAL implementations are in `ports/` directories:
|
||||||
|
|
||||||
|
- `ports/esp32/` - ESP32 using ESP-IDF I2C master driver
|
||||||
|
- `ports/rp2040/` - RP2040 using Pico SDK
|
||||||
|
|
||||||
|
The build system automatically compiles matching port sources based
|
||||||
|
on `conf.ports` setting.
|
||||||
|
|
||||||
|
## Build Configuration
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# For ESP32
|
||||||
|
MRuby::CrossBuild.new('esp32') do |conf|
|
||||||
|
conf.ports :esp32
|
||||||
|
conf.gem core: 'hw-i2c'
|
||||||
|
end
|
||||||
|
|
||||||
|
# For RP2040
|
||||||
|
MRuby::CrossBuild.new('rp2040') do |conf|
|
||||||
|
conf.ports :rp2040
|
||||||
|
conf.gem core: 'hw-i2c'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ruby API
|
||||||
|
|
||||||
|
### I2C.new
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
i2c = I2C.new(
|
||||||
|
unit: :ESP32_I2C0, # I2C unit name (platform-specific, required)
|
||||||
|
frequency: 100_000, # bus frequency in Hz (default: 100kHz)
|
||||||
|
sda_pin: 21, # SDA GPIO pin number (default: -1 for platform default)
|
||||||
|
scl_pin: 22, # SCL GPIO pin number (default: -1 for platform default)
|
||||||
|
timeout: 500 # default timeout in ms (default: 500)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Unit Names
|
||||||
|
|
||||||
|
| Platform | Available Units |
|
||||||
|
| -------- | ------------------------------ |
|
||||||
|
| ESP32 | `:ESP32_I2C0`, `:ESP32_I2C1` |
|
||||||
|
| RP2040 | `:RP2040_I2C0`, `:RP2040_I2C1` |
|
||||||
|
|
||||||
|
On RP2040, if `sda_pin` or `scl_pin` is -1, the Pico SDK default pins are used.
|
||||||
|
|
||||||
|
### I2C#write
|
||||||
|
|
||||||
|
Write data to an I2C device.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
i2c.write(addr, *data, timeout: 500)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `addr` - 7-bit I2C device address (Integer)
|
||||||
|
- `data` - one or more data arguments, each can be:
|
||||||
|
- **Integer** - a single byte (0-255)
|
||||||
|
- **Array of Integer** - multiple bytes
|
||||||
|
- **String** - raw bytes
|
||||||
|
- `timeout:` - optional timeout in ms (overrides instance default)
|
||||||
|
- Returns the number of bytes written (Integer)
|
||||||
|
- Raises `IOError` on failure
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Write a single byte
|
||||||
|
i2c.write(0x3C, 0x00)
|
||||||
|
|
||||||
|
# Write multiple bytes
|
||||||
|
i2c.write(0x3C, 0x00, [0xAE, 0xD5, 0x80])
|
||||||
|
|
||||||
|
# Write a string
|
||||||
|
i2c.write(0x3C, "hello")
|
||||||
|
|
||||||
|
# Mix data types
|
||||||
|
i2c.write(0x3C, 0x40, [0x01, 0x02], "data")
|
||||||
|
```
|
||||||
|
|
||||||
|
### I2C#read
|
||||||
|
|
||||||
|
Read data from an I2C device. Optionally write data before reading (repeated START).
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
i2c.read(addr, length, *write_data, timeout: 500)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `addr` - 7-bit I2C device address (Integer)
|
||||||
|
- `length` - number of bytes to read (Integer, must be positive)
|
||||||
|
- `write_data` - optional data to write before reading (same format as `write`). When provided, the gem performs a write-then-read transaction using I2C repeated START condition. This is the standard way to read from a specific register.
|
||||||
|
- `timeout:` - optional timeout in ms (overrides instance default)
|
||||||
|
- Returns the data read (String)
|
||||||
|
- Raises `IOError` on failure, `ArgumentError` if length <= 0
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Simple read (2 bytes from device)
|
||||||
|
data = i2c.read(0x50, 2)
|
||||||
|
|
||||||
|
# Register read: write register address 0x00, then read 2 bytes
|
||||||
|
data = i2c.read(0x50, 2, 0x00)
|
||||||
|
|
||||||
|
# Multi-byte register address
|
||||||
|
data = i2c.read(0x50, 4, [0x00, 0x10])
|
||||||
|
```
|
||||||
|
|
||||||
|
### I2C#scan
|
||||||
|
|
||||||
|
Scan the I2C bus for responsive devices.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
i2c.scan(timeout: 500)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `timeout:` - optional timeout per probe in ms
|
||||||
|
- Returns an Array of 7-bit addresses (Integer) that responded
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
found = i2c.scan
|
||||||
|
# => [0x3C, 0x50, 0x68]
|
||||||
|
```
|
||||||
|
|
||||||
|
## HAL Interface
|
||||||
|
|
||||||
|
To add support for a new platform, create a `ports/<name>/`
|
||||||
|
directory and implement the following C functions declared in
|
||||||
|
`<mruby/i2c.h>`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int mrb_i2c_unit_name_to_num(const char *name);
|
||||||
|
mrb_i2c_status mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl);
|
||||||
|
int mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len,
|
||||||
|
uint32_t timeout_us);
|
||||||
|
int mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len,
|
||||||
|
uint32_t timeout_us);
|
||||||
|
int mrb_i2c_write_read(int unit, uint8_t addr,
|
||||||
|
const uint8_t *src, size_t wlen,
|
||||||
|
uint8_t *dst, size_t rlen,
|
||||||
|
uint32_t timeout_us);
|
||||||
|
```
|
||||||
|
|
||||||
|
The port sources are compiled automatically when the build
|
||||||
|
configuration includes a matching `conf.ports` tag.
|
||||||
|
|
||||||
|
### Error Codes
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum {
|
||||||
|
MRB_I2C_OK = 0,
|
||||||
|
MRB_I2C_ERROR_UNIT = -1, /* invalid or uninitialized unit */
|
||||||
|
MRB_I2C_ERROR_TIMEOUT = -2, /* communication timeout */
|
||||||
|
MRB_I2C_ERROR_NACK = -3, /* device did not acknowledge */
|
||||||
|
} mrb_i2c_status;
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef MRUBY_I2C_H
|
||||||
|
#define MRUBY_I2C_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MRB_I2C_OK = 0,
|
||||||
|
MRB_I2C_ERROR_UNIT = -1,
|
||||||
|
MRB_I2C_ERROR_TIMEOUT = -2,
|
||||||
|
MRB_I2C_ERROR_NACK = -3,
|
||||||
|
} mrb_i2c_status;
|
||||||
|
|
||||||
|
/* HAL functions - implemented by hw-<platform>-i2c gems */
|
||||||
|
mrb_i2c_status mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl);
|
||||||
|
int mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len, uint32_t timeout_us);
|
||||||
|
int mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len, uint32_t timeout_us);
|
||||||
|
int mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen,
|
||||||
|
uint8_t *dst, size_t rlen, uint32_t timeout_us);
|
||||||
|
int mrb_i2c_unit_name_to_num(const char *name);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MRUBY_I2C_H */
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
MRuby::Gem::Specification.new('hw-i2c') do |spec|
|
||||||
|
spec.license = 'MIT'
|
||||||
|
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
|
||||||
|
spec.summary = 'I2C peripheral interface'
|
||||||
|
end
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
class I2C
|
||||||
|
DEFAULT_FREQUENCY = 100_000 # Hz
|
||||||
|
DEFAULT_TIMEOUT = 500 # ms
|
||||||
|
|
||||||
|
def initialize(unit:, frequency: DEFAULT_FREQUENCY, sda_pin: -1, scl_pin: -1, timeout: DEFAULT_TIMEOUT)
|
||||||
|
@timeout = timeout
|
||||||
|
@unit_num = __init(unit.to_s, frequency, sda_pin, scl_pin)
|
||||||
|
end
|
||||||
|
|
||||||
|
def scan(timeout: @timeout)
|
||||||
|
found = []
|
||||||
|
(0x08..0x77).each do |addr|
|
||||||
|
begin
|
||||||
|
read(addr, 1, timeout: timeout)
|
||||||
|
found << addr
|
||||||
|
rescue IOError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
found
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
#include <mruby/i2c.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
i2c_master_bus_handle_t bus;
|
||||||
|
uint32_t freq;
|
||||||
|
bool initialized;
|
||||||
|
} i2c_ctx;
|
||||||
|
|
||||||
|
/* ESP32 supports up to 2 I2C ports */
|
||||||
|
static i2c_ctx ctx[2];
|
||||||
|
|
||||||
|
static bool
|
||||||
|
valid_unit(int unit)
|
||||||
|
{
|
||||||
|
return unit >= 0 && unit <= 1 && ctx[unit].initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t
|
||||||
|
us_to_ms(uint32_t timeout_us)
|
||||||
|
{
|
||||||
|
uint32_t ms = (timeout_us + 999) / 1000;
|
||||||
|
return (ms < 10) ? 10 : ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
static i2c_master_dev_handle_t
|
||||||
|
add_device(int unit, uint8_t addr)
|
||||||
|
{
|
||||||
|
i2c_device_config_t cfg = {
|
||||||
|
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||||
|
.device_address = addr,
|
||||||
|
.scl_speed_hz = ctx[unit].freq,
|
||||||
|
};
|
||||||
|
i2c_master_dev_handle_t dev;
|
||||||
|
if (i2c_master_bus_add_device(ctx[unit].bus, &cfg, &dev) != ESP_OK)
|
||||||
|
return NULL;
|
||||||
|
return dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_i2c_unit_name_to_num(const char *name)
|
||||||
|
{
|
||||||
|
if (strcmp(name, "ESP32_I2C0") == 0) return 0;
|
||||||
|
if (strcmp(name, "ESP32_I2C1") == 0) return 1;
|
||||||
|
return MRB_I2C_ERROR_UNIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_i2c_status
|
||||||
|
mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl)
|
||||||
|
{
|
||||||
|
if (unit < 0 || unit > 1) return MRB_I2C_ERROR_UNIT;
|
||||||
|
|
||||||
|
if (ctx[unit].initialized) {
|
||||||
|
i2c_del_master_bus(ctx[unit].bus);
|
||||||
|
ctx[unit].initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_master_bus_config_t cfg = {
|
||||||
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||||
|
.i2c_port = unit,
|
||||||
|
.scl_io_num = scl,
|
||||||
|
.sda_io_num = sda,
|
||||||
|
.glitch_ignore_cnt = 7,
|
||||||
|
.flags.enable_internal_pullup = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t err = i2c_new_master_bus(&cfg, &ctx[unit].bus);
|
||||||
|
if (err != ESP_OK) return MRB_I2C_ERROR_UNIT;
|
||||||
|
|
||||||
|
ctx[unit].initialized = true;
|
||||||
|
ctx[unit].freq = freq;
|
||||||
|
return MRB_I2C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len,
|
||||||
|
uint32_t timeout_us)
|
||||||
|
{
|
||||||
|
if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT;
|
||||||
|
|
||||||
|
i2c_master_dev_handle_t dev = add_device(unit, addr);
|
||||||
|
if (!dev) return -1;
|
||||||
|
|
||||||
|
esp_err_t err = i2c_master_receive(dev, dst, len, us_to_ms(timeout_us));
|
||||||
|
i2c_master_bus_rm_device(dev);
|
||||||
|
return (err == ESP_OK) ? (int)len : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len,
|
||||||
|
uint32_t timeout_us)
|
||||||
|
{
|
||||||
|
if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT;
|
||||||
|
|
||||||
|
i2c_master_dev_handle_t dev = add_device(unit, addr);
|
||||||
|
if (!dev) return -1;
|
||||||
|
|
||||||
|
esp_err_t err = i2c_master_transmit(dev, src, len, us_to_ms(timeout_us));
|
||||||
|
i2c_master_bus_rm_device(dev);
|
||||||
|
return (err == ESP_OK) ? (int)len : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen,
|
||||||
|
uint8_t *dst, size_t rlen, uint32_t timeout_us)
|
||||||
|
{
|
||||||
|
if (!valid_unit(unit)) return MRB_I2C_ERROR_UNIT;
|
||||||
|
|
||||||
|
i2c_master_dev_handle_t dev = add_device(unit, addr);
|
||||||
|
if (!dev) return -1;
|
||||||
|
|
||||||
|
esp_err_t err = i2c_master_transmit_receive(dev, src, wlen, dst, rlen,
|
||||||
|
us_to_ms(timeout_us));
|
||||||
|
i2c_master_bus_rm_device(dev);
|
||||||
|
return (err == ESP_OK) ? (int)rlen : -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/i2c.h"
|
||||||
|
#include <mruby/i2c.h>
|
||||||
|
|
||||||
|
#define UNIT_SELECT(u) \
|
||||||
|
i2c_inst_t *inst; \
|
||||||
|
switch (u) { \
|
||||||
|
case 0: inst = i2c0; break; \
|
||||||
|
case 1: inst = i2c1; break; \
|
||||||
|
default: return MRB_I2C_ERROR_UNIT; \
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_i2c_unit_name_to_num(const char *name)
|
||||||
|
{
|
||||||
|
if (strcmp(name, "RP2040_I2C0") == 0) return 0;
|
||||||
|
if (strcmp(name, "RP2040_I2C1") == 0) return 1;
|
||||||
|
return MRB_I2C_ERROR_UNIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_i2c_status
|
||||||
|
mrb_i2c_init(int unit, uint32_t freq, int8_t sda, int8_t scl)
|
||||||
|
{
|
||||||
|
UNIT_SELECT(unit);
|
||||||
|
i2c_init(inst, freq);
|
||||||
|
|
||||||
|
if (sda < 0) sda = PICO_DEFAULT_I2C_SDA_PIN;
|
||||||
|
if (scl < 0) scl = PICO_DEFAULT_I2C_SCL_PIN;
|
||||||
|
gpio_set_function(sda, GPIO_FUNC_I2C);
|
||||||
|
gpio_set_function(scl, GPIO_FUNC_I2C);
|
||||||
|
gpio_pull_up(sda);
|
||||||
|
gpio_pull_up(scl);
|
||||||
|
|
||||||
|
return MRB_I2C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_i2c_read(int unit, uint8_t addr, uint8_t *dst, size_t len,
|
||||||
|
uint32_t timeout_us)
|
||||||
|
{
|
||||||
|
UNIT_SELECT(unit);
|
||||||
|
return i2c_read_timeout_us(inst, addr, dst, len, false, timeout_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_i2c_write(int unit, uint8_t addr, const uint8_t *src, size_t len,
|
||||||
|
uint32_t timeout_us)
|
||||||
|
{
|
||||||
|
UNIT_SELECT(unit);
|
||||||
|
return i2c_write_timeout_us(inst, addr, src, len, false, timeout_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_i2c_write_read(int unit, uint8_t addr, const uint8_t *src, size_t wlen,
|
||||||
|
uint8_t *dst, size_t rlen, uint32_t timeout_us)
|
||||||
|
{
|
||||||
|
UNIT_SELECT(unit);
|
||||||
|
/* write with nostop=true (no STOP, keeps bus for repeated START) */
|
||||||
|
int ret = i2c_write_timeout_us(inst, addr, src, wlen, true, timeout_us);
|
||||||
|
if (ret < 0) return ret;
|
||||||
|
/* read with nostop=false (STOP after read) */
|
||||||
|
return i2c_read_timeout_us(inst, addr, dst, rlen, false, timeout_us);
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <mruby.h>
|
||||||
|
#include <mruby/presym.h>
|
||||||
|
#include <mruby/variable.h>
|
||||||
|
#include <mruby/array.h>
|
||||||
|
#include <mruby/string.h>
|
||||||
|
#include <mruby/i2c.h>
|
||||||
|
|
||||||
|
#define STACK_BUF_SIZE 256
|
||||||
|
#define E_IO_ERROR mrb_exc_get_id(mrb, MRB_SYM(IOError))
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
i2c_fill_buf(mrb_state *mrb, uint8_t *buf, mrb_value *args, mrb_int argc)
|
||||||
|
{
|
||||||
|
size_t pos = 0;
|
||||||
|
for (mrb_int i = 0; i < argc; i++) {
|
||||||
|
switch (mrb_type(args[i])) {
|
||||||
|
case MRB_TT_ARRAY: {
|
||||||
|
mrb_int alen = RARRAY_LEN(args[i]);
|
||||||
|
const mrb_value *aptr = RARRAY_PTR(args[i]);
|
||||||
|
for (mrb_int j = 0; j < alen; j++) {
|
||||||
|
if (!mrb_integer_p(aptr[j])) {
|
||||||
|
mrb_raise(mrb, E_TYPE_ERROR, "array element must be Integer");
|
||||||
|
}
|
||||||
|
buf[pos++] = (uint8_t)mrb_integer(aptr[j]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MRB_TT_INTEGER:
|
||||||
|
buf[pos++] = (uint8_t)mrb_integer(args[i]);
|
||||||
|
break;
|
||||||
|
case MRB_TT_STRING:
|
||||||
|
memcpy(&buf[pos], RSTRING_PTR(args[i]), RSTRING_LEN(args[i]));
|
||||||
|
pos += RSTRING_LEN(args[i]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
i2c_calc_size(mrb_state *mrb, mrb_value *args, mrb_int argc)
|
||||||
|
{
|
||||||
|
size_t total = 0;
|
||||||
|
for (mrb_int i = 0; i < argc; i++) {
|
||||||
|
switch (mrb_type(args[i])) {
|
||||||
|
case MRB_TT_ARRAY:
|
||||||
|
total += RARRAY_LEN(args[i]);
|
||||||
|
break;
|
||||||
|
case MRB_TT_INTEGER:
|
||||||
|
total += 1;
|
||||||
|
break;
|
||||||
|
case MRB_TT_STRING:
|
||||||
|
total += RSTRING_LEN(args[i]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mrb_raise(mrb, E_TYPE_ERROR, "Integer, Array, or String expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate write buffer, fill it, return pointer and size.
|
||||||
|
Caller must free if need_free is set. */
|
||||||
|
static uint8_t*
|
||||||
|
i2c_build_buf(mrb_state *mrb, mrb_value *args, mrb_int argc,
|
||||||
|
size_t *out_len, uint8_t *sbuf, mrb_bool *need_free)
|
||||||
|
{
|
||||||
|
size_t total = i2c_calc_size(mrb, args, argc);
|
||||||
|
uint8_t *buf;
|
||||||
|
if (total <= STACK_BUF_SIZE) {
|
||||||
|
buf = sbuf;
|
||||||
|
*need_free = FALSE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buf = (uint8_t*)mrb_malloc(mrb, total);
|
||||||
|
*need_free = TRUE;
|
||||||
|
}
|
||||||
|
i2c_fill_buf(mrb, buf, args, argc);
|
||||||
|
*out_len = total;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mrb_int
|
||||||
|
get_timeout(mrb_state *mrb, mrb_value self, mrb_value kw)
|
||||||
|
{
|
||||||
|
if (mrb_undef_p(kw)) {
|
||||||
|
return mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(timeout)));
|
||||||
|
}
|
||||||
|
return mrb_integer(kw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static mrb_value
|
||||||
|
mrb_i2c_m_write(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_value *args;
|
||||||
|
mrb_int argc, addr;
|
||||||
|
const mrb_sym kw_names[] = { MRB_SYM(timeout) };
|
||||||
|
mrb_value kw_values[1];
|
||||||
|
mrb_kwargs kwargs = { 1, 0, kw_names, kw_values, NULL };
|
||||||
|
|
||||||
|
mrb_get_args(mrb, "i*:", &addr, &args, &argc, &kwargs);
|
||||||
|
|
||||||
|
mrb_int timeout_ms = get_timeout(mrb, self, kw_values[0]);
|
||||||
|
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||||
|
|
||||||
|
uint8_t sbuf[STACK_BUF_SIZE];
|
||||||
|
size_t wlen;
|
||||||
|
mrb_bool need_free;
|
||||||
|
uint8_t *buf = i2c_build_buf(mrb, args, argc, &wlen, sbuf, &need_free);
|
||||||
|
|
||||||
|
int ret = mrb_i2c_write((int)unit, (uint8_t)addr, buf, wlen,
|
||||||
|
(uint32_t)timeout_ms * 1000);
|
||||||
|
if (need_free) mrb_free(mrb, buf);
|
||||||
|
if (ret < 0) {
|
||||||
|
mrb_raise(mrb, E_IO_ERROR, "I2C write failed");
|
||||||
|
}
|
||||||
|
return mrb_fixnum_value(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static mrb_value
|
||||||
|
mrb_i2c_m_read(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_value *args;
|
||||||
|
mrb_int argc, addr, len;
|
||||||
|
const mrb_sym kw_names[] = { MRB_SYM(timeout) };
|
||||||
|
mrb_value kw_values[1];
|
||||||
|
mrb_kwargs kwargs = { 1, 0, kw_names, kw_values, NULL };
|
||||||
|
|
||||||
|
mrb_get_args(mrb, "ii*:", &addr, &len, &args, &argc, &kwargs);
|
||||||
|
|
||||||
|
if (len <= 0) {
|
||||||
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "read length must be positive");
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_int timeout_ms = get_timeout(mrb, self, kw_values[0]);
|
||||||
|
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||||
|
uint32_t timeout_us = (uint32_t)timeout_ms * 1000;
|
||||||
|
|
||||||
|
uint8_t *rxbuf = (uint8_t*)mrb_malloc(mrb, len);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (argc > 0) {
|
||||||
|
/* write-then-read (repeated START) */
|
||||||
|
uint8_t sbuf[STACK_BUF_SIZE];
|
||||||
|
size_t wlen;
|
||||||
|
mrb_bool need_free;
|
||||||
|
uint8_t *wbuf = i2c_build_buf(mrb, args, argc, &wlen, sbuf, &need_free);
|
||||||
|
|
||||||
|
ret = mrb_i2c_write_read((int)unit, (uint8_t)addr,
|
||||||
|
wbuf, wlen, rxbuf, (size_t)len, timeout_us);
|
||||||
|
if (need_free) mrb_free(mrb, wbuf);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ret = mrb_i2c_read((int)unit, (uint8_t)addr, rxbuf, (size_t)len, timeout_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
mrb_free(mrb, rxbuf);
|
||||||
|
mrb_raise(mrb, E_IO_ERROR, "I2C read failed");
|
||||||
|
}
|
||||||
|
mrb_value str = mrb_str_new(mrb, (const char*)rxbuf, ret);
|
||||||
|
mrb_free(mrb, rxbuf);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mrb_value
|
||||||
|
mrb_i2c_m_init(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
const char *unit;
|
||||||
|
mrb_int freq, sda, scl;
|
||||||
|
|
||||||
|
mrb_get_args(mrb, "ziii", &unit, &freq, &sda, &scl);
|
||||||
|
|
||||||
|
int num = mrb_i2c_unit_name_to_num(unit);
|
||||||
|
if (num < 0) {
|
||||||
|
mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown I2C unit: %s", unit);
|
||||||
|
}
|
||||||
|
mrb_i2c_status st = mrb_i2c_init(num, (uint32_t)freq, (int8_t)sda, (int8_t)scl);
|
||||||
|
if (st != MRB_I2C_OK) {
|
||||||
|
mrb_raise(mrb, E_IO_ERROR, "I2C init failed");
|
||||||
|
}
|
||||||
|
return mrb_fixnum_value(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_i2c_gem_init(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(I2C), mrb->object_class);
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_i2c_m_init, MRB_ARGS_REQ(4));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_i2c_m_write, MRB_ARGS_REQ(1)|MRB_ARGS_REST()|MRB_ARGS_KEY(1, 0));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_i2c_m_read, MRB_ARGS_REQ(2)|MRB_ARGS_REST()|MRB_ARGS_KEY(1, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_i2c_gem_final(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# hw-pwm - PWM peripheral interface for mruby
|
||||||
|
|
||||||
|
This gem provides the `PWM` class for Pulse Width Modulation
|
||||||
|
output from mruby. It is designed for embedded platforms such as
|
||||||
|
ESP32 and RP2040.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Platform-specific HAL implementations are in `ports/` directories:
|
||||||
|
|
||||||
|
- `ports/esp32/` - ESP32 using LEDC peripheral
|
||||||
|
- `ports/rp2040/` - RP2040 using Pico SDK PWM hardware
|
||||||
|
|
||||||
|
## Build Configuration
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
MRuby::CrossBuild.new('esp32') do |conf|
|
||||||
|
conf.ports :esp32
|
||||||
|
conf.gem core: 'hw-pwm'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ruby API
|
||||||
|
|
||||||
|
### PWM.new
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pwm = PWM.new(pin, frequency: 1000, duty: 50)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `pin` - GPIO pin number (Integer)
|
||||||
|
- `frequency:` - frequency in Hz (default: 0, disabled)
|
||||||
|
- `duty:` - duty cycle in percent 0-100 (default: 50)
|
||||||
|
|
||||||
|
### PWM#frequency(freq)
|
||||||
|
|
||||||
|
Set frequency in Hz. Returns the frequency. Setting 0 disables
|
||||||
|
output.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pwm.frequency(1000) # 1 kHz
|
||||||
|
pwm.frequency(0) # disable
|
||||||
|
```
|
||||||
|
|
||||||
|
### PWM#period_us(us)
|
||||||
|
|
||||||
|
Set period in microseconds. Returns the corresponding frequency.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pwm.period_us(1000) # 1ms period = 1 kHz
|
||||||
|
```
|
||||||
|
|
||||||
|
### PWM#duty(pct)
|
||||||
|
|
||||||
|
Set duty cycle in percent (0.0-100.0). Clamped to range.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pwm.duty(75.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### PWM#pulse_width_us(us)
|
||||||
|
|
||||||
|
Set pulse width in microseconds. Duty cycle is calculated from
|
||||||
|
current frequency.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
pwm.pulse_width_us(500) # 500us pulse width
|
||||||
|
```
|
||||||
|
|
||||||
|
## HAL Interface
|
||||||
|
|
||||||
|
To add support for a new platform, create a `ports/<name>/`
|
||||||
|
directory and implement the following C functions declared in
|
||||||
|
`<mruby/pwm.h>`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
void mrb_pwm_init(uint32_t pin);
|
||||||
|
void mrb_pwm_set_freq_duty(uint32_t pin, float frequency, float duty);
|
||||||
|
void mrb_pwm_set_enabled(uint32_t pin, bool enabled);
|
||||||
|
```
|
||||||
|
|
||||||
|
- `frequency` is in Hz, `duty` is in percent (0-100)
|
||||||
|
- `mrb_pwm_set_enabled` is called with `false` when frequency is 0
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef MRUBY_PWM_H
|
||||||
|
#define MRUBY_PWM_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* HAL functions - implemented in ports/<platform>/pwm.c */
|
||||||
|
void mrb_pwm_init(uint32_t pin);
|
||||||
|
void mrb_pwm_set_freq_duty(uint32_t pin, float frequency, float duty);
|
||||||
|
void mrb_pwm_set_enabled(uint32_t pin, bool enabled);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MRUBY_PWM_H */
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
MRuby::Gem::Specification.new('hw-pwm') do |spec|
|
||||||
|
spec.license = 'MIT'
|
||||||
|
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
|
||||||
|
spec.summary = 'PWM peripheral interface'
|
||||||
|
end
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
class PWM
|
||||||
|
def initialize(pin, frequency: 0, duty: 50)
|
||||||
|
@pin = pin
|
||||||
|
__init(@pin)
|
||||||
|
@frequency = frequency.to_f
|
||||||
|
@duty = duty.to_f
|
||||||
|
frequency(@frequency)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#include "driver/ledc.h"
|
||||||
|
#include <mruby/pwm.h>
|
||||||
|
|
||||||
|
#define DUTY_RESOLUTION LEDC_TIMER_14_BIT
|
||||||
|
|
||||||
|
static int8_t channel_for_gpio[GPIO_NUM_MAX];
|
||||||
|
static int next_channel;
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_pwm_init(uint32_t gpio)
|
||||||
|
{
|
||||||
|
if (gpio >= GPIO_NUM_MAX) return;
|
||||||
|
if (next_channel >= LEDC_CHANNEL_MAX) return;
|
||||||
|
|
||||||
|
ledc_timer_config_t timer_cfg = {
|
||||||
|
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||||
|
.timer_num = LEDC_TIMER_0,
|
||||||
|
.duty_resolution = DUTY_RESOLUTION,
|
||||||
|
.freq_hz = 1000,
|
||||||
|
.clk_cfg = LEDC_AUTO_CLK,
|
||||||
|
};
|
||||||
|
ledc_timer_config(&timer_cfg);
|
||||||
|
|
||||||
|
ledc_channel_config_t ch_cfg = {
|
||||||
|
.gpio_num = gpio,
|
||||||
|
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||||
|
.channel = next_channel,
|
||||||
|
.timer_sel = LEDC_TIMER_0,
|
||||||
|
.intr_type = LEDC_INTR_DISABLE,
|
||||||
|
.duty = 0,
|
||||||
|
.hpoint = 0,
|
||||||
|
};
|
||||||
|
ledc_channel_config(&ch_cfg);
|
||||||
|
|
||||||
|
channel_for_gpio[gpio] = next_channel++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_pwm_set_freq_duty(uint32_t gpio, float frequency, float duty)
|
||||||
|
{
|
||||||
|
if (gpio >= GPIO_NUM_MAX) return;
|
||||||
|
|
||||||
|
ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, (uint32_t)frequency);
|
||||||
|
|
||||||
|
int8_t ch = channel_for_gpio[gpio];
|
||||||
|
uint32_t max_duty = (1 << DUTY_RESOLUTION) - 1;
|
||||||
|
uint32_t d = (uint32_t)(duty * max_duty / 100.0f);
|
||||||
|
ledc_set_duty(LEDC_LOW_SPEED_MODE, ch, d);
|
||||||
|
ledc_update_duty(LEDC_LOW_SPEED_MODE, ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_pwm_set_enabled(uint32_t gpio, bool enabled)
|
||||||
|
{
|
||||||
|
if (!enabled) {
|
||||||
|
int8_t ch = channel_for_gpio[gpio];
|
||||||
|
ledc_stop(LEDC_LOW_SPEED_MODE, ch, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/pwm.h"
|
||||||
|
#include <mruby/pwm.h>
|
||||||
|
|
||||||
|
#define APB_CLK_FREQ 125000000
|
||||||
|
#define CLK_DIV 100.0f
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_pwm_init(uint32_t pin)
|
||||||
|
{
|
||||||
|
gpio_set_function(pin, GPIO_FUNC_PWM);
|
||||||
|
uint slice = pwm_gpio_to_slice_num(pin);
|
||||||
|
pwm_set_clkdiv(slice, CLK_DIV);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_pwm_set_freq_duty(uint32_t pin, float frequency, float duty)
|
||||||
|
{
|
||||||
|
uint slice = pwm_gpio_to_slice_num(pin);
|
||||||
|
uint channel = pwm_gpio_to_channel(pin);
|
||||||
|
float period = 1.0f / frequency;
|
||||||
|
uint16_t wrap = (uint16_t)(period * APB_CLK_FREQ / CLK_DIV);
|
||||||
|
pwm_set_wrap(slice, wrap);
|
||||||
|
uint16_t level = (uint16_t)(wrap * duty / 100.0f);
|
||||||
|
pwm_set_chan_level(slice, channel, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_pwm_set_enabled(uint32_t pin, bool enabled)
|
||||||
|
{
|
||||||
|
uint slice = pwm_gpio_to_slice_num(pin);
|
||||||
|
pwm_set_enabled(slice, enabled);
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#include <mruby.h>
|
||||||
|
#include <mruby/presym.h>
|
||||||
|
#include <mruby/variable.h>
|
||||||
|
#include <mruby/pwm.h>
|
||||||
|
|
||||||
|
static mrb_value
|
||||||
|
mrb_pwm_m_init(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int pin;
|
||||||
|
mrb_get_args(mrb, "i", &pin);
|
||||||
|
mrb_pwm_init((uint32_t)pin);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
apply_freq_duty(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
uint32_t pin = (uint32_t)mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(pin)));
|
||||||
|
mrb_float freq = mrb_as_float(mrb, mrb_iv_get(mrb, self, MRB_IVSYM(frequency)));
|
||||||
|
mrb_float duty = mrb_as_float(mrb, mrb_iv_get(mrb, self, MRB_IVSYM(duty)));
|
||||||
|
mrb_pwm_set_freq_duty(pin, (float)freq, (float)duty);
|
||||||
|
mrb_pwm_set_enabled(pin, freq > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PWM#frequency(freq) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_pwm_m_frequency(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_float freq;
|
||||||
|
mrb_get_args(mrb, "f", &freq);
|
||||||
|
mrb_iv_set(mrb, self, MRB_IVSYM(frequency), mrb_float_value(mrb, freq));
|
||||||
|
apply_freq_duty(mrb, self);
|
||||||
|
return mrb_float_value(mrb, freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PWM#period_us(us) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_pwm_m_period_us(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int us;
|
||||||
|
mrb_get_args(mrb, "i", &us);
|
||||||
|
mrb_float freq = 1000000.0 / us;
|
||||||
|
mrb_iv_set(mrb, self, MRB_IVSYM(frequency), mrb_float_value(mrb, freq));
|
||||||
|
apply_freq_duty(mrb, self);
|
||||||
|
return mrb_float_value(mrb, freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PWM#duty(pct) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_pwm_m_duty(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_float duty;
|
||||||
|
mrb_get_args(mrb, "f", &duty);
|
||||||
|
if (duty < 0.0) duty = 0.0;
|
||||||
|
if (duty > 100.0) duty = 100.0;
|
||||||
|
mrb_iv_set(mrb, self, MRB_IVSYM(duty), mrb_float_value(mrb, duty));
|
||||||
|
apply_freq_duty(mrb, self);
|
||||||
|
return mrb_float_value(mrb, duty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PWM#pulse_width_us(us) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_pwm_m_pulse_width_us(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int pw;
|
||||||
|
mrb_get_args(mrb, "i", &pw);
|
||||||
|
mrb_float freq = mrb_as_float(mrb, mrb_iv_get(mrb, self, MRB_IVSYM(frequency)));
|
||||||
|
mrb_float duty = (mrb_float)pw / 10000.0 * freq;
|
||||||
|
if (duty < 0.0) duty = 0.0;
|
||||||
|
if (duty > 100.0) duty = 100.0;
|
||||||
|
mrb_iv_set(mrb, self, MRB_IVSYM(duty), mrb_float_value(mrb, duty));
|
||||||
|
apply_freq_duty(mrb, self);
|
||||||
|
return mrb_float_value(mrb, duty);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_pwm_gem_init(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(PWM), mrb->object_class);
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(__init), mrb_pwm_m_init, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(frequency), mrb_pwm_m_frequency, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(period_us), mrb_pwm_m_period_us, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(duty), mrb_pwm_m_duty, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(pulse_width_us), mrb_pwm_m_pulse_width_us, MRB_ARGS_REQ(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_pwm_gem_final(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# hw-spi - SPI peripheral interface for mruby
|
||||||
|
|
||||||
|
This gem provides the `SPI` class for Serial Peripheral Interface
|
||||||
|
communication from mruby. It is designed for embedded platforms
|
||||||
|
such as ESP32 and RP2040.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Platform-specific HAL implementations are in `ports/` directories:
|
||||||
|
|
||||||
|
- `ports/esp32/` - ESP32 using ESP-IDF SPI master driver
|
||||||
|
- `ports/rp2040/` - RP2040 using Pico SDK
|
||||||
|
|
||||||
|
The build system automatically compiles matching port sources based
|
||||||
|
on `conf.ports` setting.
|
||||||
|
|
||||||
|
## Build Configuration
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
MRuby::CrossBuild.new('esp32') do |conf|
|
||||||
|
conf.ports :esp32
|
||||||
|
conf.gem core: 'hw-spi'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ruby API
|
||||||
|
|
||||||
|
### SPI.new
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
spi = SPI.new(
|
||||||
|
unit: :RP2040_SPI0, # SPI unit name (required)
|
||||||
|
frequency: 100_000, # clock frequency in Hz (default: 100kHz)
|
||||||
|
sck_pin: -1, # SCK GPIO pin (default: platform default)
|
||||||
|
copi_pin: -1, # COPI/MOSI GPIO pin
|
||||||
|
cipo_pin: -1, # CIPO/MISO GPIO pin
|
||||||
|
cs_pin: -1, # CS GPIO pin (-1 for manual control)
|
||||||
|
mode: 0, # SPI mode 0-3 (default: 0)
|
||||||
|
first_bit: SPI::MSB_FIRST # bit order (default: MSB_FIRST)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Unit Names
|
||||||
|
|
||||||
|
| Platform | Available Units |
|
||||||
|
| -------- | ------------------------------------------ |
|
||||||
|
| ESP32 | `:ESP32_SPI2_HOST`, `:ESP32_HSPI_HOST`, |
|
||||||
|
| | `:ESP32_SPI3_HOST`\*, `:ESP32_VSPI_HOST`\* |
|
||||||
|
| RP2040 | `:RP2040_SPI0`, `:RP2040_SPI1` |
|
||||||
|
|
||||||
|
\*SPI3 availability depends on ESP32 variant.
|
||||||
|
|
||||||
|
### `SPI#write(*data)`
|
||||||
|
|
||||||
|
Write data to the SPI bus. Data can be Integer, Array, or String.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
spi.write(0x01, [0x02, 0x03])
|
||||||
|
```
|
||||||
|
|
||||||
|
### SPI#read(len, tx_value = 0)
|
||||||
|
|
||||||
|
Read `len` bytes. Optionally specify the value to transmit during
|
||||||
|
read.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
data = spi.read(4) # transmits 0x00 while reading
|
||||||
|
data = spi.read(4, 0xFF) # transmits 0xFF while reading
|
||||||
|
```
|
||||||
|
|
||||||
|
### `SPI#transfer(*data, additional_read_bytes: 0)`
|
||||||
|
|
||||||
|
Full-duplex transfer. Sends data and returns received bytes.
|
||||||
|
Use `additional_read_bytes:` to append zero-filled read bytes.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Send 1 byte command, read 4 bytes response
|
||||||
|
rx = spi.transfer(0x9F, additional_read_bytes: 4)
|
||||||
|
```
|
||||||
|
|
||||||
|
### SPI#select / SPI#deselect
|
||||||
|
|
||||||
|
Manually control the CS pin (when using GPIO-based chip select).
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
spi.select do |s|
|
||||||
|
s.write(0x01)
|
||||||
|
data = s.read(4)
|
||||||
|
end # CS automatically deasserted
|
||||||
|
```
|
||||||
|
|
||||||
|
## HAL Interface
|
||||||
|
|
||||||
|
To add support for a new platform, create a `ports/<name>/`
|
||||||
|
directory and implement the following C functions declared in
|
||||||
|
`<mruby/spi.h>`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int mrb_spi_unit_name_to_num(const char *name);
|
||||||
|
mrb_spi_status mrb_spi_init(mrb_spi_info *info);
|
||||||
|
int mrb_spi_read(mrb_spi_info *info, uint8_t *dst, size_t len,
|
||||||
|
uint8_t tx_val);
|
||||||
|
int mrb_spi_write(mrb_spi_info *info, const uint8_t *src, size_t len);
|
||||||
|
int mrb_spi_transfer(mrb_spi_info *info, const uint8_t *tx,
|
||||||
|
uint8_t *rx, size_t len);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `mrb_spi_info` struct contains all configuration (unit, pins,
|
||||||
|
frequency, mode, bit order) and is passed to every HAL call.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef MRUBY_SPI_H
|
||||||
|
#define MRUBY_SPI_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MRB_SPI_MSB_FIRST 1
|
||||||
|
#define MRB_SPI_LSB_FIRST 0
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MRB_SPI_OK = 0,
|
||||||
|
MRB_SPI_ERROR_UNIT = -1,
|
||||||
|
MRB_SPI_ERROR_MODE = -2,
|
||||||
|
MRB_SPI_ERROR_FIRST_BIT = -3,
|
||||||
|
MRB_SPI_ERROR_INIT = -4,
|
||||||
|
} mrb_spi_status;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t frequency;
|
||||||
|
uint8_t unit_num;
|
||||||
|
int8_t sck_pin;
|
||||||
|
int8_t copi_pin;
|
||||||
|
int8_t cipo_pin;
|
||||||
|
int8_t cs_pin;
|
||||||
|
uint8_t mode;
|
||||||
|
uint8_t first_bit;
|
||||||
|
} mrb_spi_info;
|
||||||
|
|
||||||
|
/* HAL functions - implemented in ports/<platform>/spi.c */
|
||||||
|
int mrb_spi_unit_name_to_num(const char *name);
|
||||||
|
mrb_spi_status mrb_spi_init(mrb_spi_info *info);
|
||||||
|
int mrb_spi_read(mrb_spi_info *info, uint8_t *dst, size_t len, uint8_t tx_val);
|
||||||
|
int mrb_spi_write(mrb_spi_info *info, const uint8_t *src, size_t len);
|
||||||
|
int mrb_spi_transfer(mrb_spi_info *info, const uint8_t *tx, uint8_t *rx, size_t len);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MRUBY_SPI_H */
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
MRuby::Gem::Specification.new('hw-spi') do |spec|
|
||||||
|
spec.license = 'MIT'
|
||||||
|
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
|
||||||
|
spec.summary = 'SPI peripheral interface'
|
||||||
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
class SPI
|
||||||
|
MSB_FIRST = 1
|
||||||
|
LSB_FIRST = 0
|
||||||
|
DEFAULT_FREQUENCY = 100_000
|
||||||
|
|
||||||
|
def select
|
||||||
|
@cs&.write 0
|
||||||
|
if block_given?
|
||||||
|
begin
|
||||||
|
yield self
|
||||||
|
ensure
|
||||||
|
deselect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def deselect
|
||||||
|
@cs&.write 1
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include <mruby/spi.h>
|
||||||
|
|
||||||
|
static spi_device_handle_t handles[SPI_HOST_MAX];
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_spi_unit_name_to_num(const char *name)
|
||||||
|
{
|
||||||
|
if (strcmp(name, "ESP32_SPI2_HOST") == 0) return SPI2_HOST;
|
||||||
|
if (strcmp(name, "ESP32_HSPI_HOST") == 0) return SPI2_HOST;
|
||||||
|
#if (SOC_SPI_PERIPH_NUM == 3)
|
||||||
|
if (strcmp(name, "ESP32_SPI3_HOST") == 0) return SPI3_HOST;
|
||||||
|
if (strcmp(name, "ESP32_VSPI_HOST") == 0) return SPI3_HOST;
|
||||||
|
#endif
|
||||||
|
return MRB_SPI_ERROR_UNIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_spi_status
|
||||||
|
mrb_spi_init(mrb_spi_info *info)
|
||||||
|
{
|
||||||
|
if (handles[info->unit_num] != NULL) return MRB_SPI_OK;
|
||||||
|
|
||||||
|
spi_bus_config_t buscfg = {
|
||||||
|
.mosi_io_num = info->copi_pin,
|
||||||
|
.miso_io_num = info->cipo_pin,
|
||||||
|
.sclk_io_num = info->sck_pin,
|
||||||
|
.quadwp_io_num = -1,
|
||||||
|
.quadhd_io_num = -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t err = spi_bus_initialize(info->unit_num, &buscfg, SPI_DMA_CH_AUTO);
|
||||||
|
if (err != ESP_OK) return MRB_SPI_ERROR_INIT;
|
||||||
|
|
||||||
|
spi_device_interface_config_t devcfg = {
|
||||||
|
.clock_speed_hz = info->frequency,
|
||||||
|
.mode = info->mode,
|
||||||
|
.spics_io_num = info->cs_pin,
|
||||||
|
.queue_size = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
err = spi_bus_add_device(info->unit_num, &devcfg, &handles[info->unit_num]);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
spi_bus_free(info->unit_num);
|
||||||
|
handles[info->unit_num] = NULL;
|
||||||
|
return MRB_SPI_ERROR_INIT;
|
||||||
|
}
|
||||||
|
return MRB_SPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_spi_read(mrb_spi_info *info, uint8_t *dst, size_t len, uint8_t tx_val)
|
||||||
|
{
|
||||||
|
spi_transaction_t t = {
|
||||||
|
.length = len * 8,
|
||||||
|
.tx_buffer = NULL,
|
||||||
|
.rx_buffer = dst,
|
||||||
|
};
|
||||||
|
esp_err_t err = spi_device_polling_transmit(handles[info->unit_num], &t);
|
||||||
|
return (err == ESP_OK) ? (int)len : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_spi_write(mrb_spi_info *info, const uint8_t *src, size_t len)
|
||||||
|
{
|
||||||
|
spi_transaction_t t = {
|
||||||
|
.length = len * 8,
|
||||||
|
.tx_buffer = src,
|
||||||
|
.rx_buffer = NULL,
|
||||||
|
};
|
||||||
|
esp_err_t err = spi_device_polling_transmit(handles[info->unit_num], &t);
|
||||||
|
return (err == ESP_OK) ? (int)len : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_spi_transfer(mrb_spi_info *info, const uint8_t *tx, uint8_t *rx, size_t len)
|
||||||
|
{
|
||||||
|
spi_transaction_t t = {
|
||||||
|
.length = len * 8,
|
||||||
|
.tx_buffer = tx,
|
||||||
|
.rx_buffer = rx,
|
||||||
|
};
|
||||||
|
esp_err_t err = spi_device_polling_transmit(handles[info->unit_num], &t);
|
||||||
|
return (err == ESP_OK) ? (int)len : -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/spi.h"
|
||||||
|
#include <mruby/spi.h>
|
||||||
|
|
||||||
|
#define UNIT_SELECT(info) \
|
||||||
|
spi_inst_t *inst; \
|
||||||
|
switch ((info)->unit_num) { \
|
||||||
|
case 0: inst = spi0; break; \
|
||||||
|
case 1: inst = spi1; break; \
|
||||||
|
default: return MRB_SPI_ERROR_UNIT; \
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_spi_unit_name_to_num(const char *name)
|
||||||
|
{
|
||||||
|
if (strcmp(name, "RP2040_SPI0") == 0) return 0;
|
||||||
|
if (strcmp(name, "RP2040_SPI1") == 0) return 1;
|
||||||
|
return MRB_SPI_ERROR_UNIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_spi_status
|
||||||
|
mrb_spi_init(mrb_spi_info *info)
|
||||||
|
{
|
||||||
|
UNIT_SELECT(info);
|
||||||
|
spi_init(inst, info->frequency);
|
||||||
|
|
||||||
|
if (info->sck_pin < 0) info->sck_pin = PICO_DEFAULT_SPI_SCK_PIN;
|
||||||
|
if (info->cipo_pin < 0) info->cipo_pin = PICO_DEFAULT_SPI_RX_PIN;
|
||||||
|
if (info->copi_pin < 0) info->copi_pin = PICO_DEFAULT_SPI_TX_PIN;
|
||||||
|
|
||||||
|
gpio_set_function(info->sck_pin, GPIO_FUNC_SPI);
|
||||||
|
gpio_set_function(info->cipo_pin, GPIO_FUNC_SPI);
|
||||||
|
gpio_set_function(info->copi_pin, GPIO_FUNC_SPI);
|
||||||
|
|
||||||
|
if (info->first_bit != MRB_SPI_MSB_FIRST) {
|
||||||
|
return MRB_SPI_ERROR_FIRST_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_cpol_t cpol;
|
||||||
|
spi_cpha_t cpha;
|
||||||
|
switch (info->mode) {
|
||||||
|
case 0: cpol = 0; cpha = 0; break;
|
||||||
|
case 1: cpol = 0; cpha = 1; break;
|
||||||
|
case 2: cpol = 1; cpha = 0; break;
|
||||||
|
case 3: cpol = 1; cpha = 1; break;
|
||||||
|
default: return MRB_SPI_ERROR_MODE;
|
||||||
|
}
|
||||||
|
spi_set_format(inst, 8, cpol, cpha, info->first_bit);
|
||||||
|
|
||||||
|
return MRB_SPI_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_spi_read(mrb_spi_info *info, uint8_t *dst, size_t len, uint8_t tx_val)
|
||||||
|
{
|
||||||
|
UNIT_SELECT(info);
|
||||||
|
return spi_read_blocking(inst, tx_val, dst, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_spi_write(mrb_spi_info *info, const uint8_t *src, size_t len)
|
||||||
|
{
|
||||||
|
UNIT_SELECT(info);
|
||||||
|
return spi_write_blocking(inst, src, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_spi_transfer(mrb_spi_info *info, const uint8_t *tx, uint8_t *rx, size_t len)
|
||||||
|
{
|
||||||
|
UNIT_SELECT(info);
|
||||||
|
return spi_write_read_blocking(inst, tx, rx, len);
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <mruby.h>
|
||||||
|
#include <mruby/presym.h>
|
||||||
|
#include <mruby/variable.h>
|
||||||
|
#include <mruby/string.h>
|
||||||
|
#include <mruby/array.h>
|
||||||
|
#include <mruby/data.h>
|
||||||
|
#include <mruby/class.h>
|
||||||
|
#include <mruby/spi.h>
|
||||||
|
|
||||||
|
#define STACK_BUF_SIZE 256
|
||||||
|
#define E_IO_ERROR mrb_exc_get_id(mrb, MRB_SYM(IOError))
|
||||||
|
|
||||||
|
static void
|
||||||
|
spi_info_free(mrb_state *mrb, void *ptr)
|
||||||
|
{
|
||||||
|
mrb_free(mrb, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct mrb_data_type spi_info_type = { "SPI", spi_info_free };
|
||||||
|
|
||||||
|
#define SPI_INFO(self) \
|
||||||
|
((mrb_spi_info*)mrb_data_get_ptr(mrb, self, &spi_info_type))
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
spi_calc_size(mrb_state *mrb, mrb_value *args, mrb_int argc)
|
||||||
|
{
|
||||||
|
size_t total = 0;
|
||||||
|
for (mrb_int i = 0; i < argc; i++) {
|
||||||
|
switch (mrb_type(args[i])) {
|
||||||
|
case MRB_TT_ARRAY:
|
||||||
|
total += RARRAY_LEN(args[i]);
|
||||||
|
break;
|
||||||
|
case MRB_TT_INTEGER:
|
||||||
|
total += 1;
|
||||||
|
break;
|
||||||
|
case MRB_TT_STRING:
|
||||||
|
total += RSTRING_LEN(args[i]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mrb_raise(mrb, E_TYPE_ERROR, "Integer, Array, or String expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
spi_fill_buf(mrb_state *mrb, uint8_t *buf, mrb_value *args, mrb_int argc,
|
||||||
|
size_t pad)
|
||||||
|
{
|
||||||
|
size_t pos = 0;
|
||||||
|
for (mrb_int i = 0; i < argc; i++) {
|
||||||
|
switch (mrb_type(args[i])) {
|
||||||
|
case MRB_TT_ARRAY: {
|
||||||
|
mrb_int alen = RARRAY_LEN(args[i]);
|
||||||
|
const mrb_value *aptr = RARRAY_PTR(args[i]);
|
||||||
|
for (mrb_int j = 0; j < alen; j++) {
|
||||||
|
if (!mrb_integer_p(aptr[j])) {
|
||||||
|
mrb_raise(mrb, E_TYPE_ERROR, "array element must be Integer");
|
||||||
|
}
|
||||||
|
buf[pos++] = (uint8_t)mrb_integer(aptr[j]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MRB_TT_INTEGER:
|
||||||
|
buf[pos++] = (uint8_t)mrb_integer(args[i]);
|
||||||
|
break;
|
||||||
|
case MRB_TT_STRING:
|
||||||
|
memcpy(&buf[pos], RSTRING_PTR(args[i]), RSTRING_LEN(args[i]));
|
||||||
|
pos += RSTRING_LEN(args[i]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memset(&buf[pos], 0, pad);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SPI.new(unit:, frequency:, sck_pin:, cipo_pin:, copi_pin:,
|
||||||
|
cs_pin:, mode:, first_bit:) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_spi_s_new(mrb_state *mrb, mrb_value klass)
|
||||||
|
{
|
||||||
|
const char *unit_name;
|
||||||
|
mrb_int freq = 100000, sck = -1, cipo = -1, copi = -1, cs = -1;
|
||||||
|
mrb_int mode = 0, first_bit = MRB_SPI_MSB_FIRST;
|
||||||
|
|
||||||
|
const mrb_sym kw_names[] = {
|
||||||
|
MRB_SYM(unit), MRB_SYM(frequency), MRB_SYM(sck_pin),
|
||||||
|
MRB_SYM(cipo_pin), MRB_SYM(copi_pin), MRB_SYM(cs_pin),
|
||||||
|
MRB_SYM(mode), MRB_SYM(first_bit)
|
||||||
|
};
|
||||||
|
mrb_value kw_values[8];
|
||||||
|
mrb_kwargs kwargs = { 8, 7, kw_names, kw_values, NULL };
|
||||||
|
mrb_get_args(mrb, ":", &kwargs);
|
||||||
|
|
||||||
|
/* unit is required */
|
||||||
|
if (mrb_undef_p(kw_values[0])) {
|
||||||
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "unit: is required");
|
||||||
|
}
|
||||||
|
unit_name = mrb_str_to_cstr(mrb, mrb_sym_str(mrb, mrb_symbol(kw_values[0])));
|
||||||
|
|
||||||
|
if (!mrb_undef_p(kw_values[1])) freq = mrb_integer(kw_values[1]);
|
||||||
|
if (!mrb_undef_p(kw_values[2])) sck = mrb_integer(kw_values[2]);
|
||||||
|
if (!mrb_undef_p(kw_values[3])) cipo = mrb_integer(kw_values[3]);
|
||||||
|
if (!mrb_undef_p(kw_values[4])) copi = mrb_integer(kw_values[4]);
|
||||||
|
if (!mrb_undef_p(kw_values[5])) cs = mrb_integer(kw_values[5]);
|
||||||
|
if (!mrb_undef_p(kw_values[6])) mode = mrb_integer(kw_values[6]);
|
||||||
|
if (!mrb_undef_p(kw_values[7])) first_bit = mrb_integer(kw_values[7]);
|
||||||
|
|
||||||
|
int num = mrb_spi_unit_name_to_num(unit_name);
|
||||||
|
if (num < 0) {
|
||||||
|
mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown SPI unit: %s", unit_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_spi_info *info = (mrb_spi_info*)mrb_malloc(mrb, sizeof(mrb_spi_info));
|
||||||
|
info->unit_num = (uint8_t)num;
|
||||||
|
info->frequency = (uint32_t)freq;
|
||||||
|
info->sck_pin = (int8_t)sck;
|
||||||
|
info->cipo_pin = (int8_t)cipo;
|
||||||
|
info->copi_pin = (int8_t)copi;
|
||||||
|
info->cs_pin = (int8_t)cs;
|
||||||
|
info->mode = (uint8_t)mode;
|
||||||
|
info->first_bit = (uint8_t)first_bit;
|
||||||
|
|
||||||
|
mrb_value self = mrb_obj_value(
|
||||||
|
Data_Wrap_Struct(mrb, mrb_class_ptr(klass), &spi_info_type, info));
|
||||||
|
|
||||||
|
mrb_spi_status st = mrb_spi_init(info);
|
||||||
|
if (st != MRB_SPI_OK) {
|
||||||
|
mrb_raise(mrb, E_IO_ERROR, "SPI init failed");
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SPI#write(*data) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_spi_m_write(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_value *args;
|
||||||
|
mrb_int argc;
|
||||||
|
mrb_get_args(mrb, "*", &args, &argc);
|
||||||
|
|
||||||
|
mrb_spi_info *info = SPI_INFO(self);
|
||||||
|
size_t total = spi_calc_size(mrb, args, argc);
|
||||||
|
if (total == 0) return mrb_fixnum_value(0);
|
||||||
|
|
||||||
|
uint8_t sbuf[STACK_BUF_SIZE];
|
||||||
|
uint8_t *buf = sbuf;
|
||||||
|
mrb_bool need_free = FALSE;
|
||||||
|
if (total > STACK_BUF_SIZE) {
|
||||||
|
buf = (uint8_t*)mrb_malloc(mrb, total);
|
||||||
|
need_free = TRUE;
|
||||||
|
}
|
||||||
|
spi_fill_buf(mrb, buf, args, argc, 0);
|
||||||
|
|
||||||
|
int ret = mrb_spi_write(info, buf, total);
|
||||||
|
if (need_free) mrb_free(mrb, buf);
|
||||||
|
if (ret < 0) mrb_raise(mrb, E_IO_ERROR, "SPI write failed");
|
||||||
|
return mrb_fixnum_value(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SPI#read(len, tx_value=0) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_spi_m_read(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int len, tx_val = 0;
|
||||||
|
mrb_get_args(mrb, "i|i", &len, &tx_val);
|
||||||
|
if (len <= 0) mrb_raise(mrb, E_ARGUMENT_ERROR, "length must be positive");
|
||||||
|
|
||||||
|
mrb_spi_info *info = SPI_INFO(self);
|
||||||
|
uint8_t *buf = (uint8_t*)mrb_malloc(mrb, len);
|
||||||
|
int ret = mrb_spi_read(info, buf, (size_t)len, (uint8_t)tx_val);
|
||||||
|
if (ret < 0) {
|
||||||
|
mrb_free(mrb, buf);
|
||||||
|
mrb_raise(mrb, E_IO_ERROR, "SPI read failed");
|
||||||
|
}
|
||||||
|
mrb_value str = mrb_str_new(mrb, (const char*)buf, ret);
|
||||||
|
mrb_free(mrb, buf);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SPI#transfer(*data, additional_read_bytes: 0) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_spi_m_transfer(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_value *args;
|
||||||
|
mrb_int argc, extra = 0;
|
||||||
|
const mrb_sym kw_names[] = { MRB_SYM(additional_read_bytes) };
|
||||||
|
mrb_value kw_values[1];
|
||||||
|
mrb_kwargs kwargs = { 1, 0, kw_names, kw_values, NULL };
|
||||||
|
mrb_get_args(mrb, "*:", &args, &argc, &kwargs);
|
||||||
|
|
||||||
|
if (!mrb_undef_p(kw_values[0])) extra = mrb_integer(kw_values[0]);
|
||||||
|
|
||||||
|
mrb_spi_info *info = SPI_INFO(self);
|
||||||
|
size_t total = spi_calc_size(mrb, args, argc) + (size_t)extra;
|
||||||
|
if (total == 0) return mrb_str_new(mrb, "", 0);
|
||||||
|
|
||||||
|
uint8_t sbuf_tx[STACK_BUF_SIZE], sbuf_rx[STACK_BUF_SIZE];
|
||||||
|
uint8_t *tx = sbuf_tx, *rx = sbuf_rx;
|
||||||
|
mrb_bool need_free = FALSE;
|
||||||
|
if (total > STACK_BUF_SIZE) {
|
||||||
|
tx = (uint8_t*)mrb_malloc(mrb, total * 2);
|
||||||
|
rx = tx + total;
|
||||||
|
need_free = TRUE;
|
||||||
|
}
|
||||||
|
spi_fill_buf(mrb, tx, args, argc, (size_t)extra);
|
||||||
|
|
||||||
|
int ret = mrb_spi_transfer(info, tx, rx, total);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (need_free) mrb_free(mrb, tx);
|
||||||
|
mrb_raise(mrb, E_IO_ERROR, "SPI transfer failed");
|
||||||
|
}
|
||||||
|
mrb_value str = mrb_str_new(mrb, (const char*)rx, ret);
|
||||||
|
if (need_free) mrb_free(mrb, tx);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_spi_gem_init(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(SPI), mrb->object_class);
|
||||||
|
MRB_SET_INSTANCE_TT(cls, MRB_TT_CDATA);
|
||||||
|
|
||||||
|
mrb_define_class_method_id(mrb, cls, MRB_SYM(new), mrb_spi_s_new, MRB_ARGS_KEY(8, 1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_spi_m_write, MRB_ARGS_REST());
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_spi_m_read, MRB_ARGS_ARG(1, 1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(transfer), mrb_spi_m_transfer, MRB_ARGS_REST()|MRB_ARGS_KEY(1, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_spi_gem_final(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
# hw-uart - UART peripheral interface for mruby
|
||||||
|
|
||||||
|
This gem provides the `UART` class for serial communication from mruby.
|
||||||
|
It is designed for embedded platforms such as ESP32 and RP2040.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Platform-specific HAL implementations are in `ports/` directories:
|
||||||
|
|
||||||
|
- `ports/esp32/` - ESP32 using ESP-IDF UART driver with FreeRTOS
|
||||||
|
task for RX
|
||||||
|
- `ports/rp2040/` - RP2040 using Pico SDK with IRQ-driven RX
|
||||||
|
|
||||||
|
Received data is buffered in a ring buffer (allocated by the common
|
||||||
|
gem) that the platform HAL populates via interrupt or task. The ring
|
||||||
|
buffer size must be a power of two.
|
||||||
|
|
||||||
|
## Build Configuration
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# For ESP32
|
||||||
|
MRuby::CrossBuild.new('esp32') do |conf|
|
||||||
|
conf.ports :esp32
|
||||||
|
conf.gem core: 'hw-uart'
|
||||||
|
end
|
||||||
|
|
||||||
|
# For RP2040
|
||||||
|
MRuby::CrossBuild.new('rp2040') do |conf|
|
||||||
|
conf.ports :rp2040
|
||||||
|
conf.gem core: 'hw-uart'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ruby API
|
||||||
|
|
||||||
|
### Constants
|
||||||
|
|
||||||
|
| Constant | Value | Description |
|
||||||
|
| ---------------------------- | ----- | ------------------ |
|
||||||
|
| `UART::PARITY_NONE` | `0` | No parity |
|
||||||
|
| `UART::PARITY_EVEN` | `1` | Even parity |
|
||||||
|
| `UART::PARITY_ODD` | `2` | Odd parity |
|
||||||
|
| `UART::FLOW_CONTROL_NONE` | `0` | No flow control |
|
||||||
|
| `UART::FLOW_CONTROL_RTS_CTS` | `1` | Hardware flow ctrl |
|
||||||
|
|
||||||
|
### UART.new
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
uart = UART.new(
|
||||||
|
unit: :ESP32_UART1, # UART unit name (required)
|
||||||
|
tx_pin: 17, # TX GPIO pin (default: -1)
|
||||||
|
rx_pin: 16, # RX GPIO pin (default: -1)
|
||||||
|
baudrate: 9600, # baud rate (default: 9600)
|
||||||
|
data_bits: 8, # 5-8 (default: 8)
|
||||||
|
stop_bits: 1, # 1-2 (default: 1)
|
||||||
|
parity: UART::PARITY_NONE,
|
||||||
|
flow_control: UART::FLOW_CONTROL_NONE,
|
||||||
|
rx_buffer_size: 256 # must be power of two (default: 256)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Unit Names
|
||||||
|
|
||||||
|
| Platform | Available Units |
|
||||||
|
| -------- | ------------------------------------------------ |
|
||||||
|
| ESP32 | `:ESP32_UART0`, `:ESP32_UART1`, `:ESP32_UART2`\* |
|
||||||
|
| RP2040 | `:RP2040_UART0`, `:RP2040_UART1` |
|
||||||
|
|
||||||
|
\*UART2 availability depends on ESP32 variant.
|
||||||
|
|
||||||
|
### Instance Methods
|
||||||
|
|
||||||
|
#### UART#write(str)
|
||||||
|
|
||||||
|
Write a string to the UART. Returns number of bytes written.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
uart.write("Hello\r\n")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### UART#read(len = nil)
|
||||||
|
|
||||||
|
Read from the RX buffer. Returns `nil` if no data is available.
|
||||||
|
|
||||||
|
- Without argument: returns all available data
|
||||||
|
- With `len`: returns exactly `len` bytes, or `nil` if fewer are
|
||||||
|
available
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
data = uart.read # all available
|
||||||
|
data = uart.read(10) # exactly 10 bytes or nil
|
||||||
|
```
|
||||||
|
|
||||||
|
#### UART#readpartial(maxlen)
|
||||||
|
|
||||||
|
Read up to `maxlen` bytes from the RX buffer. Returns `nil` if empty.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
data = uart.readpartial(64)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### UART#gets
|
||||||
|
|
||||||
|
Read a line (up to and including `"\n"`). Returns `nil` if no
|
||||||
|
complete line is available.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
line = uart.gets
|
||||||
|
```
|
||||||
|
|
||||||
|
#### UART#bytes_available
|
||||||
|
|
||||||
|
Returns the number of bytes in the RX buffer.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
n = uart.bytes_available
|
||||||
|
```
|
||||||
|
|
||||||
|
#### UART#puts(str)
|
||||||
|
|
||||||
|
Write string with line ending appended (if not already present).
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
uart.puts("Hello") # writes "Hello\n"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### UART#flush
|
||||||
|
|
||||||
|
Wait for all TX data to be sent.
|
||||||
|
|
||||||
|
#### UART#clear_rx_buffer / UART#clear_tx_buffer
|
||||||
|
|
||||||
|
Discard buffered data.
|
||||||
|
|
||||||
|
#### UART#send_break(duration_ms = 100)
|
||||||
|
|
||||||
|
Send a UART break signal for the specified duration.
|
||||||
|
|
||||||
|
#### UART#setmode(baudrate:, data_bits:, stop_bits:, parity:, flow_control:)
|
||||||
|
|
||||||
|
Reconfigure UART parameters after initialization. All parameters are
|
||||||
|
optional.
|
||||||
|
|
||||||
|
#### UART#baudrate
|
||||||
|
|
||||||
|
Returns the current baud rate.
|
||||||
|
|
||||||
|
#### UART#line_ending=(ending)
|
||||||
|
|
||||||
|
Set the line ending used by `puts`. Must be `"\n"`, `"\r"`, or
|
||||||
|
`"\r\n"`.
|
||||||
|
|
||||||
|
## HAL Interface
|
||||||
|
|
||||||
|
To add support for a new platform, create a `ports/<name>/`
|
||||||
|
directory and implement the following C functions declared in
|
||||||
|
`<mruby/uart.h>`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int mrb_uart_unit_name_to_num(const char *name);
|
||||||
|
mrb_uart_status mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin,
|
||||||
|
mrb_uart_ringbuf *rxbuf);
|
||||||
|
uint32_t mrb_uart_set_baudrate(int unit, uint32_t baudrate);
|
||||||
|
void mrb_uart_set_format(int unit, uint32_t data_bits,
|
||||||
|
uint32_t stop_bits, uint8_t parity);
|
||||||
|
void mrb_uart_set_flow_control(int unit, bool cts, bool rts);
|
||||||
|
void mrb_uart_write(int unit, const uint8_t *src, size_t len);
|
||||||
|
void mrb_uart_flush(int unit);
|
||||||
|
void mrb_uart_send_break(int unit, uint32_t duration_ms);
|
||||||
|
void mrb_uart_clear_rx(int unit);
|
||||||
|
void mrb_uart_clear_tx(int unit);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `rxbuf` parameter passed to `mrb_uart_init` is a ring buffer
|
||||||
|
allocated by the common gem. The platform must arrange for received
|
||||||
|
bytes to be pushed into it using `mrb_uart_ringbuf_push()` (e.g.,
|
||||||
|
from an interrupt handler or RTOS task).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#ifndef MRUBY_UART_H
|
||||||
|
#define MRUBY_UART_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MRB_UART_PARITY_NONE 0
|
||||||
|
#define MRB_UART_PARITY_EVEN 1
|
||||||
|
#define MRB_UART_PARITY_ODD 2
|
||||||
|
|
||||||
|
#define MRB_UART_FLOW_NONE 0
|
||||||
|
#define MRB_UART_FLOW_RTS_CTS 1
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MRB_UART_OK = 0,
|
||||||
|
MRB_UART_ERROR_UNIT = -1,
|
||||||
|
} mrb_uart_status;
|
||||||
|
|
||||||
|
/* Ring buffer for interrupt-driven RX.
|
||||||
|
Allocated by common gem, populated by platform interrupt handler.
|
||||||
|
size must be a power of two. */
|
||||||
|
typedef struct {
|
||||||
|
volatile int head;
|
||||||
|
volatile int tail;
|
||||||
|
int mask;
|
||||||
|
uint8_t data[];
|
||||||
|
} mrb_uart_ringbuf;
|
||||||
|
|
||||||
|
/* Ring buffer helpers (implemented in hw-uart/src/ringbuf.c) */
|
||||||
|
bool mrb_uart_ringbuf_init(mrb_uart_ringbuf *rb, int size);
|
||||||
|
bool mrb_uart_ringbuf_push(mrb_uart_ringbuf *rb, uint8_t ch);
|
||||||
|
int mrb_uart_ringbuf_pop(mrb_uart_ringbuf *rb, uint8_t *dst, int len);
|
||||||
|
int mrb_uart_ringbuf_available(const mrb_uart_ringbuf *rb);
|
||||||
|
void mrb_uart_ringbuf_clear(mrb_uart_ringbuf *rb);
|
||||||
|
int mrb_uart_ringbuf_search(const mrb_uart_ringbuf *rb, uint8_t ch);
|
||||||
|
|
||||||
|
/* HAL functions - implemented by hw-<platform>-uart gems */
|
||||||
|
int mrb_uart_unit_name_to_num(const char *name);
|
||||||
|
mrb_uart_status mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin,
|
||||||
|
mrb_uart_ringbuf *rxbuf);
|
||||||
|
uint32_t mrb_uart_set_baudrate(int unit, uint32_t baudrate);
|
||||||
|
void mrb_uart_set_format(int unit, uint32_t data_bits, uint32_t stop_bits,
|
||||||
|
uint8_t parity);
|
||||||
|
void mrb_uart_set_flow_control(int unit, bool cts, bool rts);
|
||||||
|
void mrb_uart_write(int unit, const uint8_t *src, size_t len);
|
||||||
|
void mrb_uart_flush(int unit);
|
||||||
|
void mrb_uart_send_break(int unit, uint32_t duration_ms);
|
||||||
|
void mrb_uart_clear_rx(int unit);
|
||||||
|
void mrb_uart_clear_tx(int unit);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MRUBY_UART_H */
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
MRuby::Gem::Specification.new('hw-uart') do |spec|
|
||||||
|
spec.license = 'MIT'
|
||||||
|
spec.authors = ['HASUMI Hitoshi', 'mruby developers']
|
||||||
|
spec.summary = 'UART peripheral interface'
|
||||||
|
end
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
class UART
|
||||||
|
PARITY_NONE = 0
|
||||||
|
PARITY_EVEN = 1
|
||||||
|
PARITY_ODD = 2
|
||||||
|
FLOW_CONTROL_NONE = 0
|
||||||
|
FLOW_CONTROL_RTS_CTS = 1
|
||||||
|
|
||||||
|
attr_reader :baudrate
|
||||||
|
|
||||||
|
def initialize(unit:, tx_pin: -1, rx_pin: -1, baudrate: 9600,
|
||||||
|
data_bits: 8, stop_bits: 1, parity: PARITY_NONE,
|
||||||
|
flow_control: FLOW_CONTROL_NONE, rx_buffer_size: 256)
|
||||||
|
__open_rx_buffer(rx_buffer_size)
|
||||||
|
@unit_num = __open_connection(unit.to_s, tx_pin, rx_pin)
|
||||||
|
@baudrate = __set_baudrate(baudrate)
|
||||||
|
__set_format(data_bits, stop_bits, parity)
|
||||||
|
set_flow_control(flow_control)
|
||||||
|
@line_ending = "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def setmode(baudrate: nil, data_bits: nil, stop_bits: nil,
|
||||||
|
parity: nil, flow_control: nil)
|
||||||
|
@baudrate = __set_baudrate(baudrate) if baudrate
|
||||||
|
__set_format(data_bits || 8, stop_bits || 1, parity || PARITY_NONE)
|
||||||
|
set_flow_control(flow_control || FLOW_CONTROL_NONE)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def line_ending=(ending)
|
||||||
|
unless ["\n", "\r", "\r\n"].include?(ending)
|
||||||
|
raise ArgumentError, "invalid line ending"
|
||||||
|
end
|
||||||
|
@line_ending = ending
|
||||||
|
end
|
||||||
|
|
||||||
|
def puts(str)
|
||||||
|
write str
|
||||||
|
write @line_ending unless str.end_with?(@line_ending)
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_flow_control(mode)
|
||||||
|
case mode
|
||||||
|
when FLOW_CONTROL_NONE
|
||||||
|
__set_flow_control(false, false)
|
||||||
|
when FLOW_CONTROL_RTS_CTS
|
||||||
|
__set_flow_control(true, true)
|
||||||
|
else
|
||||||
|
raise ArgumentError, "invalid flow control mode"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "driver/uart.h"
|
||||||
|
#include <mruby/uart.h>
|
||||||
|
|
||||||
|
#define RX_TASK_BUF_SIZE 128
|
||||||
|
#define QUEUE_LENGTH 20
|
||||||
|
#define TASK_STACK_SIZE 4096
|
||||||
|
#define TASK_PRIORITY 12
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int unit;
|
||||||
|
QueueHandle_t queue;
|
||||||
|
mrb_uart_ringbuf *rxbuf;
|
||||||
|
} uart_ctx;
|
||||||
|
|
||||||
|
static uart_ctx ctx[UART_NUM_MAX];
|
||||||
|
|
||||||
|
static void
|
||||||
|
rx_task(void *arg)
|
||||||
|
{
|
||||||
|
uart_ctx *c = (uart_ctx*)arg;
|
||||||
|
uart_event_t event;
|
||||||
|
uint8_t buf[RX_TASK_BUF_SIZE];
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (xQueueReceive(c->queue, &event, portMAX_DELAY)) {
|
||||||
|
if (event.type == UART_DATA) {
|
||||||
|
size_t n = event.size > RX_TASK_BUF_SIZE ? RX_TASK_BUF_SIZE : event.size;
|
||||||
|
uart_read_bytes(c->unit, buf, n, portMAX_DELAY);
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
mrb_uart_ringbuf_push(c->rxbuf, buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_uart_unit_name_to_num(const char *name)
|
||||||
|
{
|
||||||
|
if (strcmp(name, "ESP32_UART0") == 0) return UART_NUM_0;
|
||||||
|
if (strcmp(name, "ESP32_UART1") == 0) return UART_NUM_1;
|
||||||
|
#ifdef UART_NUM_2
|
||||||
|
if (strcmp(name, "ESP32_UART2") == 0) return UART_NUM_2;
|
||||||
|
#endif
|
||||||
|
return MRB_UART_ERROR_UNIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_uart_status
|
||||||
|
mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin,
|
||||||
|
mrb_uart_ringbuf *rxbuf)
|
||||||
|
{
|
||||||
|
if (unit < 0 || unit >= UART_NUM_MAX) return MRB_UART_ERROR_UNIT;
|
||||||
|
|
||||||
|
uart_config_t cfg = {
|
||||||
|
.baud_rate = 9600,
|
||||||
|
.data_bits = UART_DATA_8_BITS,
|
||||||
|
.parity = UART_PARITY_DISABLE,
|
||||||
|
.stop_bits = UART_STOP_BITS_1,
|
||||||
|
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||||
|
.source_clk = UART_SCLK_DEFAULT,
|
||||||
|
};
|
||||||
|
|
||||||
|
int bufsize = (rxbuf->mask + 1);
|
||||||
|
uart_driver_install(unit, bufsize, 0, QUEUE_LENGTH, &ctx[unit].queue, 0);
|
||||||
|
uart_param_config(unit, &cfg);
|
||||||
|
uart_set_pin(unit, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||||
|
|
||||||
|
ctx[unit].unit = unit;
|
||||||
|
ctx[unit].rxbuf = rxbuf;
|
||||||
|
|
||||||
|
char name[32];
|
||||||
|
snprintf(name, sizeof(name), "uart_rx_%d", unit);
|
||||||
|
xTaskCreate(rx_task, name, TASK_STACK_SIZE, &ctx[unit], TASK_PRIORITY, NULL);
|
||||||
|
|
||||||
|
return MRB_UART_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
mrb_uart_set_baudrate(int unit, uint32_t baudrate)
|
||||||
|
{
|
||||||
|
uart_set_baudrate(unit, baudrate);
|
||||||
|
return baudrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_set_format(int unit, uint32_t data_bits, uint32_t stop_bits,
|
||||||
|
uint8_t parity)
|
||||||
|
{
|
||||||
|
static const uart_word_length_t wl[] = {
|
||||||
|
UART_DATA_5_BITS, UART_DATA_6_BITS, UART_DATA_7_BITS, UART_DATA_8_BITS
|
||||||
|
};
|
||||||
|
static const uart_stop_bits_t sb[] = {
|
||||||
|
UART_STOP_BITS_1, UART_STOP_BITS_2
|
||||||
|
};
|
||||||
|
static const uart_parity_t pr[] = {
|
||||||
|
UART_PARITY_DISABLE, UART_PARITY_EVEN, UART_PARITY_ODD
|
||||||
|
};
|
||||||
|
if (data_bits >= 5 && data_bits <= 8)
|
||||||
|
uart_set_word_length(unit, wl[data_bits - 5]);
|
||||||
|
if (stop_bits >= 1 && stop_bits <= 2)
|
||||||
|
uart_set_stop_bits(unit, sb[stop_bits - 1]);
|
||||||
|
if (parity <= 2)
|
||||||
|
uart_set_parity(unit, pr[parity]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_set_flow_control(int unit, bool cts, bool rts)
|
||||||
|
{
|
||||||
|
uart_hw_flowcontrol_t mode = UART_HW_FLOWCTRL_DISABLE;
|
||||||
|
if (cts && rts) mode = UART_HW_FLOWCTRL_CTS_RTS;
|
||||||
|
else if (cts) mode = UART_HW_FLOWCTRL_CTS;
|
||||||
|
else if (rts) mode = UART_HW_FLOWCTRL_RTS;
|
||||||
|
uart_set_hw_flow_ctrl(unit, mode, 122);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_write(int unit, const uint8_t *src, size_t len)
|
||||||
|
{
|
||||||
|
uart_write_bytes(unit, (const char*)src, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_flush(int unit)
|
||||||
|
{
|
||||||
|
uart_wait_tx_done(unit, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_send_break(int unit, uint32_t duration_ms)
|
||||||
|
{
|
||||||
|
uart_write_bytes_with_break(unit, NULL, 0, duration_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_clear_rx(int unit)
|
||||||
|
{
|
||||||
|
uart_flush_input(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_clear_tx(int unit)
|
||||||
|
{
|
||||||
|
/* not supported on ESP-IDF */
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/uart.h"
|
||||||
|
#include "hardware/irq.h"
|
||||||
|
#include <mruby/uart.h>
|
||||||
|
|
||||||
|
#define UNIT_SELECT(u) \
|
||||||
|
uart_inst_t *inst; \
|
||||||
|
switch (u) { \
|
||||||
|
case 0: inst = uart0; break; \
|
||||||
|
case 1: inst = uart1; break; \
|
||||||
|
default: return MRB_UART_ERROR_UNIT; \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* void-returning variant for functions that can't return error */
|
||||||
|
#define UNIT_SELECT_V(u) \
|
||||||
|
uart_inst_t *inst; \
|
||||||
|
switch (u) { \
|
||||||
|
case 0: inst = uart0; break; \
|
||||||
|
case 1: inst = uart1; break; \
|
||||||
|
default: return; \
|
||||||
|
}
|
||||||
|
|
||||||
|
static mrb_uart_ringbuf *rx_bufs[2];
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_uart0_rx(void)
|
||||||
|
{
|
||||||
|
while (uart_is_readable(uart0)) {
|
||||||
|
mrb_uart_ringbuf_push(rx_bufs[0], uart_getc(uart0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_uart1_rx(void)
|
||||||
|
{
|
||||||
|
while (uart_is_readable(uart1)) {
|
||||||
|
mrb_uart_ringbuf_push(rx_bufs[1], uart_getc(uart1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_uart_unit_name_to_num(const char *name)
|
||||||
|
{
|
||||||
|
if (strcmp(name, "RP2040_UART0") == 0) return 0;
|
||||||
|
if (strcmp(name, "RP2040_UART1") == 0) return 1;
|
||||||
|
return MRB_UART_ERROR_UNIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_uart_status
|
||||||
|
mrb_uart_init(int unit, uint32_t tx_pin, uint32_t rx_pin,
|
||||||
|
mrb_uart_ringbuf *rxbuf)
|
||||||
|
{
|
||||||
|
UNIT_SELECT(unit);
|
||||||
|
uart_init(inst, 9600);
|
||||||
|
|
||||||
|
gpio_set_function(tx_pin, GPIO_FUNC_UART);
|
||||||
|
gpio_set_function(rx_pin, GPIO_FUNC_UART);
|
||||||
|
|
||||||
|
rx_bufs[unit] = rxbuf;
|
||||||
|
|
||||||
|
uint irq;
|
||||||
|
if (unit == 0) {
|
||||||
|
irq = UART0_IRQ;
|
||||||
|
irq_set_exclusive_handler(irq, on_uart0_rx);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
irq = UART1_IRQ;
|
||||||
|
irq_set_exclusive_handler(irq, on_uart1_rx);
|
||||||
|
}
|
||||||
|
irq_set_enabled(irq, true);
|
||||||
|
uart_set_irq_enables(inst, true, false);
|
||||||
|
|
||||||
|
return MRB_UART_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
mrb_uart_set_baudrate(int unit, uint32_t baudrate)
|
||||||
|
{
|
||||||
|
UNIT_SELECT(unit);
|
||||||
|
return uart_set_baudrate(inst, baudrate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_set_format(int unit, uint32_t data_bits, uint32_t stop_bits,
|
||||||
|
uint8_t parity)
|
||||||
|
{
|
||||||
|
UNIT_SELECT_V(unit);
|
||||||
|
uart_set_format(inst, data_bits, stop_bits, (uart_parity_t)parity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_set_flow_control(int unit, bool cts, bool rts)
|
||||||
|
{
|
||||||
|
UNIT_SELECT_V(unit);
|
||||||
|
uart_set_hw_flow(inst, cts, rts);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_write(int unit, const uint8_t *src, size_t len)
|
||||||
|
{
|
||||||
|
UNIT_SELECT_V(unit);
|
||||||
|
uart_write_blocking(inst, src, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_flush(int unit)
|
||||||
|
{
|
||||||
|
UNIT_SELECT_V(unit);
|
||||||
|
uart_tx_wait_blocking(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_send_break(int unit, uint32_t duration_ms)
|
||||||
|
{
|
||||||
|
UNIT_SELECT_V(unit);
|
||||||
|
uart_set_break(inst, true);
|
||||||
|
sleep_ms(duration_ms);
|
||||||
|
uart_set_break(inst, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_clear_rx(int unit)
|
||||||
|
{
|
||||||
|
UNIT_SELECT_V(unit);
|
||||||
|
while (uart_is_readable(inst)) {
|
||||||
|
uart_getc(inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_clear_tx(int unit)
|
||||||
|
{
|
||||||
|
/* not supported on RP2040 */
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#include <mruby/uart.h>
|
||||||
|
|
||||||
|
bool
|
||||||
|
mrb_uart_ringbuf_init(mrb_uart_ringbuf *rb, int size)
|
||||||
|
{
|
||||||
|
/* size must be a power of two */
|
||||||
|
if (size <= 0 || (size & (size - 1)) != 0) return false;
|
||||||
|
rb->head = 0;
|
||||||
|
rb->tail = 0;
|
||||||
|
rb->mask = size - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
mrb_uart_ringbuf_push(mrb_uart_ringbuf *rb, uint8_t ch)
|
||||||
|
{
|
||||||
|
int next = (rb->head + 1) & rb->mask;
|
||||||
|
if (next == rb->tail) return false; /* full */
|
||||||
|
rb->data[rb->head] = ch;
|
||||||
|
rb->head = next;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_uart_ringbuf_pop(mrb_uart_ringbuf *rb, uint8_t *dst, int len)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
if (rb->tail == rb->head) break; /* empty */
|
||||||
|
dst[i] = rb->data[rb->tail];
|
||||||
|
rb->tail = (rb->tail + 1) & rb->mask;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_uart_ringbuf_available(const mrb_uart_ringbuf *rb)
|
||||||
|
{
|
||||||
|
return (rb->head - rb->tail) & rb->mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_uart_ringbuf_clear(mrb_uart_ringbuf *rb)
|
||||||
|
{
|
||||||
|
rb->tail = rb->head;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mrb_uart_ringbuf_search(const mrb_uart_ringbuf *rb, uint8_t ch)
|
||||||
|
{
|
||||||
|
int pos = rb->tail;
|
||||||
|
int i = 0;
|
||||||
|
while (pos != rb->head) {
|
||||||
|
if (rb->data[pos] == ch) return i;
|
||||||
|
pos = (pos + 1) & rb->mask;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
#include <mruby.h>
|
||||||
|
#include <mruby/presym.h>
|
||||||
|
#include <mruby/variable.h>
|
||||||
|
#include <mruby/string.h>
|
||||||
|
#include <mruby/data.h>
|
||||||
|
#include <mruby/class.h>
|
||||||
|
#include <mruby/uart.h>
|
||||||
|
|
||||||
|
#define E_IO_ERROR mrb_exc_get_id(mrb, MRB_SYM(IOError))
|
||||||
|
|
||||||
|
#define DEFAULT_RX_BUF_SIZE 256
|
||||||
|
|
||||||
|
static void
|
||||||
|
rxbuf_free(mrb_state *mrb, void *ptr)
|
||||||
|
{
|
||||||
|
mrb_free(mrb, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct mrb_data_type rxbuf_type = { "UART", rxbuf_free };
|
||||||
|
|
||||||
|
/* UART#__open_rx_buffer(size) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_open_rxbuf(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int size;
|
||||||
|
mrb_get_args(mrb, "i", &size);
|
||||||
|
if (size <= 0) size = DEFAULT_RX_BUF_SIZE;
|
||||||
|
|
||||||
|
mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_malloc(mrb,
|
||||||
|
sizeof(mrb_uart_ringbuf) + sizeof(uint8_t) * size);
|
||||||
|
if (!mrb_uart_ringbuf_init(rb, (int)size)) {
|
||||||
|
mrb_free(mrb, rb);
|
||||||
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "rx_buffer_size must be a power of two");
|
||||||
|
}
|
||||||
|
DATA_PTR(self) = rb;
|
||||||
|
DATA_TYPE(self) = &rxbuf_type;
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#__open_connection(unit_name, tx_pin, rx_pin) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_open_conn(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
const char *name;
|
||||||
|
mrb_int tx_pin, rx_pin;
|
||||||
|
mrb_get_args(mrb, "zii", &name, &tx_pin, &rx_pin);
|
||||||
|
|
||||||
|
int num = mrb_uart_unit_name_to_num(name);
|
||||||
|
if (num < 0) {
|
||||||
|
mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown UART unit: %s", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type);
|
||||||
|
mrb_uart_status st = mrb_uart_init(num, (uint32_t)tx_pin, (uint32_t)rx_pin, rb);
|
||||||
|
if (st != MRB_UART_OK) {
|
||||||
|
mrb_raise(mrb, E_IO_ERROR, "UART init failed");
|
||||||
|
}
|
||||||
|
return mrb_fixnum_value(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#__set_baudrate(baud) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_set_baudrate(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int baud;
|
||||||
|
mrb_get_args(mrb, "i", &baud);
|
||||||
|
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||||
|
uint32_t actual = mrb_uart_set_baudrate((int)unit, (uint32_t)baud);
|
||||||
|
return mrb_fixnum_value(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#__set_format(data_bits, stop_bits, parity) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_set_format(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int data_bits, stop_bits, parity;
|
||||||
|
mrb_get_args(mrb, "iii", &data_bits, &stop_bits, &parity);
|
||||||
|
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||||
|
mrb_uart_set_format((int)unit, (uint32_t)data_bits, (uint32_t)stop_bits, (uint8_t)parity);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#__set_flow_control(cts, rts) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_set_flow(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_bool cts, rts;
|
||||||
|
mrb_get_args(mrb, "bb", &cts, &rts);
|
||||||
|
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||||
|
mrb_uart_set_flow_control((int)unit, cts, rts);
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#write(str) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_write(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_value str;
|
||||||
|
mrb_get_args(mrb, "S", &str);
|
||||||
|
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||||
|
size_t len = RSTRING_LEN(str);
|
||||||
|
mrb_uart_write((int)unit, (const uint8_t*)RSTRING_PTR(str), len);
|
||||||
|
return mrb_fixnum_value(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#read(len=nil) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_read(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int len = -1;
|
||||||
|
mrb_get_args(mrb, "|i", &len);
|
||||||
|
|
||||||
|
mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type);
|
||||||
|
int avail = mrb_uart_ringbuf_available(rb);
|
||||||
|
if (avail == 0) return mrb_nil_value();
|
||||||
|
|
||||||
|
if (len >= 0) {
|
||||||
|
if (avail < len) return mrb_nil_value();
|
||||||
|
avail = (int)len;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *buf = (uint8_t*)mrb_malloc(mrb, avail);
|
||||||
|
int n = mrb_uart_ringbuf_pop(rb, buf, avail);
|
||||||
|
mrb_value str = mrb_str_new(mrb, (const char*)buf, n);
|
||||||
|
mrb_free(mrb, buf);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#readpartial(maxlen) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_readpartial(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int maxlen;
|
||||||
|
mrb_get_args(mrb, "i", &maxlen);
|
||||||
|
|
||||||
|
mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type);
|
||||||
|
int avail = mrb_uart_ringbuf_available(rb);
|
||||||
|
if (avail == 0) return mrb_nil_value();
|
||||||
|
if (avail > maxlen) avail = (int)maxlen;
|
||||||
|
|
||||||
|
uint8_t *buf = (uint8_t*)mrb_malloc(mrb, avail);
|
||||||
|
int n = mrb_uart_ringbuf_pop(rb, buf, avail);
|
||||||
|
mrb_value str = mrb_str_new(mrb, (const char*)buf, n);
|
||||||
|
mrb_free(mrb, buf);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#bytes_available */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_bytes_available(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type);
|
||||||
|
return mrb_fixnum_value(mrb_uart_ringbuf_available(rb));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#gets */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_gets(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type);
|
||||||
|
int pos = mrb_uart_ringbuf_search(rb, (uint8_t)'\n');
|
||||||
|
if (pos < 0) return mrb_nil_value();
|
||||||
|
int len = pos + 1;
|
||||||
|
uint8_t *buf = (uint8_t*)mrb_malloc(mrb, len);
|
||||||
|
mrb_uart_ringbuf_pop(rb, buf, len);
|
||||||
|
mrb_value str = mrb_str_new(mrb, (const char*)buf, len);
|
||||||
|
mrb_free(mrb, buf);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#flush */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_flush(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||||
|
mrb_uart_flush((int)unit);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#clear_tx_buffer */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_clear_tx(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||||
|
mrb_uart_clear_tx((int)unit);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#clear_rx_buffer */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_clear_rx(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_uart_ringbuf *rb = (mrb_uart_ringbuf*)mrb_data_get_ptr(mrb, self, &rxbuf_type);
|
||||||
|
mrb_uart_ringbuf_clear(rb);
|
||||||
|
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||||
|
mrb_uart_clear_rx((int)unit);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UART#send_break(duration_ms=100) */
|
||||||
|
static mrb_value
|
||||||
|
mrb_uart_m_send_break(mrb_state *mrb, mrb_value self)
|
||||||
|
{
|
||||||
|
mrb_int ms = 100;
|
||||||
|
mrb_get_args(mrb, "|i", &ms);
|
||||||
|
mrb_int unit = mrb_integer(mrb_iv_get(mrb, self, MRB_IVSYM(unit_num)));
|
||||||
|
mrb_uart_send_break((int)unit, (uint32_t)ms);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_uart_gem_init(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
struct RClass *cls = mrb_define_class_id(mrb, MRB_SYM(UART), mrb->object_class);
|
||||||
|
MRB_SET_INSTANCE_TT(cls, MRB_TT_CDATA);
|
||||||
|
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(__open_rx_buffer), mrb_uart_m_open_rxbuf, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(__open_connection), mrb_uart_m_open_conn, MRB_ARGS_REQ(3));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(__set_baudrate), mrb_uart_m_set_baudrate, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(__set_format), mrb_uart_m_set_format, MRB_ARGS_REQ(3));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(__set_flow_control), mrb_uart_m_set_flow, MRB_ARGS_REQ(2));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(write), mrb_uart_m_write, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(read), mrb_uart_m_read, MRB_ARGS_OPT(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(readpartial), mrb_uart_m_readpartial, MRB_ARGS_REQ(1));
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(bytes_available), mrb_uart_m_bytes_available, MRB_ARGS_NONE());
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(gets), mrb_uart_m_gets, MRB_ARGS_NONE());
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(flush), mrb_uart_m_flush, MRB_ARGS_NONE());
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(clear_tx_buffer), mrb_uart_m_clear_tx, MRB_ARGS_NONE());
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(clear_rx_buffer), mrb_uart_m_clear_rx, MRB_ARGS_NONE());
|
||||||
|
mrb_define_method_id(mrb, cls, MRB_SYM(send_break), mrb_uart_m_send_break, MRB_ARGS_OPT(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mrb_hw_uart_gem_final(mrb_state *mrb)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -450,27 +450,7 @@ class Array
|
|||||||
# a.permutation(0).to_a #=> [[]] # one permutation of length 0
|
# a.permutation(0).to_a #=> [[]] # one permutation of length 0
|
||||||
# a.permutation(4).to_a #=> [] # no permutations of length 4
|
# a.permutation(4).to_a #=> [] # no permutations of length 4
|
||||||
def permutation(n=self.size, &block)
|
def permutation(n=self.size, &block)
|
||||||
n = n.__to_int
|
__combination(:permutation, n, &block)
|
||||||
return to_enum(:permutation, n) unless block
|
|
||||||
size = self.size
|
|
||||||
if n == 0
|
|
||||||
yield []
|
|
||||||
elsif 0 < n && n <= size
|
|
||||||
i = 0
|
|
||||||
while i<size
|
|
||||||
result = [self[i]]
|
|
||||||
if n-1 > 0
|
|
||||||
ary = self[0...i] + self[i+1..-1]
|
|
||||||
ary.permutation(n-1) do |c|
|
|
||||||
yield result + c
|
|
||||||
end
|
|
||||||
else
|
|
||||||
yield result
|
|
||||||
end
|
|
||||||
i += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -497,28 +477,7 @@ class Array
|
|||||||
# a.combination(5).to_a #=> [] # no combinations of length 5
|
# a.combination(5).to_a #=> [] # no combinations of length 5
|
||||||
|
|
||||||
def combination(n, &block)
|
def combination(n, &block)
|
||||||
n = n.__to_int
|
__combination(:combination, n, &block)
|
||||||
return to_enum(:combination, n) unless block
|
|
||||||
size = self.size
|
|
||||||
if n == 0
|
|
||||||
yield []
|
|
||||||
elsif n == 1
|
|
||||||
i = 0
|
|
||||||
while i<size
|
|
||||||
yield [self[i]]
|
|
||||||
i += 1
|
|
||||||
end
|
|
||||||
elsif n <= size
|
|
||||||
i = 0
|
|
||||||
while i<size
|
|
||||||
result = [self[i]]
|
|
||||||
self[i+1..-1].combination(n-1) do |c|
|
|
||||||
yield result + c
|
|
||||||
end
|
|
||||||
i += 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -642,9 +601,7 @@ class Array
|
|||||||
# a = [1, 2, 3]
|
# a = [1, 2, 3]
|
||||||
# a.repeated_combination(2).to_a #=> [[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]
|
# a.repeated_combination(2).to_a #=> [[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]
|
||||||
def repeated_combination(n, &block)
|
def repeated_combination(n, &block)
|
||||||
raise TypeError, "no implicit conversion into Integer" unless 0 <=> n
|
__combination(:repeated_combination, n, &block)
|
||||||
return to_enum(:repeated_combination, n) unless block
|
|
||||||
__repeated_combination(n, false, &block)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -667,36 +624,27 @@ class Array
|
|||||||
# a = [1, 2]
|
# a = [1, 2]
|
||||||
# a.repeated_permutation(2).to_a #=> [[1,1],[1,2],[2,1],[2,2]]
|
# a.repeated_permutation(2).to_a #=> [[1,1],[1,2],[2,1],[2,2]]
|
||||||
def repeated_permutation(n, &block)
|
def repeated_permutation(n, &block)
|
||||||
n = n.__to_int
|
__combination(:repeated_permutation, n, &block)
|
||||||
raise TypeError, "no implicit conversion into Integer" unless 0 <=> n
|
|
||||||
return to_enum(:repeated_permutation, n) unless block
|
|
||||||
__repeated_combination(n, true, &block)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def __repeated_combination(n, permutation, &block)
|
def __combination(mode, k, &block)
|
||||||
n = n.__to_int
|
k = k.__to_int
|
||||||
case n
|
return to_enum(mode, k) unless block
|
||||||
|
|
||||||
|
case k
|
||||||
when 0
|
when 0
|
||||||
yield []
|
yield []
|
||||||
when 1
|
when 1
|
||||||
# Keep fast Ruby path for n=1
|
# Keep fast Ruby path for k=1
|
||||||
i = 0
|
i = 0
|
||||||
while i < self.size
|
while i < self.size
|
||||||
yield [self[i]]
|
yield [self[i]]
|
||||||
i += 1
|
i += 1
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if n > 0
|
if state = __combination_init(mode, k)
|
||||||
# Use C iterator for complex cases
|
# Use C iterator for complex cases
|
||||||
state = __combination_init(n, permutation)
|
while tmp = __combination_next(state)
|
||||||
while (indices = __combination_next(state))
|
|
||||||
# Convert indices to elements in Ruby
|
|
||||||
tmp = [nil] * n
|
|
||||||
i = 0
|
|
||||||
while i < n
|
|
||||||
tmp[i] = self[indices[i]]
|
|
||||||
i += 1
|
|
||||||
end
|
|
||||||
yield tmp
|
yield tmp
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,10 +32,8 @@ typedef khash_t(ary_set) ary_set_t;
|
|||||||
/* Combination state structure for repeated_combination optimization */
|
/* Combination state structure for repeated_combination optimization */
|
||||||
struct mrb_combination_state {
|
struct mrb_combination_state {
|
||||||
mrb_int *indices;
|
mrb_int *indices;
|
||||||
mrb_int n;
|
mrb_int n, k; /* nPk, nCk */
|
||||||
mrb_int array_size;
|
int mode;
|
||||||
mrb_bool permutation;
|
|
||||||
mrb_bool finished;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -1533,6 +1531,13 @@ ary_deconstruct(mrb_state *mrb, mrb_value ary)
|
|||||||
return ary;
|
return ary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
comb_finished = 0,
|
||||||
|
comb_repeated_permutation = 1,
|
||||||
|
comb_repeated_combination = 2,
|
||||||
|
comb_permutation = 3,
|
||||||
|
comb_combination = 4
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Internal method to initialize combination state.
|
* Internal method to initialize combination state.
|
||||||
@@ -1541,33 +1546,74 @@ ary_deconstruct(mrb_state *mrb, mrb_value ary)
|
|||||||
static mrb_value
|
static mrb_value
|
||||||
ary_combination_init(mrb_state *mrb, mrb_value self)
|
ary_combination_init(mrb_state *mrb, mrb_value self)
|
||||||
{
|
{
|
||||||
mrb_int n;
|
mrb_int k;
|
||||||
mrb_bool permutation;
|
mrb_sym mode_sym;
|
||||||
|
|
||||||
mrb_get_args(mrb, "ib", &n, &permutation);
|
mrb_get_args(mrb, "ni", &mode_sym, &k);
|
||||||
#if MRB_INT_MAX > SIZE_MAX
|
#if MRB_INT_MAX > SIZE_MAX
|
||||||
if (n > SIZE_MAX) {
|
if (k > SIZE_MAX) {
|
||||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "number too large");
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "number too large");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (k < 1 || RARRAY_LEN(self) < 1) {
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
int mode;
|
||||||
|
switch (mode_sym) {
|
||||||
|
case MRB_SYM(repeated_permutation):
|
||||||
|
mode = comb_repeated_permutation;
|
||||||
|
break;
|
||||||
|
case MRB_SYM(repeated_combination):
|
||||||
|
mode = comb_repeated_combination;
|
||||||
|
break;
|
||||||
|
case MRB_SYM(permutation):
|
||||||
|
if (k > RARRAY_LEN(self)) {
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
mode = comb_permutation;
|
||||||
|
break;
|
||||||
|
case MRB_SYM(combination):
|
||||||
|
if (k > RARRAY_LEN(self)) {
|
||||||
|
return mrb_nil_value();
|
||||||
|
}
|
||||||
|
mode = comb_combination;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong mode");
|
||||||
|
}
|
||||||
|
|
||||||
struct RData *d;
|
struct RData *d;
|
||||||
struct mrb_combination_state *state;
|
struct mrb_combination_state *state;
|
||||||
Data_Make_Struct(mrb, mrb->object_class, struct mrb_combination_state,
|
Data_Make_Struct(mrb, mrb->object_class, struct mrb_combination_state,
|
||||||
&mrb_combination_state_type, state, d);
|
&mrb_combination_state_type, state, d);
|
||||||
|
|
||||||
state->n = n;
|
state->k = k;
|
||||||
state->array_size = RARRAY_LEN(self);
|
state->n = RARRAY_LEN(self);
|
||||||
state->permutation = permutation;
|
state->mode = mode;
|
||||||
state->finished = (n <= 0 && n != 0);
|
state->indices = (mrb_int*)mrb_calloc(mrb, k, sizeof(mrb_int));
|
||||||
|
|
||||||
if (n > 0) {
|
if (mode == comb_permutation || mode == comb_combination) {
|
||||||
state->indices = (mrb_int*)mrb_calloc(mrb, n, sizeof(mrb_int));
|
for (mrb_int i = 0; i < k; i++) {
|
||||||
|
state->indices[i] = i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mrb_obj_value(d);
|
return mrb_obj_value(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
adjust_next_permutation_index(struct mrb_combination_state *state, mrb_int i)
|
||||||
|
{
|
||||||
|
for (mrb_int j = i - 1; j >= 0; j--) {
|
||||||
|
if (state->indices[i] == state->indices[j]) {
|
||||||
|
state->indices[i]++;
|
||||||
|
j = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Internal method to get next combination as index array.
|
* Internal method to get next combination as index array.
|
||||||
* Returns array of indices or nil when iteration is complete.
|
* Returns array of indices or nil when iteration is complete.
|
||||||
@@ -1575,68 +1621,87 @@ ary_combination_init(mrb_state *mrb, mrb_value self)
|
|||||||
static mrb_value
|
static mrb_value
|
||||||
ary_combination_next(mrb_state *mrb, mrb_value self)
|
ary_combination_next(mrb_state *mrb, mrb_value self)
|
||||||
{
|
{
|
||||||
mrb_value state_obj;
|
|
||||||
mrb_get_args(mrb, "o", &state_obj);
|
|
||||||
|
|
||||||
struct mrb_combination_state *state;
|
struct mrb_combination_state *state;
|
||||||
|
mrb_get_args(mrb, "d", &state, &mrb_combination_state_type);
|
||||||
/* Validate state object type and get data */
|
|
||||||
state = (struct mrb_combination_state*)mrb_data_check_and_get(mrb, state_obj, &mrb_combination_state_type);
|
|
||||||
if (!state) {
|
|
||||||
mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid combination state");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if iteration is complete */
|
/* Check if iteration is complete */
|
||||||
if (state->finished) return mrb_nil_value();
|
if (state->mode == comb_finished) return mrb_nil_value();
|
||||||
|
|
||||||
/* Validate array hasn't been modified during iteration */
|
/* Validate array hasn't been modified during iteration */
|
||||||
if (RARRAY_LEN(self) != state->array_size) {
|
if (RARRAY_LEN(self) != state->n) {
|
||||||
mrb_raise(mrb, E_RUNTIME_ERROR, "array modified during iteration");
|
mrb_raise(mrb, E_RUNTIME_ERROR, "array modified during iteration");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Edge case: empty array */
|
|
||||||
if (state->array_size == 0) {
|
|
||||||
state->finished = TRUE;
|
|
||||||
return mrb_nil_value();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Validate current indices are still in bounds */
|
/* Validate current indices are still in bounds */
|
||||||
for (mrb_int i = 0; i < state->n; i++) {
|
for (mrb_int i = 0; i < state->k; i++) {
|
||||||
if (state->indices[i] >= state->array_size) {
|
if (state->indices[i] >= state->n) {
|
||||||
state->finished = TRUE;
|
state->mode = comb_finished;
|
||||||
|
mrb_free(mrb, state->indices);
|
||||||
|
state->indices = NULL;
|
||||||
return mrb_nil_value();
|
return mrb_nil_value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Build current combination indices */
|
/* Build current combination */
|
||||||
mrb_value result = mrb_ary_new_capa(mrb, state->n);
|
mrb_value result = mrb_ary_new_capa(mrb, state->k);
|
||||||
for (mrb_int i = 0; i < state->n; i++) {
|
const mrb_value *p = RARRAY_PTR(self);
|
||||||
mrb_ary_push(mrb, result, mrb_fixnum_value(state->indices[i]));
|
for (mrb_int i = 0; i < state->k; i++) {
|
||||||
|
mrb_ary_push(mrb, result, p[state->indices[i]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
mrb_int pos = state->n - 1;
|
switch (state->mode) {
|
||||||
|
case comb_repeated_permutation:
|
||||||
while (pos >= 0) {
|
case comb_repeated_combination:
|
||||||
state->indices[pos]++;
|
for (mrb_int i = state->k - 1; i >= 0; i--) {
|
||||||
if (state->indices[pos] < state->array_size) break;
|
state->indices[i]++;
|
||||||
pos--;
|
if (state->indices[i] < state->n) {
|
||||||
}
|
/* Reset dependent indices */
|
||||||
|
mrb_int reset = (state->mode == comb_repeated_permutation) ? 0 : state->indices[i];
|
||||||
if (pos < 0) {
|
for (i++; i < state->k; i++) {
|
||||||
state->finished = TRUE;
|
state->indices[i] = reset;
|
||||||
}
|
}
|
||||||
else {
|
return result;
|
||||||
/* Reset dependent indices */
|
|
||||||
for (mrb_int i = pos + 1; i < state->n; i++) {
|
|
||||||
if (state->permutation) {
|
|
||||||
state->indices[i] = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
state->indices[i] = state->indices[i - 1];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case comb_permutation:
|
||||||
|
for (mrb_int i = state->k - 1; i >= 0; i--) {
|
||||||
|
state->indices[i]++;
|
||||||
|
|
||||||
|
// adjust so that it does not overlap with the leading index
|
||||||
|
adjust_next_permutation_index(state, i);
|
||||||
|
|
||||||
|
if (state->indices[i] < state->n) {
|
||||||
|
// adjust all trailing indexes to complete the function
|
||||||
|
for (i++; i < state->k; i++) {
|
||||||
|
state->indices[i] = 0;
|
||||||
|
adjust_next_permutation_index(state, i);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case comb_combination:
|
||||||
|
for (mrb_int i = state->k - 1; i >= 0; i--) {
|
||||||
|
state->indices[i]++;
|
||||||
|
|
||||||
|
if (state->indices[i] <= state->n - state->k + i) {
|
||||||
|
// replace each overflowed indices with an index incremented by 1 from the previous one
|
||||||
|
for (i++; i < state->k; i++) {
|
||||||
|
state->indices[i] = state->indices[i - 1] + 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: // it probably won’t happen, but just in case
|
||||||
|
result = mrb_nil_value();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state->mode = comb_finished;
|
||||||
|
mrb_free(mrb, state->indices);
|
||||||
|
state->indices = NULL;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,11 +50,15 @@ typedef struct mpz_context {
|
|||||||
mpz_pool_t *pool; /* NULL for heap-only operations */
|
mpz_pool_t *pool; /* NULL for heap-only operations */
|
||||||
} mpz_ctx_t;
|
} mpz_ctx_t;
|
||||||
|
|
||||||
/* Convenience macros for context creation */
|
/* Convenience macros for context creation.
|
||||||
|
* Uses positional aggregate initialization instead of a C99 compound
|
||||||
|
* literal with designated initializers, so the file compiles as C++
|
||||||
|
* on legacy toolchains (pre-C++20). Member order must match the
|
||||||
|
* mpz_context struct declaration above. */
|
||||||
#define MPZ_CTX_INIT(mrb_ptr, ctx, pool_ptr) \
|
#define MPZ_CTX_INIT(mrb_ptr, ctx, pool_ptr) \
|
||||||
mpz_pool_t pool ## _storage = {{0}};\
|
mpz_pool_t pool ## _storage = {{0}};\
|
||||||
mpz_pool_t *pool_ptr = &pool ## _storage;\
|
mpz_pool_t *pool_ptr = &pool ## _storage;\
|
||||||
mpz_ctx_t ctx ## _struct = ((mpz_ctx_t){.mrb = (mrb_ptr), .pool = (pool_ptr)}); \
|
mpz_ctx_t ctx ## _struct = { (mrb_ptr), (pool_ptr) }; \
|
||||||
mpz_ctx_t *ctx = &(ctx ## _struct);
|
mpz_ctx_t *ctx = &(ctx ## _struct);
|
||||||
|
|
||||||
/* Access macros for readability */
|
/* Access macros for readability */
|
||||||
@@ -344,6 +348,14 @@ mpz_move(mpz_ctx_t *ctx, mpz_t *y, mpz_t *x)
|
|||||||
x->sz = 0;
|
x->sz = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
mpz_swap(mpz_t *a, mpz_t *b)
|
||||||
|
{
|
||||||
|
mpz_t tmp = *a;
|
||||||
|
*a = *b;
|
||||||
|
*b = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
digits(mpz_t *x)
|
digits(mpz_t *x)
|
||||||
{
|
{
|
||||||
@@ -361,6 +373,8 @@ trim(mpz_t *x)
|
|||||||
while (x->sz && x->p[x->sz-1] == 0) {
|
while (x->sz && x->p[x->sz-1] == 0) {
|
||||||
x->sz--;
|
x->sz--;
|
||||||
}
|
}
|
||||||
|
/* Maintain invariant: sz == 0 implies sn == 0 (zero is canonical). */
|
||||||
|
if (x->sz == 0) x->sn = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* z = x + y, without regard for sign */
|
/* z = x + y, without regard for sign */
|
||||||
@@ -1236,7 +1250,8 @@ mpn_add_var(mp_limb *rp, const mp_limb *ap, size_t an,
|
|||||||
rp[i] = LOW(sum);
|
rp[i] = LOW(sum);
|
||||||
carry = HIGH(sum);
|
carry = HIGH(sum);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
for (; i < bn; i++) {
|
for (; i < bn; i++) {
|
||||||
mp_dbl_limb sum = (mp_dbl_limb)bp[i] + carry;
|
mp_dbl_limb sum = (mp_dbl_limb)bp[i] + carry;
|
||||||
rp[i] = LOW(sum);
|
rp[i] = LOW(sum);
|
||||||
@@ -1560,7 +1575,8 @@ mpz_mul_toom3(mpz_ctx_t *ctx, mp_limb *result,
|
|||||||
mpn_add(t2, t2, w_len, w0, w_len);
|
mpn_add(t2, t2, w_len, w0, w_len);
|
||||||
mpn_add(t2, t2, w_len, winf, w_len);
|
mpn_add(t2, t2, w_len, winf, w_len);
|
||||||
mpn_neg(t2, t2, w_len);
|
mpn_neg(t2, t2, w_len);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
mpn_sub(t2, t2, w_len, w0, w_len);
|
mpn_sub(t2, t2, w_len, w0, w_len);
|
||||||
mpn_sub(t2, t2, w_len, winf, w_len);
|
mpn_sub(t2, t2, w_len, winf, w_len);
|
||||||
}
|
}
|
||||||
@@ -3133,8 +3149,11 @@ mpz_mod(mpz_ctx_t *ctx, mpz_t *r, mpz_t *x, mpz_t *y)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Barrett reduction for moderate-sized moduli (>= 4 limbs where setup is worthwhile) */
|
/* Barrett reduction for moderate-sized moduli (>= 4 limbs where setup is worthwhile).
|
||||||
if (y->sz >= 4 && y->sz <= 16 && x->sz >= y->sz + 2) {
|
* Barrett's precondition is x < 2^(2*bits(m)); inputs beyond ~2*m.sz limbs
|
||||||
|
* violate it and the algorithm silently truncates high limbs. Fall through
|
||||||
|
* to general division for those. */
|
||||||
|
if (y->sz >= 4 && y->sz <= 16 && x->sz >= y->sz + 2 && x->sz <= 2 * y->sz) {
|
||||||
mpz_t mu;
|
mpz_t mu;
|
||||||
mpz_init_temp(ctx, &mu, y->sz + 1);
|
mpz_init_temp(ctx, &mu, y->sz + 1);
|
||||||
mpz_barrett_mu(ctx, &mu, y);
|
mpz_barrett_mu(ctx, &mu, y);
|
||||||
@@ -4030,7 +4049,7 @@ mpz_get_str(mpz_ctx_t *ctx, char *s, mrb_int sz, mrb_int base, mpz_t *x)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// convert to character
|
// convert to character
|
||||||
for (mp_limb b=b2; b>=base; b/=(mp_limb)base) {
|
for (mp_limb b=b2; b>=(mp_limb)base; b/=(mp_limb)base) {
|
||||||
char a0 = (char)(a % base);
|
char a0 = (char)(a % base);
|
||||||
if (a0 < 10) a0 += '0';
|
if (a0 < 10) a0 += '0';
|
||||||
else a0 += 'a' - 10;
|
else a0 += 'a' - 10;
|
||||||
@@ -4817,7 +4836,11 @@ mpz_power_of_2_p(mpz_t *x)
|
|||||||
return (limb != 0) && ((limb & (limb - 1)) == 0);
|
return (limb != 0) && ((limb & (limb - 1)) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Binary GCD algorithm (Stein's algorithm) - faster than Euclidean GCD */
|
/* Binary GCD (Stein's algorithm): factor out common powers of 2,
|
||||||
|
then iterate on odd operands with subtract + trailing-zero shift.
|
||||||
|
For heavily unbalanced pairs (one operand has at least two more
|
||||||
|
limbs than the other) a single Euclidean step via mpz_mod replaces
|
||||||
|
many Stein subtracts. */
|
||||||
static void
|
static void
|
||||||
mpz_gcd(mpz_ctx_t *ctx, mpz_t *gg, mpz_t *aa, mpz_t *bb)
|
mpz_gcd(mpz_ctx_t *ctx, mpz_t *gg, mpz_t *aa, mpz_t *bb)
|
||||||
{
|
{
|
||||||
@@ -4889,14 +4912,29 @@ mpz_gcd(mpz_ctx_t *ctx, mpz_t *gg, mpz_t *aa, mpz_t *bb)
|
|||||||
mpz_div_2exp(ctx, &a, &a, a_zeros);
|
mpz_div_2exp(ctx, &a, &a, a_zeros);
|
||||||
mpz_div_2exp(ctx, &b, &b, b_zeros);
|
mpz_div_2exp(ctx, &b, &b, b_zeros);
|
||||||
|
|
||||||
/* Euclidean algorithm for multi-limb numbers */
|
/* Stein main loop. Invariant: a and b are positive and odd.
|
||||||
|
Euclidean fallback when b has >=2 more limbs than a. */
|
||||||
while (!zero_p(&b)) {
|
while (!zero_p(&b)) {
|
||||||
mpz_t temp;
|
if (mpz_cmp(ctx, &a, &b) > 0) {
|
||||||
mpz_init_temp(ctx, &temp, a.sz);
|
mpz_swap(&a, &b);
|
||||||
mpz_mod(ctx, &temp, &a, &b);
|
}
|
||||||
mpz_move(ctx, &a, &b);
|
if (b.sz >= a.sz + 2) {
|
||||||
mpz_move(ctx, &b, &temp);
|
mpz_t temp;
|
||||||
mpz_clear(ctx, &temp);
|
mpz_init_temp(ctx, &temp, a.sz);
|
||||||
|
mpz_mod(ctx, &temp, &b, &a);
|
||||||
|
mpz_move(ctx, &b, &temp);
|
||||||
|
mpz_clear(ctx, &temp);
|
||||||
|
if (zero_p(&b)) break;
|
||||||
|
size_t bz = mpz_trailing_zeros(&b);
|
||||||
|
if (bz > 0)
|
||||||
|
mpz_div_2exp(ctx, &b, &b, bz);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mpz_sub(ctx, &b, &b, &a);
|
||||||
|
if (zero_p(&b)) break;
|
||||||
|
size_t bz = mpz_trailing_zeros(&b);
|
||||||
|
mpz_div_2exp(ctx, &b, &b, bz);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mpz_mul_2exp(ctx, gg, &a, shift);
|
mpz_mul_2exp(ctx, gg, &a, shift);
|
||||||
mpz_clear(ctx, &a);
|
mpz_clear(ctx, &a);
|
||||||
@@ -5215,12 +5253,19 @@ mpz_powm_montgomery(mpz_ctx_t *ctx, mpz_t *result,
|
|||||||
mpz_init(ctx, &one_mont);
|
mpz_init(ctx, &one_mont);
|
||||||
mpz_montgomery_reduce(ctx, &one_mont, &R2, n, rho);
|
mpz_montgomery_reduce(ctx, &one_mont, &R2, n, rho);
|
||||||
|
|
||||||
/* Convert base to Montgomery form: base_mont = base * R mod n = REDC(base * R^2) */
|
/* Convert base to Montgomery form: base_mont = base * R mod n = REDC(base * R^2).
|
||||||
mpz_t base_mont, temp;
|
* REDC requires its input T to satisfy T < R*N. If `base` is not already
|
||||||
|
* reduced (e.g. base >= n), `base * R^2` can exceed R*N and REDC produces
|
||||||
|
* a wrong result. Pre-reduce base modulo n via mpz_mmod (the general
|
||||||
|
* division path) -- both operands are non-negative here so this is
|
||||||
|
* semantically equivalent to mpz_mod. */
|
||||||
|
mpz_t base_mont, base_reduced, temp;
|
||||||
mpz_init(ctx, &base_mont);
|
mpz_init(ctx, &base_mont);
|
||||||
|
mpz_init(ctx, &base_reduced);
|
||||||
mpz_init_temp(ctx, &temp, n->sz * 4);
|
mpz_init_temp(ctx, &temp, n->sz * 4);
|
||||||
|
|
||||||
mpz_mul(ctx, &temp, (mpz_t*)base, &R2);
|
mpz_mmod(ctx, &base_reduced, (mpz_t*)base, (mpz_t*)n);
|
||||||
|
mpz_mul(ctx, &temp, &base_reduced, &R2);
|
||||||
mpz_montgomery_reduce(ctx, &base_mont, &temp, n, rho);
|
mpz_montgomery_reduce(ctx, &base_mont, &temp, n, rho);
|
||||||
|
|
||||||
/* Initialize accumulator to 1 in Montgomery form */
|
/* Initialize accumulator to 1 in Montgomery form */
|
||||||
@@ -5252,6 +5297,7 @@ mpz_powm_montgomery(mpz_ctx_t *ctx, mpz_t *result,
|
|||||||
mpz_clear(ctx, &R2);
|
mpz_clear(ctx, &R2);
|
||||||
mpz_clear(ctx, &one_mont);
|
mpz_clear(ctx, &one_mont);
|
||||||
mpz_clear(ctx, &base_mont);
|
mpz_clear(ctx, &base_mont);
|
||||||
|
mpz_clear(ctx, &base_reduced);
|
||||||
mpz_clear(ctx, &temp);
|
mpz_clear(ctx, &temp);
|
||||||
mpz_clear(ctx, &acc);
|
mpz_clear(ctx, &acc);
|
||||||
pool_restore(ctx, pool_state);
|
pool_restore(ctx, pool_state);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ MRuby::Gem::Specification.new('mruby-bigint') do |spec|
|
|||||||
spec.summary = 'Integer class extension to multiple-precision'
|
spec.summary = 'Integer class extension to multiple-precision'
|
||||||
spec.build.defines << "MRB_USE_BIGINT"
|
spec.build.defines << "MRB_USE_BIGINT"
|
||||||
|
|
||||||
|
spec.add_test_dependency('mruby-numeric-ext', :core => 'mruby-numeric-ext')
|
||||||
|
|
||||||
spec.build.libmruby_core_objs << Dir.glob(File.join(__dir__, "core/**/*.c")).map { |fn|
|
spec.build.libmruby_core_objs << Dir.glob(File.join(__dir__, "core/**/*.c")).map { |fn|
|
||||||
objfile(fn.relative_path_from(__dir__).pathmap("#{spec.build_dir}/%X"))
|
objfile(fn.relative_path_from(__dir__).pathmap("#{spec.build_dir}/%X"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,8 +150,75 @@ assert 'Bigint pow' do
|
|||||||
# assert_equal(-1041439304, n.pow(n, -1234567890))
|
# assert_equal(-1041439304, n.pow(n, -1234567890))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
assert 'Bigint Integer#pow(e, m) - Montgomery path' do
|
||||||
|
# Regression: mpz_powm_montgomery() failed to pre-reduce base mod n,
|
||||||
|
# producing wrong results when base >= n. Also trim() must restore
|
||||||
|
# the canonical sn=0 when sz becomes 0, otherwise an inconsistent
|
||||||
|
# zero bignum (sn!=0, sz=0) propagates through the squaring loop.
|
||||||
|
m = (2**40) + 1
|
||||||
|
assert_equal 1, (2**160).pow(2, m)
|
||||||
|
assert_equal 1, (2**320).pow(2, m)
|
||||||
|
assert_equal 8, ((2**160) + 1).pow(3, m)
|
||||||
|
m2 = (2**100) + 3
|
||||||
|
assert_equal (3**500) % m2, (3**500).pow(1, m2)
|
||||||
|
assert_equal ((5**300) ** 7) % m2, (5**300).pow(7, m2)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert 'Bigint Integer#remainder large operand' do
|
||||||
|
# Regression: mpz_mod's Barrett path didn't enforce its precondition
|
||||||
|
# x < 2^(2*bits(m)), so it silently truncated high limbs when x was
|
||||||
|
# much larger than m^2, producing the wrong remainder. Integer#%
|
||||||
|
# took the udiv path and worked, but Integer#remainder went through
|
||||||
|
# mpz_mod and was broken.
|
||||||
|
m = (2**100) + 3
|
||||||
|
assert_equal (3**500) % m, (3**500).remainder(m)
|
||||||
|
assert_equal (5**500) % ((2**150) + 1), (5**500).remainder((2**150) + 1)
|
||||||
|
assert_equal (2**400) % ((2**130) + 1), (2**400).remainder((2**130) + 1)
|
||||||
|
end
|
||||||
|
|
||||||
assert 'Bigint abs' do
|
assert 'Bigint abs' do
|
||||||
n = 1<<65
|
n = 1<<65
|
||||||
assert_equal 36893488147419103232, n.abs
|
assert_equal 36893488147419103232, n.abs
|
||||||
assert_equal 36893488147419103232, (-n).abs
|
assert_equal 36893488147419103232, (-n).abs
|
||||||
end
|
end
|
||||||
|
|
||||||
|
assert 'Bigint gcd' do
|
||||||
|
# zero cases
|
||||||
|
assert_equal 0, 0.gcd(0)
|
||||||
|
n = 1 << 200
|
||||||
|
assert_equal n, n.gcd(0)
|
||||||
|
assert_equal n, 0.gcd(n)
|
||||||
|
|
||||||
|
# power-of-2 fast path
|
||||||
|
assert_equal 1 << 100, (1 << 200).gcd(1 << 100)
|
||||||
|
assert_equal 1 << 100, (1 << 100).gcd(1 << 200)
|
||||||
|
assert_equal 1 << 40, (10 ** 50).gcd(1 << 40)
|
||||||
|
|
||||||
|
# negative operands: result is the positive GCD
|
||||||
|
a = 1 << 200
|
||||||
|
b = 3 << 200
|
||||||
|
assert_equal a, a.gcd(b)
|
||||||
|
assert_equal a, (-a).gcd(b)
|
||||||
|
assert_equal a, a.gcd(-b)
|
||||||
|
assert_equal a, (-a).gcd(-b)
|
||||||
|
|
||||||
|
# balanced multi-limb with known common factor
|
||||||
|
fib1000 = (1..1000).inject([0, 1]) { |(x, y), _| [y, x + y] }[0]
|
||||||
|
common = fib1000
|
||||||
|
k, m = 1_000_003, 1_000_033 # small coprime primes
|
||||||
|
assert_equal common, (common * k).gcd(common * m)
|
||||||
|
assert_equal common, (common * m).gcd(common * k)
|
||||||
|
|
||||||
|
# unbalanced: small coprime vs large
|
||||||
|
big = common * k
|
||||||
|
assert_equal 1, big.gcd(m)
|
||||||
|
assert_equal 1, m.gcd(big)
|
||||||
|
|
||||||
|
# Fibonacci neighbors are always coprime
|
||||||
|
f100 = (1..100).inject([0, 1]) { |(x, y), _| [y, x + y] }[0]
|
||||||
|
f101 = (1..101).inject([0, 1]) { |(x, y), _| [y, x + y] }[0]
|
||||||
|
assert_equal 1, f100.gcd(f101)
|
||||||
|
|
||||||
|
# Euclidean fallback path: operand sizes differ by several limbs
|
||||||
|
assert_equal 7, (7 * (1 << 4000)).gcd(7 * 13)
|
||||||
|
end
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ MRuby::Gem::Specification.new('mruby-bin-config') do |spec|
|
|||||||
if iscross
|
if iscross
|
||||||
build.products << mruby_config_path
|
build.products << mruby_config_path
|
||||||
else
|
else
|
||||||
build.bins << mruby_config
|
build.products << build.define_installer(mruby_config_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
directory mruby_config_dir
|
directory mruby_config_dir
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user