├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ ├── coverage.yml │ ├── docs.yml │ └── fmt.yml ├── .gitignore ├── .vscode ├── janetrs-snippets.code-snippets └── settings.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bors.toml ├── examples ├── hello_world.rs └── janet_native_module.rs ├── janetrs_macros ├── Cargo.toml ├── README.md ├── src │ ├── lib.rs │ └── utils.rs └── tests │ ├── 00-test │ ├── 01-paths │ ├── 01-paths.stderr │ ├── 02-not-function │ ├── 02-not-function.stderr │ ├── 03-not-mut │ ├── 03-not-mut.stderr │ ├── 04-not-ref │ ├── 04-not-ref.stderr │ ├── 05-wrong-args │ ├── 05-wrong-args.stderr │ ├── 06-not-slice │ ├── 06-not-slice.stderr │ ├── 07-mod │ ├── 08-wrong-args │ ├── 08-wrong-args.stderr │ ├── 09-version-wrong │ ├── 09-version-wrong.stderr │ ├── 10-declare-janet-module │ ├── 11-declare_mod_errors │ ├── 11-declare_mod_errors.stderr │ └── progress.rs ├── janetrs_version ├── Cargo.toml └── src │ └── lib.rs ├── rustfmt.toml └── src ├── allocator.rs ├── client.rs ├── env.rs ├── gc.rs ├── lib.rs ├── macros.rs ├── types.rs ├── types ├── abstract.rs ├── array.rs ├── buffer.rs ├── fiber.rs ├── function.rs ├── io.rs ├── pointer.rs ├── random.rs ├── string.rs ├── structs.rs ├── symbol.rs ├── table.rs └── tuple.rs └── util.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: GrayJack -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Check and Test 2 | 3 | on: 4 | push: 5 | branches: ["**"] 6 | tags-ignore: ["**"] 7 | paths-ignore: 8 | - "**.md" 9 | - LICENSE 10 | - .github/FUNDING.yml 11 | - .gitignore 12 | pull_request: 13 | paths-ignore: 14 | - "**.md" 15 | - "**/LICENSE" 16 | - .github/FUNDING.yml 17 | - .editorconfig 18 | - .gitignore 19 | 20 | jobs: 21 | build: 22 | name: ${{ matrix.target.name }} (${{ matrix.rust }}) 23 | runs-on: ${{ matrix.target.os }} 24 | strategy: 25 | matrix: 26 | rust: [1.81.0, stable, nightly] 27 | target: 28 | - name: Linux 29 | os: ubuntu-latest 30 | - name: macOS 31 | os: macos-latest 32 | # - name: Windows 33 | # os: windows-latest 34 | steps: 35 | - name: Checkout sources 36 | uses: actions/checkout@v4 37 | - name: Install Rust toolchain 38 | uses: dtolnay/rust-toolchain@master 39 | with: 40 | toolchain: ${{ matrix.rust }} 41 | - name: Cache cargo output 42 | uses: Swatinem/rust-cache@v2 43 | - name: Check 44 | run: cargo check --features=amalgation 45 | - name: Check no_std 46 | run: cargo check --no-default-features --features=amalgation 47 | - name: Check and Run tests 48 | run: cargo test --features amalgation 49 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | env: 4 | CARGO_INCREMENTAL: 0 5 | 6 | on: 7 | push: 8 | branches: ["**"] 9 | tags-ignore: ["**"] 10 | paths-ignore: 11 | - "**.md" 12 | - LICENSE 13 | pull_request: 14 | paths-ignore: 15 | - "**.md" 16 | - "**/LICENSE" 17 | - .github/FUNDING.yml 18 | - .editorconfig 19 | - .gitignore 20 | 21 | 22 | jobs: 23 | coverage: 24 | runs-on: ubuntu-20.04 25 | if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) || github.event_name == 'push' }} 26 | 27 | steps: 28 | - name: Checkout sources 29 | uses: actions/checkout@v4 30 | with: 31 | persist-credentials: false 32 | 33 | - name: Install Rust toolchain 34 | uses: dtolnay/rust-toolchain@master 35 | with: 36 | toolchain: nightly 37 | components: llvm-tools-preview 38 | 39 | - name: Install cargo-llvm-cov 40 | run: | 41 | curl -LsSf https://github.com/taiki-e/cargo-llvm-cov/releases/latest/download/cargo-llvm-cov-x86_64-unknown-linux-gnu.tar.gz | tar xzf - -C ~/.cargo/bin 42 | 43 | - name: Generate coverage report 44 | run: | 45 | cargo llvm-cov clean 46 | cargo llvm-cov --no-report --features nightly,amalgation -- --test-threads=1 47 | cargo llvm-cov --no-report --tests --features nightly,amalgation -- compile_fail 48 | cargo llvm-cov --no-run --lcov > lcov.txt 49 | 50 | - name: Upload coverage report 51 | uses: codecov/codecov-action@v4 52 | with: 53 | files: ./lcov.txt 54 | token: ${{ secrets.CODECOV_TOKEN }} 55 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: rustdoc 2 | on: 3 | push: 4 | branches: 5 | - dev 6 | paths-ignore: 7 | - "**.md" 8 | - LICENSE 9 | - .github/FUNDING.yml 10 | - .gitignore 11 | 12 | jobs: 13 | rustdoc: 14 | runs-on: ubuntu-latest 15 | env: 16 | RUSTFLAGS: -D warnings 17 | CARGO_INCREMENTAL: 0 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | - name: Install Rust toolchain 24 | uses: dtolnay/rust-toolchain@master 25 | with: 26 | toolchain: nightly 27 | components: rust-src 28 | 29 | - name: Cache cargo output 30 | uses: Swatinem/rust-cache@v1 31 | 32 | - name: Build Documentation 33 | run: cargo doc --features amalgation,nightly -Zrustdoc-map 34 | env: 35 | RUSTDOCFLAGS: --cfg docsrs 36 | 37 | - name: Deploy Docs 38 | uses: peaceiris/actions-gh-pages@v3 39 | with: 40 | github_token: ${{ secrets.GITHUB_TOKEN }} 41 | publish_branch: gh-pages 42 | publish_dir: ./target/doc 43 | force_orphan: true 44 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: Format test 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - "**.md" 7 | - LICENSE 8 | - .github/FUNDING.yml 9 | - .gitignore 10 | pull_request: 11 | paths-ignore: 12 | - "**.md" 13 | - "**/LICENSE" 14 | - .github/FUNDING.yml 15 | - .editorconfig 16 | - .gitignore 17 | 18 | jobs: 19 | build: 20 | name: Format 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout sources 24 | uses: actions/checkout@v4 25 | - name: Install Rust toolchain 26 | uses: dtolnay/rust-toolchain@master 27 | with: 28 | toolchain: nightly 29 | components: rustfmt 30 | - name: Check Format 31 | run: cargo fmt -- --check -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | # Cargo.lock 3 | janetrs_macros/wip/* -------------------------------------------------------------------------------- /.vscode/janetrs-snippets.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | // Place your janetrs workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and 3 | // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope 4 | // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is 5 | // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: 6 | // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. 7 | // Placeholders with the same ids are connected. 8 | // Example: 9 | // "Print to console": { 10 | // "scope": "javascript,typescript", 11 | // "prefix": "log", 12 | // "body": [ 13 | // "console.log('$1');", 14 | // "$2" 15 | // ], 16 | // "description": "Log output to console" 17 | // } 18 | 19 | "JanetRS Test": { 20 | "scope": "rust", 21 | "prefix": ["janetrstest", "test"], 22 | "description": "Make a test function", 23 | "body": [ 24 | "#[test]", 25 | "fn $1() -> Result<(), crate::client::Error> {", 26 | " let _client = crate::client::JanetClient::init()?;", 27 | " $2", 28 | " Ok(())", 29 | "}" 30 | ] 31 | }, 32 | 33 | "JanetRS Module Test": { 34 | "scope": "rust", 35 | "prefix": ["janetrstestmod", "testmod"], 36 | "description": "Make a test module", 37 | "body": [ 38 | "#[cfg(all(test, any(feature = \"amalgation\", feature = \"link-system\")))]", 39 | "mod tests {", 40 | " use super::*;", 41 | " use crate::client::JanetClient;", 42 | "", 43 | " #[test]", 44 | " fn $1() -> Result<(), crate::client::Error> {", 45 | " let _client = crate::client::JanetClient::init()?;", 46 | " $2", 47 | " Ok(())", 48 | " }", 49 | "}" 50 | ] 51 | 52 | } 53 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.extraEnv": { 3 | "CFLAGS": "", 4 | "CXXFLAGS": "" 5 | }, 6 | "rust-analyzer.checkOnSave.features": ["amalgation", "nightly"], 7 | "rust-analyzer.cargo.features": [ 8 | "amalgation", "nightly" 9 | ], 10 | "rust-analyzer.imports.preferNoStd": true, 11 | "rust-analyzer.completion.snippets.custom": { 12 | "JanetRS Test": { 13 | "prefix": ["janetrstest", "test"], 14 | "description": "Make a test function", 15 | "body": [ 16 | "#[test]", 17 | "fn $1() -> Result<(), crate::client::Error> {", 18 | "\tlet _client = crate::client::JanetClient::init()?;", 19 | "\t$2", 20 | "\tOk(())", 21 | "}" 22 | ], 23 | "scope": "item", 24 | }, 25 | 26 | "JanetRS Module Test": { 27 | "prefix": ["janetrstestmod", "testmod"], 28 | "description": "Make a test module", 29 | "body": [ 30 | "#[cfg(all(test, any(feature = \"amalgation\", feature = \"link-system\")))]", 31 | "mod tests {", 32 | "\tuse super::*;", 33 | "\tuse crate::client::JanetClient;", 34 | "", 35 | "\t#[test]", 36 | "\tfn $1() -> Result<(), crate::client::Error> {", 37 | "\t\tlet _client = crate::client::JanetClient::init()?;", 38 | "\t\t$2", 39 | "\t\tOk(())", 40 | "\t}", 41 | "}" 42 | ], 43 | "scope": "item", 44 | } 45 | }, 46 | "cSpell.ignoreWords": [ 47 | "abcdefg", 48 | "barfoz", 49 | "bstr", 50 | "cstr", 51 | "cstring", 52 | "ebar", 53 | "f", 54 | "fbar", 55 | "ffbar", 56 | "indexmut", 57 | "janetrs", 58 | "jpanic", 59 | "lion", 60 | "nbaz", 61 | "nquux", 62 | "orld", 63 | "pushs", 64 | "quux", 65 | "rabz", 66 | "rfind", 67 | "rsplit", 68 | "rsplitn", 69 | "setcount", 70 | "splitn", 71 | "tbaz", 72 | "x", 73 | "xleopard", 74 | "xtiger", 75 | "αβγγδ" 76 | ], 77 | "cSpell.words": [ 78 | "amalgation", 79 | "repr" 80 | ], 81 | "FSharp.suggestGitignore": false 82 | } 83 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to the library should be put here 4 | 5 | ## 0.8.0 6 | 7 | - **Breaking** Refactor: Rename `JanetArgs::get_unwrapped` to `JanetArgs::try_get` 8 | - **Breaking** Refactor: Rename `JanetArgs::get_panic` to `JanetArgs::get_or_panic` 9 | - **Breaking** Refactor: Turn `JanetConversionError` into a enum 10 | - **Breaking** Refactor: Refactor `CFunOptions` to use `CStr` instead of `str` 11 | - **Breaking** Refactor: Use `usize` for length/index/capacity in collections 12 | - **Breaking** Refactor: Make `IsJanetAbstract` an unsafe trait 13 | - **Breaking** Feat: Make `amalgation` feature enabled by default 14 | - **Breaking** Feat: Make `#[janet_fn]` maintain the high-level function in scope 15 | - Feat: Add `Janet::dynamic_from_cstr` constructor 16 | - Feat: Add `JanetArgs::get_value` and `JanetArgs::get_tagged` trait methods 17 | - Feat: Add `JanetArgs::get_or_default` trait method 18 | - Feat: Add `JanetArgs::get_or_else` trait method 19 | - Feat: Add `JanetArgs::get_matches` and `JanetArgs::get_tagged_matches` 20 | - Feat: Add `JanetArray::pop_if` method 21 | - Feat: Add `JanetArray::push_within_capacity` method 22 | - Feat: Add `JanetArray::extract_if` method 23 | - Feat: Add `JanetArray::split_off` method 24 | - Feat: Add `JanetArray::swap_unchecked` method 25 | - Feat: Add `JanetArray::retain` and `JanetArray::retain_mut` methods 26 | - Feat: Add `JanetArray::get_range`, `JanetArray::get_range_mut`, `JanetArray::get_range_unchecked` and , `JanetArray::get_range_unchecked_mut` methods 27 | - Feat: Add `JanetArray::weak` and `JanetArray::weak_with_capacity` constructors 28 | - Feat: Add `JanetBuffer::push_janet_string` method 29 | - Feat: Add `assert_deep_eq!` macro 30 | - Feat: Add `assert_deep_ne!` macro 31 | - Feat: Add `env::set_dynamic` function 32 | - Feat: Implement `JanetArgs` for `[Janet; N]` 33 | - Feat: Add conversion from `Janet` to `isize` and `usize` 34 | - Feat: Add conversion from `isize`/`usize` to `Janet` 35 | - Feat: Make conversion from `Janet` to `i64` and `u64` accept when Janet is Number 36 | - Feat: Make conversion from `Janet` to `i64`, `u64`, `usize`, `isize` accept when Janet is String 37 | - Feat: Always have `#[no_std]` and import std when `std` feature is activated 38 | - Perf: Avoid allocation in `Janet::dynamic` if the passed argument is already null terminated 39 | - Refactor: Use default implementation on `JanetArgs` trait for most methods 40 | - Refactor: Simplify `jpanic!` macro 41 | - Refactor: janetrs_macros 0.7.0 — Update `syn` crate to 2.0 42 | - Refactor: Use `core::error` 43 | - Fix: janetrs_macros 0.7.1 — Fix `janet_fn` attribute macro not generating Janet docstring correctly 44 | - Fix: janetrs_version — Add missed feature flag `inline-more` 45 | - Fix: Fix conversion from `Janet` to 32 bit integers 46 | - Fix: Fix bounds checking for `JanetArray::insert` 47 | - Fix: Fix adding C functions to environment/client with namespace 48 | 49 | ## 0.7.0 50 | 51 | - **BREAKING:** Refactor: Rename `JanetArgs::get_unwraped` to `JanetArgs::get_unwrapped` 52 | - **BREAKING:** Refactor: Rename `FileFlags::is_serializeble` to `FileFlags::is_serializable` 53 | - Feat: Add `JanetAbstract::take` method 54 | - Feat: Add `JanetAbstract::into_inner` method 55 | - Feat: Add `JanetTuple::{sourcemap, set_sourcemap}` methods 56 | - Refactor: Simplify `JanetAbstract::new` 57 | - Fix: Fix undefined behavior in `JanetArray::as_{ref,mut}` methods 58 | - Fix: Fix clippy lints 59 | - docs: Update a few documentation comments 60 | 61 | ## 0.6.0 62 | 63 | - **BREAKING:** Feat: Conditionally expose `JanetFile` "piped" flag (PIPED was removed in Janet 1.22.0) 64 | - **BREAKING:** Refactor: Changed definition of `IsJanetAbstract` trait 65 | - **BREAKING:** Refactor: Changed the return type of `JanetAbstract::get{_mut, _unchecked, _unchecked_mut}` 66 | - **BREAKING:** Feat: Move everything possible to `C-unwind` 67 | - **BREAKING:** Up Minimum Rust version to 1.71.0 68 | - Feat: Add additional implementation of `From` implementation for 69 | `JanetBuffer`, `JanetArray` and `JanetTable` 70 | - Feat: Expose `JanetBuffer` methods that use `CStr` on no_std environment 71 | - Feat: Expose `JanetFile` "update" flag 72 | - Feat: Expose more of the String-like types API on no_std environment 73 | - Feat: Add method `can_resume` for `JanetFiber` 74 | - Refactor: Simplify the `tuple!` and `structs!` macros 75 | - Refactor: Simplify a few `PartialEq` implementations 76 | - Refactor: Simplify `JanetStruct` implementation of `Clone` 77 | - Refactor: Adapt lifetimes to changes on bstr crate 78 | - Refactor: Modernize format strings 79 | - Fix: Fix compilation when `unicode` feature os off 80 | - Fix: Fix `check_mut_ref` on `janet_fn` attribute macro 81 | - Fix: Fix linking on non x86_64 targets 82 | - Docs: Improve documentation flags 83 | - Docs: Simplify links 84 | - CI: Many CI improvements 85 | 86 | ## 0.5.0 87 | 88 | - **BREAKING:** Rename `JanetClient::with_default_env` -> 89 | `JanetClient::load_env_default` 90 | - **BREAKING:** Rename `JanetClient::with_replacements` -> 91 | `JanetClient::load_env_replacements` 92 | - Add `catch` arg to `janet_fn` attribute macro that adds code to catch Rust 93 | panics and transform them into Janet panics 94 | - Add two new functions to initialize `JanetClient`: 95 | `JanetClient::init_with_replacements` and `JanetClient::init_with_default_env` 96 | - Add `JanetFile::temp` 97 | - Fix `tuple::{IntoIter, Iter}::size_hint` implementation 98 | - Migrate to Rust 2021 99 | 100 | ## 0.4.1 101 | 102 | - Add the trait `JanetArgs` that extend functionality of `[Janet]` used in Rust 103 | defined Janet functions 104 | - Add the trait `JanetTypeName` that defines the name of the types displayed 105 | janet messages 106 | - Add `bad_slot!` macro to shorten and help developing Rust defined Janet 107 | functions 108 | 109 | ## 0.4.0 110 | 111 | - **BREAKING:** Make `JanetGc::collect` an unsafe function 112 | - **BREAKING:** Remove `JanetEnvironment::add_def_with_doc`, 113 | `JanetEnvironment::add_var_with_doc`, `JanetEnvironment::add_c_func_with_doc` 114 | and `JanetClient` functions with the same names 115 | - **BREAKING:** Remove `util::def`, `util::var`, `util::c_func` 116 | - **BREAKING:** Rename `JanetEnviornment::add_c_func` to 117 | `JanetEnvironment::add_c_fn` `JanetEnvironment::add_c_fn` 118 | - Add `JanetFile` type 119 | - Add `JanetRng` type 120 | - Add `JanetTable::try_insert` and related error type 121 | - Add `DefOptions`, `VarOptions`, `CFunOptions` to interact with the Janet 122 | environment 123 | - Add `declare_janet_mod` macro to generate the machinery that Janet requires do 124 | create a Janet native module 125 | - It satisfies the same purpose as `janet_mod`, but it can get the 126 | documentation string from the function doc-comments and, for Janet versions 127 | above 1.17.0, it also add source map information for Janet 128 | - Add `janet_abstract::register` function to register an abstract type. 129 | - Add option to `janet_fn` attribute macro to include arity checks 130 | - Add `Janet::unwrap_or`, `Janet::unwrap_or_else` and `Janet::unwrap_or_default` 131 | - Implement `Display` for `TaggedJanet` and defer the `Janet` display 132 | implementation to that 133 | - Improve error report of attribute macros 134 | - Refactor the `janet_fn` attribute macro parameter parsing 135 | - Refactor the `JanetEnvironment` and `JanetClient` API 136 | - `janet_fn` now emits code with the function documentation and source map 137 | information as constants to be used by another macro `declare_janet_mod` 138 | - Fix compilation when no_std and with unicode feature enabled 139 | 140 | ## 0.3.2 141 | 142 | - Add `JanetTable::clear` in Janet version below 1.10.1 143 | 144 | ## 0.3.1 145 | 146 | ### Fixes 147 | 148 | - Fix compiler complaining about alloc crate when `std` feature is active while 149 | using a macro 150 | 151 | ## 0.3.0 152 | 153 | ### Changes 154 | 155 | - **BREAKING:** Rename `as_ptr_mut` to `as_mut_ptr` 156 | - **BREAKING:** Rename `as_raw_mut` to `as_mut_raw` 157 | - **BREAKING:** `JanetAbstract::new` now takes a value 158 | - **BREAKING:** Make the `janetrs::types` module private and export everything 159 | inside it in the upper module 160 | - **BREAKING:** Modify `From<&str>` for `Janet` to return a Janet keyword if 161 | `&str` starts with `:` 162 | - **BREAKING:** Modify `CallError::stacktrace` function. 163 | - Add ability to change some Janet behavior using the `amalgation` feature using 164 | environment variables 165 | - Add `DeepEq` trait 166 | - Add `dedup`, `dedup_by` and `dedup_by_key` for `JanetArray` 167 | - Add `get_unchecked` and `get_unchecked_mut` for `JanetArray` 168 | - Add `get_unchecked` for `JanetTuple` 169 | - Add `get_method` and `has_method` to `Janet` 170 | - Add `prototype`, `set_prototype` and `with_prototype` methods for `JanetTable` 171 | - Add `get_key_value_proto{_mut}` and `get_proto{_mut}` methods for `JanetTable` 172 | - Add `JanetGc` and `JanetGcLockGuard` types to access some Janet GC operations 173 | - Add `JanetGcRootGuard` and the functions `JanetGc::root` and `JanetGc::unroot` 174 | to root a Janet object to the GC 175 | - Add functions to get reference to a `JanetAbstract` data safely 176 | - Add `JanetAbstract::is` 177 | - Add `Janet::int64` 178 | - Add `Janet::uint64` 179 | - Create `janetrs_version` crate to use as common code used by `janet_version` 180 | macro and `janetrs::util` module 181 | - Implement `DeepEq` for most types 182 | - Implement `Debug` and `Display` for `JanetSymbol` 183 | - Implement `Debug` and `Display` for `JanetKeyword` 184 | - Implement `IsJanetAbstract` for i64 and u64 185 | - Implement `PartialEq`, `Eq`, `PartialOrd` and `Ord` for `JanetAbstract` 186 | - Implement `PartialEq`, `Eq`, `PartialOrd` and `Ord` for `JanetFunction` 187 | - Implement `PartialOrd` and `Ord` for `JanetFiber` 188 | - Implement `From` and `TryFrom` between `i64` and `Janet` 189 | - Implement `From` and `TryFrom` between `u64` and `Janet` 190 | - Include "@" before the debug representation of Janet mutable types 191 | - Refactor `Debug` implementation of `Janet` type 192 | - Refactor `Display` implementation of `Janet` type 193 | - Refactor some implementations of `From` and `TryFrom` related to `Janet` type 194 | - Reduce code duplication in `JanetAbstract` functions 195 | 196 | ### Fixes 197 | 198 | - **BREAKING:** Change definition of `IsJanetAbstract` trait 199 | - Expose `jcatch!` macro only if Janet version supports the underlying mechanism 200 | - Fix some clippy lints 201 | - Fix compilation on no_std environment. 202 | - Make some functions const if using a recent enough Rust version 203 | 204 | ## 0.2.0 205 | 206 | ### Changes 207 | 208 | - **BREAKING:** Add `Janet::unwrap` that return `TaggedJanet` 209 | - **BREAKING:** Rename `Janet::unwrap` to `Janet::try_unwrap` 210 | - Add `JanetEnvironment` type 211 | - Add `janet_version`/`cjvg` attribute macros for conditional compilation of 212 | Janet versions 213 | - Add split iterator for `JanetBuffer` and `JanetString` 214 | - Add `jcatch` declarative macro 215 | - Refactor `JanetClient` in terms of `JanetEnvironment` 216 | - Implement `TaggetJanet` type 217 | - Implement `JanetAbstract` type 218 | - Implement `JanetPointer` type 219 | - Implement `JanetTryState` for Janet "exception" recovery 220 | - Implement `PartialEq`, `Eq`, `PartialOrd` and `Ord` for several Janet types 221 | - `janet_fn` now can accept a parameter `check_mut_ref` that checks if the 222 | function received more than one `*mut` pointer as parameter (not the default 223 | because Janet types are like interior mutability types) 224 | - More methods added for several types and improvements to the docs 225 | 226 | ### Bug Fixes 227 | 228 | - Fix change in behavior in `JanetBuffer` since Janet 1.13.0 and also enforce 229 | that on earlier versions 230 | - Fix UB in `JanetTryState` safe API 231 | - Fix `Default` implementation for `JanetEnvironment` 232 | - Fix `JanetTuple` implementation of `PartialEq` to match the Janet 233 | implementation 234 | 235 | ## 0.1.2 236 | 237 | ### Changes 238 | 239 | - Implement Display for `JanetType` 240 | 241 | ### Bug Fixes 242 | 243 | - Fix `From` for `JanetString` not considering that char can be 244 | represented with more than 1 byte in UTF-8 245 | 246 | ## 0.1.0 ~ 0.1.1 247 | 248 | ### Changes 249 | 250 | - Basic Janet types manipulation 251 | - A way to run the Janet runtime 252 | - Macros to create Janet collections 253 | - Macro to cause Janet Panics 254 | - Macro to catch Rust Panic and transform to Janet Panic 255 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "bindgen" 16 | version = "0.70.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" 19 | dependencies = [ 20 | "bitflags", 21 | "cexpr", 22 | "clang-sys", 23 | "itertools", 24 | "log", 25 | "prettyplease", 26 | "proc-macro2", 27 | "quote", 28 | "regex", 29 | "rustc-hash", 30 | "shlex", 31 | "syn", 32 | ] 33 | 34 | [[package]] 35 | name = "bitflags" 36 | version = "2.8.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 39 | 40 | [[package]] 41 | name = "bstr" 42 | version = "1.11.3" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" 45 | dependencies = [ 46 | "memchr", 47 | "regex-automata", 48 | "serde", 49 | ] 50 | 51 | [[package]] 52 | name = "cc" 53 | version = "1.2.10" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" 56 | dependencies = [ 57 | "shlex", 58 | ] 59 | 60 | [[package]] 61 | name = "cexpr" 62 | version = "0.6.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 65 | dependencies = [ 66 | "nom", 67 | ] 68 | 69 | [[package]] 70 | name = "cfg-if" 71 | version = "1.0.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 74 | 75 | [[package]] 76 | name = "clang-sys" 77 | version = "1.8.1" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 80 | dependencies = [ 81 | "glob", 82 | "libc", 83 | "libloading", 84 | ] 85 | 86 | [[package]] 87 | name = "dissimilar" 88 | version = "1.0.9" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" 91 | 92 | [[package]] 93 | name = "either" 94 | version = "1.13.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 97 | 98 | [[package]] 99 | name = "equivalent" 100 | version = "1.0.1" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 103 | 104 | [[package]] 105 | name = "evil-janet" 106 | version = "1.37.2" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "5e10724408052d8c9f0bb9d920597c81ed7408a165f2cd7273eee83ee254ecfe" 109 | dependencies = [ 110 | "bindgen", 111 | "cc", 112 | "libc", 113 | ] 114 | 115 | [[package]] 116 | name = "glob" 117 | version = "0.3.2" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 120 | 121 | [[package]] 122 | name = "hashbrown" 123 | version = "0.15.2" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 126 | 127 | [[package]] 128 | name = "indexmap" 129 | version = "2.7.1" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 132 | dependencies = [ 133 | "equivalent", 134 | "hashbrown", 135 | ] 136 | 137 | [[package]] 138 | name = "itertools" 139 | version = "0.13.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 142 | dependencies = [ 143 | "either", 144 | ] 145 | 146 | [[package]] 147 | name = "itoa" 148 | version = "1.0.14" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 151 | 152 | [[package]] 153 | name = "janetrs" 154 | version = "0.8.0" 155 | dependencies = [ 156 | "bstr", 157 | "evil-janet", 158 | "janetrs_macros", 159 | "janetrs_version", 160 | "libc", 161 | "rand_core", 162 | ] 163 | 164 | [[package]] 165 | name = "janetrs_macros" 166 | version = "0.7.3" 167 | dependencies = [ 168 | "janetrs", 169 | "janetrs_version", 170 | "proc-macro2", 171 | "quote", 172 | "syn", 173 | "trybuild", 174 | ] 175 | 176 | [[package]] 177 | name = "janetrs_version" 178 | version = "0.1.0" 179 | dependencies = [ 180 | "evil-janet", 181 | ] 182 | 183 | [[package]] 184 | name = "libc" 185 | version = "0.2.169" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 188 | 189 | [[package]] 190 | name = "libloading" 191 | version = "0.8.6" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 194 | dependencies = [ 195 | "cfg-if", 196 | "windows-targets", 197 | ] 198 | 199 | [[package]] 200 | name = "log" 201 | version = "0.4.25" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 204 | 205 | [[package]] 206 | name = "memchr" 207 | version = "2.7.4" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 210 | 211 | [[package]] 212 | name = "minimal-lexical" 213 | version = "0.2.1" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 216 | 217 | [[package]] 218 | name = "nom" 219 | version = "7.1.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 222 | dependencies = [ 223 | "memchr", 224 | "minimal-lexical", 225 | ] 226 | 227 | [[package]] 228 | name = "prettyplease" 229 | version = "0.2.29" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" 232 | dependencies = [ 233 | "proc-macro2", 234 | "syn", 235 | ] 236 | 237 | [[package]] 238 | name = "proc-macro2" 239 | version = "1.0.93" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 242 | dependencies = [ 243 | "unicode-ident", 244 | ] 245 | 246 | [[package]] 247 | name = "quote" 248 | version = "1.0.38" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 251 | dependencies = [ 252 | "proc-macro2", 253 | ] 254 | 255 | [[package]] 256 | name = "rand_core" 257 | version = "0.6.4" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 260 | 261 | [[package]] 262 | name = "regex" 263 | version = "1.11.1" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 266 | dependencies = [ 267 | "aho-corasick", 268 | "memchr", 269 | "regex-automata", 270 | "regex-syntax", 271 | ] 272 | 273 | [[package]] 274 | name = "regex-automata" 275 | version = "0.4.9" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 278 | dependencies = [ 279 | "aho-corasick", 280 | "memchr", 281 | "regex-syntax", 282 | ] 283 | 284 | [[package]] 285 | name = "regex-syntax" 286 | version = "0.8.5" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 289 | 290 | [[package]] 291 | name = "rustc-hash" 292 | version = "1.1.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 295 | 296 | [[package]] 297 | name = "ryu" 298 | version = "1.0.18" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 301 | 302 | [[package]] 303 | name = "serde" 304 | version = "1.0.217" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 307 | dependencies = [ 308 | "serde_derive", 309 | ] 310 | 311 | [[package]] 312 | name = "serde_derive" 313 | version = "1.0.217" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 316 | dependencies = [ 317 | "proc-macro2", 318 | "quote", 319 | "syn", 320 | ] 321 | 322 | [[package]] 323 | name = "serde_json" 324 | version = "1.0.137" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" 327 | dependencies = [ 328 | "itoa", 329 | "memchr", 330 | "ryu", 331 | "serde", 332 | ] 333 | 334 | [[package]] 335 | name = "serde_spanned" 336 | version = "0.6.8" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 339 | dependencies = [ 340 | "serde", 341 | ] 342 | 343 | [[package]] 344 | name = "shlex" 345 | version = "1.3.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 348 | 349 | [[package]] 350 | name = "syn" 351 | version = "2.0.96" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" 354 | dependencies = [ 355 | "proc-macro2", 356 | "quote", 357 | "unicode-ident", 358 | ] 359 | 360 | [[package]] 361 | name = "target-triple" 362 | version = "0.1.3" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" 365 | 366 | [[package]] 367 | name = "termcolor" 368 | version = "1.4.1" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 371 | dependencies = [ 372 | "winapi-util", 373 | ] 374 | 375 | [[package]] 376 | name = "toml" 377 | version = "0.8.19" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 380 | dependencies = [ 381 | "serde", 382 | "serde_spanned", 383 | "toml_datetime", 384 | "toml_edit", 385 | ] 386 | 387 | [[package]] 388 | name = "toml_datetime" 389 | version = "0.6.8" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 392 | dependencies = [ 393 | "serde", 394 | ] 395 | 396 | [[package]] 397 | name = "toml_edit" 398 | version = "0.22.22" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 401 | dependencies = [ 402 | "indexmap", 403 | "serde", 404 | "serde_spanned", 405 | "toml_datetime", 406 | "winnow", 407 | ] 408 | 409 | [[package]] 410 | name = "trybuild" 411 | version = "1.0.102" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "9f14b5c02a137632f68194ec657ecb92304138948e8957c932127eb1b58c23be" 414 | dependencies = [ 415 | "dissimilar", 416 | "glob", 417 | "serde", 418 | "serde_derive", 419 | "serde_json", 420 | "target-triple", 421 | "termcolor", 422 | "toml", 423 | ] 424 | 425 | [[package]] 426 | name = "unicode-ident" 427 | version = "1.0.14" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 430 | 431 | [[package]] 432 | name = "winapi-util" 433 | version = "0.1.9" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 436 | dependencies = [ 437 | "windows-sys", 438 | ] 439 | 440 | [[package]] 441 | name = "windows-sys" 442 | version = "0.59.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 445 | dependencies = [ 446 | "windows-targets", 447 | ] 448 | 449 | [[package]] 450 | name = "windows-targets" 451 | version = "0.52.6" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 454 | dependencies = [ 455 | "windows_aarch64_gnullvm", 456 | "windows_aarch64_msvc", 457 | "windows_i686_gnu", 458 | "windows_i686_gnullvm", 459 | "windows_i686_msvc", 460 | "windows_x86_64_gnu", 461 | "windows_x86_64_gnullvm", 462 | "windows_x86_64_msvc", 463 | ] 464 | 465 | [[package]] 466 | name = "windows_aarch64_gnullvm" 467 | version = "0.52.6" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 470 | 471 | [[package]] 472 | name = "windows_aarch64_msvc" 473 | version = "0.52.6" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 476 | 477 | [[package]] 478 | name = "windows_i686_gnu" 479 | version = "0.52.6" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 482 | 483 | [[package]] 484 | name = "windows_i686_gnullvm" 485 | version = "0.52.6" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 488 | 489 | [[package]] 490 | name = "windows_i686_msvc" 491 | version = "0.52.6" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 494 | 495 | [[package]] 496 | name = "windows_x86_64_gnu" 497 | version = "0.52.6" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 500 | 501 | [[package]] 502 | name = "windows_x86_64_gnullvm" 503 | version = "0.52.6" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 506 | 507 | [[package]] 508 | name = "windows_x86_64_msvc" 509 | version = "0.52.6" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 512 | 513 | [[package]] 514 | name = "winnow" 515 | version = "0.6.24" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" 518 | dependencies = [ 519 | "memchr", 520 | ] 521 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "janetrs" 3 | version = "0.8.0" 4 | authors = ["Eric Shimizu Karbstein "] 5 | description = "High level binding for Janet programming language" 6 | repository = "https://github.com/GrayJack/janetrs" 7 | homepage = "https://crates.io/crates/janetrs" 8 | documentation = "https://docs.rs/janetrs" 9 | license-file = "LICENSE" 10 | readme = "README.md" 11 | edition = "2021" 12 | rust-version = "1.81.0" 13 | keywords = ["janet", "embedding", "ffi"] 14 | categories = ["api-bindings"] 15 | exclude = ["/.github", "/.vscode"] 16 | 17 | [package.metadata.docs.rs] 18 | targets = [ 19 | "x86_64-unknown-linux-gnu", 20 | "x86_64-unknown-freebsd", 21 | "x86_64-unknown-netbsd", 22 | "x86_64-apple-darwin", 23 | "aarch64-apple-darwin", 24 | "x86_64-unknown-illumos", 25 | "aarch64-unknown-linux-gnu", 26 | "i686-unknown-linux-gnu", 27 | "x86_64-pc-windows-gnu", 28 | "x86_64-pc-windows-msvc", 29 | ] 30 | all-features = false 31 | features = ["amalgation", "nightly"] 32 | rustdoc-args = ["--cfg", "docsrs"] 33 | 34 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 35 | 36 | [dependencies] 37 | bstr = { version = "1.0.0", default-features = false, features = ["alloc"] } 38 | evil-janet = "1" 39 | janetrs_macros = { version = "0.7.3", path = "janetrs_macros" } 40 | janetrs_version = { version = "0.1.0", path = "janetrs_version" } 41 | libc = { version = "0.2", default-features = false, features = [ 42 | "extra_traits", 43 | ] } 44 | rand_core = "0.6" 45 | 46 | [dev-dependencies] 47 | 48 | [features] 49 | default = ["std", "unicode", "amalgation"] 50 | # To use Error trait and allocations 51 | std = ["bstr/std", "janetrs_macros/std", "libc/std"] 52 | # Use to statically link to janet runtime and have Janet client 53 | amalgation = ["evil-janet/link-amalg"] 54 | # Inline more functions 55 | inline-more = [] 56 | # Unicode conveniences 57 | unicode = ["bstr/unicode"] 58 | # Use system janet.h 59 | system = [ 60 | "evil-janet/system", 61 | "janetrs_macros/system", 62 | "janetrs_version/system", 63 | ] 64 | # Use to link to system libjanet (not recommended generally) 65 | link-system = [ 66 | "evil-janet/link-system", 67 | "janetrs_macros/system", 68 | "janetrs_version/system", 69 | ] 70 | # Crate features that depends of a nightly gated features 71 | nightly = [] 72 | 73 | [workspace] 74 | members = ["janetrs_macros", "janetrs_version"] 75 | 76 | [[example]] 77 | name = "hello_world" 78 | required-features = ["amalgation"] 79 | 80 | [[example]] 81 | name = "janet_native_module" 82 | crate-type = ["cdylib", "staticlib"] 83 | required-features = ["amalgation"] 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Eric Shimizu Karbstein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JanetRS 2 | 3 | [![Hits-of-Code](https://hitsofcode.com/github/grayjack/janetrs?branch=dev)](https://hitsofcode.com/view/github/grayjack/janetrs?branch=dev) 4 | [![Build Status](https://github.com/GrayJack/janetrs/workflows/Check%20and%20Test/badge.svg)](https://github.com/GrayJack/janetrs/actions) 5 | [![Crates.io](https://img.shields.io/crates/v/janetrs?logo=rust)](https://crates.io/crates/janetrs) 6 | [![Docs latest release](https://img.shields.io/static/v1?label=Docs&message=latest&color=blue&logo=rust)](https://docs.rs/janetrs/) 7 | [![Docs dev branch](https://img.shields.io/static/v1?label=Docs&message=dev&color=lightgray&logo=rust)](https://grayjack.github.io/janetrs/janetrs/index.html) 8 | [![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENCE) 9 | 10 | A crate with high level bindings to Janet C API. 11 | 12 | ## Goals 13 | 14 | Provide a safe and ergonomic interface to the Janet C API to create Janet 15 | clients and Janet modules/libraries using Rust. 16 | 17 | This project still are in it's early stages, so breaking changes may happen, 18 | there is no minimal supported Rust version (MSRV) yet. 19 | 20 | 21 | ## Cargo Features 22 | 23 | - `std`: Enable some trait impl for types that only exist on the `std` and the 24 | Error trait 25 | - `unicode`: Enable more methods for JanetString and JanetBuffer 26 | - `inline-more`: More aggressive inlining 27 | - `amalgation`: Link the Janet runtime to the package, enabling to use the 28 | client module 29 | - `unicode`: Enable some unicode methods for JanetString and JanetBuffer 30 | - `system`: Use system header to get Janet functions 31 | - `link-system`: Link the Janet runtime to the package from the system, enabling 32 | to use the client module 33 | - `nightly`: Enable some parts of the crate that uses nightly features, to use 34 | this feature you must compile the crate using a nightly rust version 35 | 36 | By default, the following features are enabled: `std`, `unicode`, `amalgation`. 37 | 38 | **Note:** If you don't need the Janet runtime (eg. making a Janet Library), you can use disable the default features 39 | 40 | ```toml 41 | janetrs = { version = "0.7.0", default-features = false, features = ["std", "unicode"] } 42 | ``` 43 | 44 | ## Environment variables 45 | 46 | **These variables are only used when the `amalgation` feature is enabled** 47 | 48 | It is possible to use environment variables to overwrite some Janet definitions. 49 | 50 | - `JANET_RECURSION_GUARD=` 51 | - `JANET_MAX_PROTO_DEPTH=` 52 | - `JANET_MAX_MACRO_EXPAND=` 53 | - `JANET_STACK_MAX=` 54 | 55 | ## Licensing 56 | 57 | This software is licensed under the terms of the 58 | [MIT Public License](./LICENSE). 59 | 60 | ### TODO: Types: Lacking or Incomplete 61 | 62 | - [ ] Marshaling 63 | 64 | `[ ]: Lacking` `[I]: Incomplete` `[X]: Done` 65 | 66 | Probably there is much more missing, for that you can use the `lowlevel` module 67 | to access the raw C API of Janet 68 | 69 | ### TODO: Lib level 70 | 71 | - Better docs. 72 | - Marshalling mechanism 73 | 74 | ## Acknowledgments 75 | 76 | - [Calvin Rose](https://github.com/bakpakin) for creating this amazing language 77 | called Janet 78 | - [andrewchambers](https://github.com/andrewchambers) for janet_ll crate and 79 | discuss with us some ideas for the abstractions of this crate 80 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "Format", 3 | "Linux (1.60.0)", 4 | "Linux (stable)", 5 | "MacOS (1.60.0)", 6 | "MacOS (stable)", 7 | ] 8 | delete_merged_branches = true 9 | -------------------------------------------------------------------------------- /examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | use janetrs::{ 2 | Janet, JanetArgs, 3 | client::{Error, JanetClient}, 4 | env::CFunOptions, 5 | }; 6 | 7 | #[janetrs::janet_fn(arity(fix(1)))] 8 | fn testing(args: &mut [Janet]) -> Janet { 9 | use janetrs::JanetType::*; 10 | 11 | let arg = args.get_matches(0, &[Abstract, Buffer]); 12 | 13 | dbg!(arg); 14 | 15 | Janet::nil() 16 | } 17 | 18 | fn main() -> Result<(), Error> { 19 | let mut client = JanetClient::init_with_default_env()?; 20 | 21 | client.run("(print `Hello from Janet!`)")?; 22 | 23 | client.add_c_fn(CFunOptions::new(c"testing", testing_c)); 24 | 25 | // let out = client.run("(+ 2 2)")?; 26 | 27 | let out = client.run("(testing nil)")?; 28 | 29 | println!("{out}"); 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /examples/janet_native_module.rs: -------------------------------------------------------------------------------- 1 | //! For a more complete example to create a Janet package with jpm, check out [this 2 | //! template repository](https://github.com/GrayJack/rust-janet-module-template) 3 | 4 | use janetrs::{Janet, JanetArgs, JanetTuple, TaggedJanet, declare_janet_mod, janet_fn}; 5 | 6 | /// (template/hello) 7 | /// 8 | /// Rust say hello 9 | #[janet_fn(arity(fix(0)))] 10 | pub fn rust_hello(_args: &mut [Janet]) -> Janet { 11 | println!("Hello from Rust!"); 12 | Janet::nil() 13 | } 14 | 15 | /// (template/chars) 16 | /// 17 | /// If the argument is a buffer or string, return a array or tuple of the chars of the 18 | /// argument, else return nil 19 | #[janet_fn(arity(fix(1)))] 20 | pub fn chars(args: &mut [Janet]) -> Janet { 21 | use janetrs::JanetType::*; 22 | 23 | match args.get_tagged_matches(0, &[Buffer, String]) { 24 | TaggedJanet::Buffer(b) => b.chars().collect::().into(), 25 | TaggedJanet::String(s) => s.chars().collect::().into(), 26 | _ => unreachable!("Already checked to be a buffer|string"), 27 | } 28 | } 29 | 30 | declare_janet_mod!("template"; 31 | {"hello", rust_hello}, 32 | {"chars", chars}, 33 | ); 34 | -------------------------------------------------------------------------------- /janetrs_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "janetrs_macros" 3 | version = "0.7.3" 4 | authors = ["Eric Shimizu Karbstein "] 5 | description = "Attribute macros for JanetRS" 6 | repository = "https://github.com/GrayJack/janetrs" 7 | homepage = "https://crates.io/crates/janetrs_macros" 8 | documentation = "https://docs.rs/janetrs_macros" 9 | license-file = "../LICENSE" 10 | edition = "2021" 11 | rust-version = "1.71.0" 12 | keywords = ["janet", "embedding", "proc-macro"] 13 | categories = ["development-tools::procedural-macro-helpers"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [lib] 18 | name = "janetrs_macros" 19 | path = "src/lib.rs" 20 | proc-macro = true 21 | 22 | [dependencies] 23 | syn = { version = "2.0.60", features = ["extra-traits", "full"] } 24 | quote = "1" 25 | proc-macro2 = "1" 26 | janetrs_version = { version = "0.1", path = "../janetrs_version" } 27 | 28 | [features] 29 | default = [] 30 | std = [] 31 | system = ["janetrs_version/system"] 32 | 33 | [[test]] 34 | name = "tests" 35 | path = "tests/progress.rs" 36 | 37 | [dev-dependencies] 38 | trybuild = { version = "1.0", features = ["diff"] } 39 | janetrs = { path = "../../janetrs" } 40 | -------------------------------------------------------------------------------- /janetrs_macros/README.md: -------------------------------------------------------------------------------- 1 | # janetrs_macros 2 | 3 | A proc-macro crate for JanetRS 4 | 5 | ## Macros 6 | 7 | ### janet_fn 8 | 9 | **Usages**: 10 | 11 | - `#[janet_fn]` 12 | - `#[janet_fn(arity(fix()))]` where `N` is an integer literal 13 | - `#[janet_fn(arity(range( [, MAX])))]` where `MIN` and `MAX` are integer 14 | literals 15 | - `#[janet_fn(check_mut_ref)]` 16 | - `#[janet_fn(arity(<...>), check_mut_ref)]` Combining both 17 | 18 | Macro that tranforms a high-level Janet function (`fn(&mut [Janet]) -> Janet`) 19 | to the thing the Janet C API is expecting 20 | (`fn(i32, *mut janetrs::lowlevel::Janet) -> janetrs::lowlevel::Janet`) 21 | 22 | The optional argument `arity` adds a arity check for the function. It must 23 | receive the kind of arity check. These kinds are `fix`, for fixed arity, and 24 | `range`, for ranged or variadic arity. The `fix` kind receives a integer of the 25 | number of the parameters the Janet function must have and the `range` kind can 26 | receive two arguments, the first one if mandatory while the second one is 27 | optional, the first represents the minimal of arguments the Janet function have 28 | to receive and the second represents the maximum of arguments the Janet function 29 | can receive. If the maximum is not set for the range arity, the maximum check is 30 | disabled, allowing variadic arguments. 31 | 32 | The optional arg `check_mut_ref` adds a check to see if the function received 33 | more than one reference to the same `*mut` pointer. This check is not the 34 | default because Janet Types act like types with interior mutability and the 35 | check is expensive, but if you want to make sure that your function never 36 | receives the same pointer more than once you can use this. 37 | 38 | ### Conditional Janet Version Gate 39 | 40 | **Usages:** 41 | 42 | - `#[cjvg(, [MAX_VERSION])]` where `MIN_VERSION` and `MAX_VERSION` 43 | are string literals. 44 | - `#[janet_version(, [MAX_VERSION])]` where `MIN_VERSION` and 45 | `MAX_VERSION` are string literals. 46 | 47 | A macro da conditionally includes the `input` if the version of Janet is bigger 48 | or equal to the passed minimal version and smaller than the passed maximum 49 | version. 50 | 51 | That means that the range is open in the maximum version: [MIN_VERSION, 52 | MAX_VERSION). 53 | 54 | ### Janet Module Declaration 55 | 56 | **Usages:** 57 | 58 | ```rust 59 | use janetrs::{janet_mod, Janet, janet_fn}; 60 | 61 | /// (rust/hello) 62 | /// 63 | /// Rust says hello to you! 🦀 64 | #[janet_fn(arity(fix(0)))] 65 | fn rust_hello(args: &mut [Janet]) -> Janet { 66 | println!("Hello from Rust!"); 67 | Janet::nil() 68 | } 69 | 70 | /// (rust/hi) 71 | /// 72 | /// I introducing myself to you! 🙆 73 | #[janet_fn(arity(fix(0)))] 74 | fn hi(args: &mut [Janet]) -> Janet { 75 | Janet::from("Hi! My name is GrayJack!") 76 | } 77 | 78 | #[janet_fn(arity(fix(0)))] 79 | fn no_doc_fn(args: &mut [Janet]) -> Janet { 80 | Janet::nil() 81 | } 82 | 83 | declare_janet_mod!("rust"; 84 | {"hello", rust_hello}, 85 | {"hi", hi}, 86 | {"no_doc_fn", no_doc_fn, "Using custom docs as string literal"}, 87 | ); 88 | ``` 89 | 90 | A macro to declare a Janet module/library and the exposed functions. 91 | -------------------------------------------------------------------------------- /janetrs_macros/src/utils.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use quote::ToTokens; 3 | use syn::{LitStr, Token, parse::Parse, punctuated::Punctuated, spanned::Spanned}; 4 | 5 | /// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span. 6 | macro_rules! err_spanned { 7 | ($span:expr => $msg:expr) => { 8 | syn::Error::new($span, $msg) 9 | }; 10 | } 11 | 12 | #[allow(unused_macros)] 13 | /// Macro inspired by `anyhow::bail!` to return a compiler error with the given span. 14 | macro_rules! bail_spanned { 15 | ($span:expr => $msg:expr) => { 16 | return Err(err_spanned!($span => $msg)); 17 | }; 18 | } 19 | 20 | /// Macro inspired by `anyhow::ensure!` to return a compiler error with the given span if 21 | /// the specified condition is not met. 22 | #[allow(unused_macros)] 23 | macro_rules! ensure_spanned { 24 | ($condition:expr, $span:expr => $msg:expr) => { 25 | if !($condition) { 26 | bail_spanned!($span => $msg); 27 | } 28 | } 29 | } 30 | 31 | #[derive(Debug, Clone, Copy)] 32 | pub(crate) enum ArityArgs { 33 | Fix(usize), 34 | Range(usize, Option), 35 | } 36 | 37 | #[derive(Debug, Clone, Copy)] 38 | pub(crate) enum Arg { 39 | CheckMutRef(Span), 40 | Catch(Span), 41 | Arity(ArityArgs, Span), 42 | } 43 | 44 | pub(crate) struct Args(pub(crate) Vec); 45 | 46 | impl Parse for Args { 47 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 48 | let args_span = input.cursor().token_stream().span(); 49 | let content: Punctuated<_, _> = input.parse_terminated(Arg::parse, Token![,])?; 50 | if content.len() > 2 { 51 | let span = content.last().map(|s| s.span()).unwrap_or(args_span); 52 | return Err(syn::parse::Error::new( 53 | span, 54 | "expected a maximum of two arguments to the janet_fn proc-macro", 55 | )); 56 | } 57 | 58 | if content.len() == 2 && content[0] == content[1] { 59 | return Err(syn::parse::Error::new( 60 | content[0].span(), 61 | "repeated argument kind: There must be only one argument of each kind, that is, \ 62 | only one of `arity` or `check_mut_ref`", 63 | )); 64 | } 65 | let items = content.into_iter().collect(); 66 | Ok(Self(items)) 67 | } 68 | } 69 | 70 | impl Parse for Arg { 71 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 72 | use syn::parse::Error; 73 | 74 | let ident: syn::Ident = input.parse()?; 75 | 76 | if ident == "check_mut_ref" { 77 | return Ok(Arg::CheckMutRef(ident.span())); 78 | } 79 | 80 | if ident == "catch" { 81 | return Ok(Arg::Catch(ident.span())); 82 | } 83 | 84 | if ident == "arity" { 85 | let content; 86 | syn::parenthesized!(content in input); 87 | 88 | let arity_type: syn::Ident = content.parse()?; 89 | if arity_type == "fix" { 90 | let arity_arg; 91 | syn::parenthesized!(arity_arg in content); 92 | 93 | let num = arity_arg.parse::()?.base10_parse::()?; 94 | return Ok(Arg::Arity(ArityArgs::Fix(num), ident.span())); 95 | } else if arity_type == "range" { 96 | let arity_buff; 97 | let paren = syn::parenthesized!(arity_buff in content); 98 | 99 | let arity_args: Punctuated<_, _> = 100 | arity_buff.parse_terminated(syn::LitInt::parse, Token![,])?; 101 | 102 | let (min, max) = match arity_args.len() { 103 | 1 => (arity_args[0].base10_parse::()?, None), 104 | 2 => ( 105 | arity_args[0].base10_parse::()?, 106 | Some(arity_args[1].base10_parse::()?), 107 | ), 108 | x => { 109 | return Err(Error::new( 110 | paren.span.span(), 111 | format!( 112 | "invalid number of arguments for `range`: Expected at least 1, \ 113 | with max of 2 arguments, got {x}" 114 | ), 115 | )); 116 | }, 117 | }; 118 | 119 | return Ok(Arg::Arity(ArityArgs::Range(min, max), ident.span())); 120 | } else { 121 | return Err(syn::parse::Error::new( 122 | arity_type.span(), 123 | "invalid arity type. Expected either `fix` or `range`", 124 | )); 125 | } 126 | } 127 | 128 | Err(syn::parse::Error::new( 129 | ident.span(), 130 | "invalid argument for the macro. Expected `arity` or `check_mut_ref`", 131 | )) 132 | } 133 | } 134 | 135 | impl PartialEq for Arg { 136 | fn eq(&self, other: &Self) -> bool { 137 | #[allow(clippy::match_like_matches_macro)] 138 | match (self, other) { 139 | (Self::Catch(_), Self::Catch(_)) => true, 140 | (Self::Arity(..), Self::Arity(..)) => true, 141 | (Self::CheckMutRef(_), Self::CheckMutRef(_)) => true, 142 | _ => false, 143 | } 144 | } 145 | } 146 | 147 | impl Arg { 148 | pub(crate) fn span(&self) -> Span { 149 | match self { 150 | Arg::CheckMutRef(s) => *s, 151 | Arg::Catch(s) => *s, 152 | Arg::Arity(_, s) => *s, 153 | } 154 | } 155 | } 156 | 157 | /// Check for the `Janet` type path 158 | pub(crate) fn janet_path_checker(path: &syn::Path) -> bool { 159 | match path.segments.len() { 160 | 1 => { 161 | let ident = if let Some(i) = path.get_ident() { 162 | i 163 | } else { 164 | return false; 165 | }; 166 | let test = syn::Ident::new("Janet", ident.span()); 167 | 168 | *ident == test 169 | }, 170 | 2 => { 171 | let janetrs_mod = &path.segments.first().unwrap().ident; 172 | let janet_ident = &path.segments.last().unwrap().ident; 173 | 174 | let janetrs_expected = syn::Ident::new("janetrs", janetrs_mod.span()); 175 | let janet_expected = syn::Ident::new("Janet", janet_ident.span()); 176 | 177 | *janetrs_mod == janetrs_expected && *janet_ident == janet_expected 178 | }, 179 | _ => false, 180 | } 181 | } 182 | 183 | /// Get the doc string of the function item 184 | /// Got as a reference of [PyO3](https://github.com/PyO3/pyo3/blob/main/pyo3-macros-backend/src/utils.rs#L57) example 185 | pub(crate) fn get_doc(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream { 186 | use proc_macro2::TokenStream; 187 | 188 | let mut parts = Punctuated::::new(); 189 | let mut first = true; 190 | let mut current_part = String::new(); 191 | 192 | for attr in attrs { 193 | if attr.path().is_ident("doc") { 194 | if let Ok(nv) = attr.meta.require_name_value() { 195 | if !first { 196 | current_part.push('\n'); 197 | } else { 198 | first = false; 199 | } 200 | 201 | if let syn::Expr::Lit(syn::ExprLit { 202 | lit: syn::Lit::Str(lit_str), 203 | .. 204 | }) = &nv.value 205 | { 206 | // Strip single left space from literal strings, if needed. 207 | // e.g. `/// Hello world` expands to #[doc = " Hello world"] 208 | let doc_line = lit_str.value(); 209 | current_part.push_str(doc_line.strip_prefix(' ').unwrap_or(&doc_line)); 210 | } else { 211 | // This is probably a macro doc from Rust 1.54, e.g. #[doc = include_str!(...)] 212 | // Reset the string buffer, write that part, and then push this macro part too. 213 | parts.push(current_part.to_token_stream()); 214 | current_part.clear(); 215 | parts.push(nv.value.to_token_stream()); 216 | } 217 | } 218 | } 219 | } 220 | 221 | if !parts.is_empty() { 222 | // Doc contained macro pieces - return as `concat!` expression 223 | if !current_part.is_empty() { 224 | parts.push(current_part.to_token_stream()); 225 | } 226 | 227 | let mut tokens = TokenStream::new(); 228 | 229 | syn::Ident::new("concat", Span::call_site()).to_tokens(&mut tokens); 230 | syn::token::Not(Span::call_site()).to_tokens(&mut tokens); 231 | syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| { 232 | parts.to_tokens(tokens); 233 | syn::token::Comma(Span::call_site()).to_tokens(tokens); 234 | syn::LitStr::new("\0", Span::call_site()).to_tokens(tokens); 235 | }); 236 | 237 | tokens 238 | } else { 239 | // Just a string doc - return directly with nul terminator 240 | current_part.push('\0'); 241 | current_part.to_token_stream() 242 | } 243 | } 244 | 245 | 246 | /// Args for the `define_janet_mod` macro 247 | pub(crate) struct ModArgs { 248 | pub(crate) mod_name: syn::LitStr, 249 | pub(crate) fn_names: Vec, 250 | pub(crate) fn_ptr_idents: Vec, 251 | pub(crate) fn_doc_idents: Vec, 252 | pub(crate) fn_line_idents: Vec, 253 | pub(crate) fn_file_idents: Vec, 254 | pub(crate) fn_doc_lits: Vec>, 255 | } 256 | 257 | #[derive(Clone)] 258 | pub(crate) struct JanetFn { 259 | pub(crate) fn_name: syn::LitStr, 260 | pub(crate) fn_ptr_ident: syn::Ident, 261 | pub(crate) fn_doc_ident: syn::Ident, 262 | pub(crate) fn_line_ident: syn::Ident, 263 | pub(crate) fn_file_ident: syn::Ident, 264 | pub(crate) fn_doc_lit: Option, 265 | } 266 | 267 | impl Parse for ModArgs { 268 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 269 | let mod_name = { 270 | let mod_name: syn::LitStr = input.parse()?; 271 | let mut name = mod_name.value(); 272 | name.push('\0'); 273 | syn::LitStr::new(&name, mod_name.span()) 274 | }; 275 | let _a: Token![;] = input.parse()?; 276 | 277 | let mut fn_names = Vec::with_capacity(10); 278 | let mut fn_ptr_idents = Vec::with_capacity(10); 279 | let mut fn_doc_idents = Vec::with_capacity(10); 280 | let mut fn_line_idents = Vec::with_capacity(10); 281 | let mut fn_file_idents = Vec::with_capacity(10); 282 | let mut fn_doc_lits = Vec::with_capacity(10); 283 | 284 | let fn_infos: Punctuated<_, _> = input.parse_terminated(JanetFn::parse, Token![,])?; 285 | 286 | for JanetFn { 287 | fn_name, 288 | fn_ptr_ident, 289 | fn_doc_ident, 290 | fn_line_ident, 291 | fn_file_ident, 292 | fn_doc_lit, 293 | } in fn_infos.into_iter() 294 | { 295 | fn_names.push(fn_name); 296 | fn_ptr_idents.push(fn_ptr_ident); 297 | fn_doc_idents.push(fn_doc_ident); 298 | fn_line_idents.push(fn_line_ident); 299 | fn_file_idents.push(fn_file_ident); 300 | fn_doc_lits.push(fn_doc_lit); 301 | } 302 | 303 | Ok(Self { 304 | mod_name, 305 | fn_names, 306 | fn_ptr_idents, 307 | fn_doc_idents, 308 | fn_line_idents, 309 | fn_file_idents, 310 | fn_doc_lits, 311 | }) 312 | } 313 | } 314 | 315 | impl Parse for JanetFn { 316 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 317 | let content; 318 | syn::braced!(content in input); 319 | 320 | let fn_name = { 321 | let orig_name: syn::LitStr = content.parse()?; 322 | let mut name = orig_name.value(); 323 | name.push('\0'); 324 | syn::LitStr::new(&name, orig_name.span()) 325 | }; 326 | 327 | let _a: Token![,] = content.parse()?; 328 | let fn_ptr_ident: syn::Ident = content.parse()?; 329 | 330 | let fn_doc_ident = { 331 | let mut docstring = fn_ptr_ident.to_string(); 332 | docstring.push_str("_docstring_"); 333 | syn::Ident::new(&docstring, fn_ptr_ident.span()) 334 | }; 335 | let fn_file_ident = { 336 | let mut file = fn_ptr_ident.to_string(); 337 | file.push_str("_file_"); 338 | syn::Ident::new(&file, fn_ptr_ident.span()) 339 | }; 340 | let fn_line_ident = { 341 | let mut line = fn_ptr_ident.to_string(); 342 | line.push_str("_line_"); 343 | syn::Ident::new(&line, fn_ptr_ident.span()) 344 | }; 345 | 346 | let fn_doc_lit = match content.parse::() { 347 | Ok(_) => { 348 | if content.is_empty() { 349 | None 350 | } else { 351 | let orig_doc_str: syn::LitStr = content.parse()?; 352 | let mut name = orig_doc_str.value(); 353 | name.push('\0'); 354 | 355 | Some(syn::LitStr::new(&name, orig_doc_str.span())) 356 | } 357 | }, 358 | Err(_) => None, 359 | }; 360 | 361 | // let fn_doc_lit = if content.peek(Token![,]) && content.peek2(syn::LitStr) { 362 | // let _a: Token![,] = content.parse()?; 363 | // let orig_doc_str: syn::LitStr = content.parse()?; 364 | // let mut name = orig_doc_str.value(); 365 | // name.push('\0'); 366 | 367 | // Some(syn::LitStr::new(&name, orig_doc_str.span())) 368 | // } else if content.peek(Token![,]) { 369 | // let _a: Token![,] = content.parse()?; 370 | // None 371 | // } else { 372 | // None 373 | // }; 374 | 375 | Ok(Self { 376 | fn_name, 377 | fn_ptr_ident, 378 | fn_doc_ident, 379 | fn_line_ident, 380 | fn_file_ident, 381 | fn_doc_lit, 382 | }) 383 | } 384 | } 385 | 386 | #[derive(Debug)] 387 | pub(crate) struct JanetVersionArgs { 388 | pub(crate) min_version: LitStr, 389 | pub(crate) max_version: Option, 390 | } 391 | 392 | impl Parse for JanetVersionArgs { 393 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 394 | let args: Punctuated = Punctuated::parse_terminated(input)?; 395 | let args_span = args.span(); 396 | 397 | if args.len() > 2 { 398 | let span = args 399 | .iter() 400 | .skip(2) 401 | .map(|a| a.span()) 402 | .reduce(|a, other| a.join(other).unwrap_or(other)) 403 | .unwrap(); 404 | return Err(err_spanned!( 405 | span => 406 | "expected at max two arguments to the janet_version proc-macro" 407 | )); 408 | } 409 | 410 | let mut args_iter = args.into_iter(); 411 | 412 | let min_version = match args_iter.next() { 413 | Some(min) => min, 414 | None => { 415 | return Err(err_spanned!( 416 | args_span => 417 | "expected at least one argument to the janet_version proc-macro" 418 | )); 419 | }, 420 | }; 421 | 422 | let max_version = args_iter.next(); 423 | 424 | Ok(Self { 425 | min_version, 426 | max_version, 427 | }) 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /janetrs_macros/tests/00-test: -------------------------------------------------------------------------------- 1 | use janetrs_macros::*; 2 | use janetrs::{self, Janet}; 3 | 4 | #[janet_fn] 5 | pub fn function(_args: &mut [Janet]) -> Janet { 6 | Janet::nil() 7 | } 8 | 9 | #[janet_fn] 10 | pub fn function2(_args: &mut [janetrs::Janet]) -> janetrs::Janet { 11 | Janet::nil() 12 | } 13 | 14 | #[janet_fn(arity(fix(0)))] 15 | pub fn function3(_args: &mut [janetrs::Janet]) -> janetrs::Janet { 16 | Janet::nil() 17 | } 18 | 19 | #[janet_fn(arity(range(0)))] 20 | pub fn function4(_args: &mut [janetrs::Janet]) -> janetrs::Janet { 21 | Janet::nil() 22 | } 23 | 24 | #[janet_fn(arity(range(0, 10)))] 25 | pub fn function5(_args: &mut [janetrs::Janet]) -> janetrs::Janet { 26 | Janet::nil() 27 | } 28 | 29 | #[janet_fn(arity(range(0, 10)), check_mut_ref)] 30 | pub fn function6(_args: &mut [janetrs::Janet]) -> janetrs::Janet { 31 | Janet::nil() 32 | } 33 | 34 | #[janet_fn(check_mut_ref)] 35 | pub fn function7(_args: &mut [::janetrs::Janet]) -> ::janetrs::Janet { 36 | Janet::nil() 37 | } 38 | 39 | #[janet_version("1")] 40 | pub fn function8() { 41 | todo!() 42 | } 43 | 44 | #[janet_version("1.8")] 45 | pub fn function9() { 46 | todo!() 47 | } 48 | 49 | #[janet_version("1.11.2")] 50 | pub fn function10() { 51 | todo!() 52 | } 53 | 54 | #[janet_version("1.11.2", "2")] 55 | pub fn function11() { 56 | todo!() 57 | } 58 | 59 | fn main() {} 60 | -------------------------------------------------------------------------------- /janetrs_macros/tests/01-paths: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use janetrs_macros::*; 4 | use janetrs::{self, Janet}; 5 | 6 | #[janet_fn] 7 | pub fn function(_args: &mut [Janet]) -> Janets { 8 | Janet::nil() 9 | } 10 | 11 | #[janet_fn] 12 | pub fn function2(_args: &mut [tips::Janet]) -> types::Janet { 13 | Janet::nil() 14 | } 15 | 16 | #[janet_fn] 17 | pub fn function3(_args: &mut [types::Janet]) {} 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /janetrs_macros/tests/01-paths.stderr: -------------------------------------------------------------------------------- 1 | error: expected return type to be `janetrs::Janet` 2 | --> tests/01-paths:7:41 3 | | 4 | 7 | pub fn function(_args: &mut [Janet]) -> Janets { 5 | | ^^^^^^ 6 | 7 | error: expected argument to be a `janetrs::Janet` 8 | --> tests/01-paths:12:31 9 | | 10 | 12 | pub fn function2(_args: &mut [tips::Janet]) -> types::Janet { 11 | | ^^^^^^^^^^^ 12 | 13 | error: expected argument to be a `janetrs::Janet` 14 | --> tests/01-paths:17:31 15 | | 16 | 17 | pub fn function3(_args: &mut [types::Janet]) {} 17 | | ^^^^^^^^^^^^ 18 | -------------------------------------------------------------------------------- /janetrs_macros/tests/02-not-function: -------------------------------------------------------------------------------- 1 | use janetrs_macros::*; 2 | 3 | #[janet_fn] 4 | struct A(i32); 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /janetrs_macros/tests/02-not-function.stderr: -------------------------------------------------------------------------------- 1 | error: expected fn item 2 | --> tests/02-not-function:4:1 3 | | 4 | 4 | struct A(i32); 5 | | ^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /janetrs_macros/tests/03-not-mut: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use janetrs_macros::*; 4 | use janetrs::Janet; 5 | 6 | #[janet_fn] 7 | pub fn function(_args: &[Janet]) -> Janet { 8 | Janet::nil() 9 | } 10 | 11 | fn main() {} -------------------------------------------------------------------------------- /janetrs_macros/tests/03-not-mut.stderr: -------------------------------------------------------------------------------- 1 | error: expected argument to be a mutable reference and found a immutable reference 2 | --> tests/03-not-mut:7:24 3 | | 4 | 7 | pub fn function(_args: &[Janet]) -> Janet { 5 | | ^ 6 | -------------------------------------------------------------------------------- /janetrs_macros/tests/04-not-ref: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use janetrs_macros::*; 4 | use janetrs::Janet; 5 | 6 | #[janet_fn] 7 | pub fn function(_args: Janet) -> Janet { 8 | Janet::nil() 9 | } 10 | 11 | fn main() {} -------------------------------------------------------------------------------- /janetrs_macros/tests/04-not-ref.stderr: -------------------------------------------------------------------------------- 1 | error: expected argument to be a mutable reference and found something that is not a reference at all 2 | --> $DIR/04-not-ref:7:24 3 | | 4 | 7 | pub fn function(_args: Janet) -> Janet { 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /janetrs_macros/tests/05-wrong-args: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use janetrs_macros::*; 4 | use janetrs::Janet; 5 | 6 | #[janet_fn] 7 | pub fn function(_args: &mut [Janet], _wrong: Janet) -> Janet { 8 | Janet::nil() 9 | } 10 | 11 | fn main() {} -------------------------------------------------------------------------------- /janetrs_macros/tests/05-wrong-args.stderr: -------------------------------------------------------------------------------- 1 | error: expected exactly one argument of type `&mut [janetrs::Janet]` 2 | --> tests/05-wrong-args:7:17 3 | | 4 | 7 | pub fn function(_args: &mut [Janet], _wrong: Janet) -> Janet { 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /janetrs_macros/tests/06-not-slice: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use janetrs_macros::*; 4 | use janetrs::{self, Janet}; 5 | 6 | #[janet_fn] 7 | pub fn function(_args: &mut Janet) -> Janet { 8 | Janet::nil() 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /janetrs_macros/tests/06-not-slice.stderr: -------------------------------------------------------------------------------- 1 | error: expected argument to be a slice of `janetrs::Janet` 2 | --> tests/06-not-slice:7:29 3 | | 4 | 7 | pub fn function(_args: &mut Janet) -> Janet { 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /janetrs_macros/tests/07-mod: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use janetrs::{janet_mod, lowlevel, *}; 4 | use janetrs_macros::janet_fn; 5 | 6 | #[janet_fn] 7 | pub fn rust_hello(_args: &mut [Janet]) -> Janet { 8 | println!("Hello from Rust!"); 9 | Janet::nil() 10 | } 11 | 12 | #[janet_fn] 13 | pub fn string(_args: &mut [Janet]) -> Janet { 14 | Janet::from("I'm a returned string! =)") 15 | } 16 | 17 | janet_mod!("rust"; 18 | {"hello", rust_hello_c, "(rust/hello)\n\nRust say hello"}, 19 | {"string", string_c, "(rust/string\n\nReturn a string"}, 20 | ); 21 | 22 | fn main() {} 23 | -------------------------------------------------------------------------------- /janetrs_macros/tests/08-wrong-args: -------------------------------------------------------------------------------- 1 | 2 | use janetrs_macros::janet_fn; 3 | 4 | #[janet_fn(a)] 5 | pub fn rust_hello(_args: &mut [Janet]) -> Janet { 6 | println!("Hello from Rust!"); 7 | Janet::nil() 8 | } 9 | 10 | #[janet_fn(arity(fix("")))] 11 | pub fn test1(_args: &mut [Janet]) -> Janet { 12 | Janet::nil() 13 | } 14 | 15 | #[janet_fn(arity(fix))] 16 | pub fn test2(_args: &mut [Janet]) -> Janet { 17 | Janet::nil() 18 | } 19 | 20 | #[janet_fn(arity(fix()))] 21 | pub fn test3(_args: &mut [Janet]) -> Janet { 22 | Janet::nil() 23 | } 24 | 25 | #[janet_fn(arity(range()))] 26 | pub fn test4(_args: &mut [Janet]) -> Janet { 27 | Janet::nil() 28 | } 29 | 30 | #[janet_fn(check_mut_ref, check_mut_ref)] 31 | pub fn test5(_args: &mut [Janet]) -> Janet { 32 | Janet::nil() 33 | } 34 | 35 | #[janet_fn(arity(fix(1)), check_mut_ref, arity(fix(2)))] 36 | pub fn test6(_args: &mut [Janet]) -> Janet { 37 | Janet::nil() 38 | } 39 | 40 | fn main() {} -------------------------------------------------------------------------------- /janetrs_macros/tests/08-wrong-args.stderr: -------------------------------------------------------------------------------- 1 | error: invalid argument for the macro. Expected `arity` or `check_mut_ref` 2 | --> tests/08-wrong-args:4:12 3 | | 4 | 4 | #[janet_fn(a)] 5 | | ^ 6 | 7 | error: expected integer literal 8 | --> tests/08-wrong-args:10:22 9 | | 10 | 10 | #[janet_fn(arity(fix("")))] 11 | | ^^ 12 | 13 | error: unexpected end of input, expected parentheses 14 | --> tests/08-wrong-args:15:21 15 | | 16 | 15 | #[janet_fn(arity(fix))] 17 | | ^ 18 | 19 | error: unexpected end of input, expected integer literal 20 | --> tests/08-wrong-args:20:22 21 | | 22 | 20 | #[janet_fn(arity(fix()))] 23 | | ^ 24 | 25 | error: invalid number of arguments for `range`: Expected at least 1, with max of 2 arguments, got 0 26 | --> tests/08-wrong-args:25:23 27 | | 28 | 25 | #[janet_fn(arity(range()))] 29 | | ^^ 30 | 31 | error: repeated argument kind: There must be only one argument of each kind, that is, only one of `arity` or `check_mut_ref` 32 | --> tests/08-wrong-args:30:12 33 | | 34 | 30 | #[janet_fn(check_mut_ref, check_mut_ref)] 35 | | ^^^^^^^^^^^^^ 36 | 37 | error: expected a maximum of two arguments to the janet_fn proc-macro 38 | --> tests/08-wrong-args:35:42 39 | | 40 | 35 | #[janet_fn(arity(fix(1)), check_mut_ref, arity(fix(2)))] 41 | | ^^^^^ 42 | -------------------------------------------------------------------------------- /janetrs_macros/tests/09-version-wrong: -------------------------------------------------------------------------------- 1 | use janetrs_macros::janet_version; 2 | 3 | #[janet_version(wrong)] 4 | pub fn func1() { 5 | todo!() 6 | } 7 | 8 | #[janet_version("1,12,10")] 9 | pub fn func2() { 10 | todo!() 11 | } 12 | 13 | #[janet_version("aaaaaa")] 14 | pub fn func3() { 15 | todo!() 16 | } 17 | 18 | #[janet_version()] 19 | pub fn func4() { 20 | todo!() 21 | } 22 | 23 | #[janet_version("1", "aaa")] 24 | pub fn func5() { 25 | todo!() 26 | } 27 | 28 | #[janet_version("1", "2", "3")] 29 | pub fn func6() { 30 | todo!() 31 | } 32 | 33 | fn main() {} -------------------------------------------------------------------------------- /janetrs_macros/tests/09-version-wrong.stderr: -------------------------------------------------------------------------------- 1 | error: expected string literal 2 | --> tests/09-version-wrong:3:17 3 | | 4 | 3 | #[janet_version(wrong)] 5 | | ^^^^^ 6 | 7 | error: invalid string literal: invalid digit found in string 8 | --> tests/09-version-wrong:8:17 9 | | 10 | 8 | #[janet_version("1,12,10")] 11 | | ^^^^^^^^^ 12 | 13 | error: invalid string literal: invalid digit found in string 14 | --> tests/09-version-wrong:13:17 15 | | 16 | 13 | #[janet_version("aaaaaa")] 17 | | ^^^^^^^^ 18 | 19 | error: expected at least one argument to the janet_version proc-macro 20 | --> tests/09-version-wrong:18:1 21 | | 22 | 18 | #[janet_version()] 23 | | ^^^^^^^^^^^^^^^^^^ 24 | | 25 | = note: this error originates in the attribute macro `janet_version` (in Nightly builds, run with -Z macro-backtrace for more info) 26 | 27 | error: invalid string literal: invalid digit found in string 28 | --> tests/09-version-wrong:23:22 29 | | 30 | 23 | #[janet_version("1", "aaa")] 31 | | ^^^^^ 32 | 33 | error: expected at max two arguments to the janet_version proc-macro 34 | --> tests/09-version-wrong:28:27 35 | | 36 | 28 | #[janet_version("1", "2", "3")] 37 | | ^^^ 38 | -------------------------------------------------------------------------------- /janetrs_macros/tests/10-declare-janet-module: -------------------------------------------------------------------------------- 1 | use janetrs_macros::*; 2 | use janetrs::{self, Janet}; 3 | 4 | #[janet_fn] 5 | pub fn function(_args: &mut [Janet]) -> Janet { 6 | Janet::nil() 7 | } 8 | 9 | #[janet_fn] 10 | pub fn function2(_args: &mut [janetrs::Janet]) -> janetrs::Janet { 11 | Janet::nil() 12 | } 13 | 14 | #[janet_fn] 15 | pub fn function3(_args: &mut [janetrs::Janet]) -> janetrs::Janet { 16 | Janet::nil() 17 | } 18 | 19 | declare_janet_mod!("test"; 20 | {"function1", function}, 21 | {"function2", function2, "My doc string here"}, 22 | {"function3", function3,} 23 | ); 24 | 25 | fn main() {} -------------------------------------------------------------------------------- /janetrs_macros/tests/11-declare_mod_errors: -------------------------------------------------------------------------------- 1 | use janetrs_macros::*; 2 | use janetrs::{self, Janet}; 3 | 4 | #[janet_fn] 5 | pub fn function(_args: &mut [Janet]) -> Janet { 6 | Janet::nil() 7 | } 8 | 9 | #[janet_fn] 10 | pub fn function2(_args: &mut [janetrs::Janet]) -> janetrs::Janet { 11 | Janet::nil() 12 | } 13 | 14 | declare_janet_mod!(10; 15 | {"function1", function}, 16 | {"function2", function2}, 17 | ); 18 | 19 | declare_janet_mod!("test"; 20 | {10, function}, 21 | {"function2", function2}, 22 | ); 23 | 24 | declare_janet_mod!("test"; 25 | {"function1", 10}, 26 | {"function2", function2}, 27 | ); 28 | 29 | declare_janet_mod!("test"; 30 | {"function1", function}, 31 | {"function2", "function2"}, 32 | ); 33 | 34 | declare_janet_mod!("test"; 35 | {"function1", function}, 36 | {"function2", function2, 10}, 37 | ); 38 | 39 | fn main() {} -------------------------------------------------------------------------------- /janetrs_macros/tests/11-declare_mod_errors.stderr: -------------------------------------------------------------------------------- 1 | error: expected string literal 2 | --> $DIR/11-declare_mod_errors:14:20 3 | | 4 | 14 | declare_janet_mod!(10; 5 | | ^^ 6 | 7 | error: expected string literal 8 | --> $DIR/11-declare_mod_errors:20:6 9 | | 10 | 20 | {10, function}, 11 | | ^^ 12 | 13 | error: expected identifier 14 | --> $DIR/11-declare_mod_errors:25:19 15 | | 16 | 25 | {"function1", 10}, 17 | | ^^ 18 | 19 | error: expected identifier 20 | --> $DIR/11-declare_mod_errors:31:19 21 | | 22 | 31 | {"function2", "function2"}, 23 | | ^^^^^^^^^^^ 24 | 25 | error: expected string literal 26 | --> $DIR/11-declare_mod_errors:36:30 27 | | 28 | 36 | {"function2", function2, 10}, 29 | | ^^ 30 | -------------------------------------------------------------------------------- /janetrs_macros/tests/progress.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn tests() { 3 | let t = trybuild::TestCases::new(); 4 | t.pass("tests/00-test"); 5 | t.compile_fail("tests/01-paths"); 6 | t.compile_fail("tests/02-not-function"); 7 | t.compile_fail("tests/03-not-mut"); 8 | t.compile_fail("tests/04-not-ref"); 9 | t.compile_fail("tests/05-wrong-args"); 10 | t.compile_fail("tests/06-not-slice"); 11 | t.pass("tests/07-mod"); 12 | t.compile_fail("tests/08-wrong-args"); 13 | t.compile_fail("tests/09-version-wrong"); 14 | t.pass("tests/10-declare-janet-module"); 15 | t.compile_fail("tests/11-declare_mod_errors"); 16 | } 17 | -------------------------------------------------------------------------------- /janetrs_version/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "janetrs_version" 3 | version = "0.1.0" 4 | authors = ["Eric Shimizu Karbstein "] 5 | description = "Janet version types for JanetRS" 6 | repository = "https://github.com/GrayJack/janetrs" 7 | homepage = "https://crates.io/crates/janetrs_macros" 8 | documentation = "https://docs.rs/janetrs_macros" 9 | license-file = "../LICENSE" 10 | edition = "2021" 11 | rust-version = "1.60.0" 12 | keywords = ["janet", "utils"] 13 | categories = ["api-bindings"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | evil-janet = "1" 19 | 20 | [features] 21 | default = [] 22 | system = ["evil-janet/system"] 23 | inline-more = [] 24 | -------------------------------------------------------------------------------- /janetrs_version/src/lib.rs: -------------------------------------------------------------------------------- 1 | use core::{cmp::Ordering, fmt}; 2 | 3 | use evil_janet::{ 4 | JANET_CURRENT_CONFIG_BITS, JANET_VERSION_MAJOR, JANET_VERSION_MINOR, JANET_VERSION_PATCH, 5 | }; 6 | 7 | /// Janet configuration in the build. 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 9 | pub struct JanetBuildConfig { 10 | version: JanetVersion, 11 | bits: u32, 12 | } 13 | 14 | impl JanetBuildConfig { 15 | /// Get the current Janet build version. 16 | #[inline] 17 | pub const fn current() -> Self { 18 | Self { 19 | version: JanetVersion::current(), 20 | bits: JANET_CURRENT_CONFIG_BITS, 21 | } 22 | } 23 | 24 | /// Create a custom [`JanetBuildConfig`]. 25 | /// 26 | /// Mostly used to check if current version match a requirement for your code. 27 | #[inline] 28 | pub const fn custom(major: u32, minor: u32, patch: u32, bits: u32) -> Self { 29 | Self { 30 | version: JanetVersion::custom(major, minor, patch), 31 | bits, 32 | } 33 | } 34 | 35 | /// Return the version of the Janet. 36 | #[inline] 37 | pub const fn version(&self) -> JanetVersion { 38 | self.version 39 | } 40 | 41 | /// Return `true` if Janet single threaded bit is set. 42 | #[inline] 43 | pub const fn is_single_threaded(&self) -> bool { 44 | match self.bits { 45 | 0 | 1 => false, 46 | 2 | 3 => true, 47 | _ => false, 48 | } 49 | } 50 | 51 | /// Return `true` is Janet nanbox bit is set. 52 | #[inline] 53 | pub const fn is_nanbox(&self) -> bool { 54 | match self.bits { 55 | 0 | 2 => false, 56 | 1 | 3 => true, 57 | _ => false, 58 | } 59 | } 60 | } 61 | 62 | /// Janet version representation. 63 | /// 64 | /// It has convenient comparison operators implementations with triples `(u32, u32, u32)`, 65 | /// arrays `[u32; 3]` and [`str`]. 66 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 67 | pub struct JanetVersion { 68 | major: u32, 69 | minor: u32, 70 | patch: u32, 71 | } 72 | 73 | impl JanetVersion { 74 | /// Get the current Janet version. 75 | #[inline] 76 | pub const fn current() -> Self { 77 | Self { 78 | major: JANET_VERSION_MAJOR, 79 | minor: JANET_VERSION_MINOR, 80 | patch: JANET_VERSION_PATCH, 81 | } 82 | } 83 | 84 | /// Create a custom [`JanetVersion`] given the version number. 85 | /// 86 | /// Mostly used to check if current version match a requirement for your code. 87 | #[inline] 88 | pub const fn custom(major: u32, minor: u32, patch: u32) -> Self { 89 | Self { 90 | major, 91 | minor, 92 | patch, 93 | } 94 | } 95 | 96 | /// Return the Janet major version. 97 | #[inline] 98 | pub const fn major(&self) -> u32 { 99 | self.major 100 | } 101 | 102 | /// Return the Janet minor version. 103 | #[inline] 104 | pub const fn minor(&self) -> u32 { 105 | self.minor 106 | } 107 | 108 | /// Return the Janet patch version. 109 | #[inline] 110 | pub const fn patch(&self) -> u32 { 111 | self.patch 112 | } 113 | } 114 | 115 | impl fmt::Display for JanetVersion { 116 | #[inline] 117 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 118 | fmt::Display::fmt( 119 | &format_args!("Janet {}.{}.{}", self.major, self.minor, self.patch), 120 | f, 121 | ) 122 | } 123 | } 124 | 125 | impl PartialEq<(u32, u32, u32)> for JanetVersion { 126 | #[inline] 127 | fn eq(&self, (major, minor, patch): &(u32, u32, u32)) -> bool { 128 | self.major.eq(major) && self.minor.eq(minor) && self.patch.eq(patch) 129 | } 130 | } 131 | 132 | impl PartialEq<[u32; 3]> for JanetVersion { 133 | #[inline] 134 | fn eq(&self, [major, minor, patch]: &[u32; 3]) -> bool { 135 | self.major.eq(major) && self.minor.eq(minor) && self.patch.eq(patch) 136 | } 137 | } 138 | 139 | impl PartialEq<&str> for JanetVersion { 140 | #[cfg_attr(feature = "inline-more", inline)] 141 | fn eq(&self, other: &&str) -> bool { 142 | if other.split('.').count() > 3 { 143 | false 144 | } else { 145 | other 146 | .split('.') 147 | .map(|s| s.parse::()) 148 | .take(3) 149 | .zip([self.major, self.minor, self.patch].iter()) 150 | .all(|(o, s)| o.unwrap_or(u32::MAX).eq(s)) 151 | } 152 | } 153 | } 154 | 155 | impl PartialOrd for JanetVersion { 156 | #[inline] 157 | fn partial_cmp(&self, other: &Self) -> Option { 158 | Some(self.cmp(other)) 159 | } 160 | } 161 | 162 | impl PartialOrd<(u32, u32, u32)> for JanetVersion { 163 | #[cfg_attr(feature = "inline-more", inline)] 164 | fn partial_cmp(&self, (major, minor, patch): &(u32, u32, u32)) -> Option { 165 | match self.major.cmp(major) { 166 | Ordering::Equal => match self.minor.cmp(minor) { 167 | Ordering::Equal => Some(self.patch.cmp(patch)), 168 | x => Some(x), 169 | }, 170 | x => Some(x), 171 | } 172 | } 173 | } 174 | 175 | impl PartialOrd<[u32; 3]> for JanetVersion { 176 | #[cfg_attr(feature = "inline-more", inline)] 177 | fn partial_cmp(&self, [major, minor, patch]: &[u32; 3]) -> Option { 178 | match self.major.cmp(major) { 179 | Ordering::Equal => match self.minor.cmp(minor) { 180 | Ordering::Equal => Some(self.patch.cmp(patch)), 181 | x => Some(x), 182 | }, 183 | x => Some(x), 184 | } 185 | } 186 | } 187 | 188 | impl PartialOrd<&str> for JanetVersion { 189 | #[cfg_attr(feature = "inline-more", inline)] 190 | fn partial_cmp(&self, other: &&str) -> Option { 191 | let [major, minor, patch] = { 192 | let iter = other.split('.'); 193 | 194 | if iter.clone().count() > 3 { 195 | return None; 196 | } 197 | 198 | let mut arr = [0; 3]; 199 | 200 | for (index, elem) in iter.map(|s| s.parse().ok()).enumerate() { 201 | arr[index] = elem?; 202 | } 203 | arr 204 | }; 205 | 206 | match self.major.cmp(&major) { 207 | Ordering::Equal => match self.minor.cmp(&minor) { 208 | Ordering::Equal => Some(self.patch.cmp(&patch)), 209 | x => Some(x), 210 | }, 211 | x => Some(x), 212 | } 213 | } 214 | } 215 | 216 | impl Ord for JanetVersion { 217 | #[inline] 218 | fn cmp(&self, other: &Self) -> Ordering { 219 | match self.major.cmp(&other.major) { 220 | Ordering::Equal => match self.minor.cmp(&other.minor) { 221 | Ordering::Equal => self.patch.cmp(&other.patch), 222 | x => x, 223 | }, 224 | x => x, 225 | } 226 | } 227 | } 228 | 229 | #[cfg(test)] 230 | mod tests { 231 | use super::*; 232 | 233 | #[test] 234 | fn janet_version_ord() { 235 | assert_eq!( 236 | Ordering::Equal, 237 | JanetVersion::custom(1, 1, 1).cmp(&JanetVersion::custom(1, 1, 1)) 238 | ); 239 | assert_eq!( 240 | Ordering::Equal, 241 | JanetVersion::custom(1, 2, 1).cmp(&JanetVersion::custom(1, 2, 1)) 242 | ); 243 | 244 | assert_eq!( 245 | Ordering::Less, 246 | JanetVersion::custom(1, 1, 1).cmp(&JanetVersion::custom(2, 1, 1)) 247 | ); 248 | assert_eq!( 249 | Ordering::Less, 250 | JanetVersion::custom(1, 1, 1).cmp(&JanetVersion::custom(1, 2, 1)) 251 | ); 252 | assert_eq!( 253 | Ordering::Less, 254 | JanetVersion::custom(1, 1, 1).cmp(&JanetVersion::custom(1, 1, 2)) 255 | ); 256 | 257 | assert_eq!( 258 | Ordering::Greater, 259 | JanetVersion::custom(2, 1, 1).cmp(&JanetVersion::custom(1, 1, 1)) 260 | ); 261 | assert_eq!( 262 | Ordering::Greater, 263 | JanetVersion::custom(1, 2, 1).cmp(&JanetVersion::custom(1, 1, 1)) 264 | ); 265 | assert_eq!( 266 | Ordering::Greater, 267 | JanetVersion::custom(1, 1, 2).cmp(&JanetVersion::custom(1, 1, 1)) 268 | ); 269 | } 270 | 271 | #[test] 272 | fn version_eq_tuple() { 273 | assert!(JanetVersion::custom(1, 10, 0) == (1, 10, 0)); 274 | assert!(JanetVersion::custom(0, 1, 9) == (0, 1, 9)); 275 | } 276 | 277 | #[test] 278 | fn version_eq_str() { 279 | assert!(JanetVersion::custom(1, 10, 0) == "1.10.0"); 280 | assert!(JanetVersion::custom(0, 1, 9) == "0.1.9"); 281 | } 282 | 283 | #[test] 284 | fn version_ord_tuple() { 285 | assert_eq!( 286 | Some(Ordering::Equal), 287 | JanetVersion::custom(1, 1, 1).partial_cmp(&(1, 1, 1)) 288 | ); 289 | assert_eq!( 290 | Some(Ordering::Equal), 291 | JanetVersion::custom(1, 2, 1).partial_cmp(&(1, 2, 1)) 292 | ); 293 | 294 | assert_eq!( 295 | Some(Ordering::Less), 296 | JanetVersion::custom(1, 1, 1).partial_cmp(&(2, 1, 1)) 297 | ); 298 | assert_eq!( 299 | Some(Ordering::Less), 300 | JanetVersion::custom(1, 1, 1).partial_cmp(&(1, 2, 1)) 301 | ); 302 | assert_eq!( 303 | Some(Ordering::Less), 304 | JanetVersion::custom(1, 1, 1).partial_cmp(&(1, 1, 2)) 305 | ); 306 | 307 | assert_eq!( 308 | Some(Ordering::Greater), 309 | JanetVersion::custom(2, 1, 1).partial_cmp(&(1, 1, 1)) 310 | ); 311 | assert_eq!( 312 | Some(Ordering::Greater), 313 | JanetVersion::custom(1, 2, 1).partial_cmp(&(1, 1, 1)) 314 | ); 315 | assert_eq!( 316 | Some(Ordering::Greater), 317 | JanetVersion::custom(1, 1, 2).partial_cmp(&(1, 1, 1)) 318 | ); 319 | } 320 | 321 | #[test] 322 | fn version_ord_str() { 323 | assert_eq!( 324 | Some(Ordering::Equal), 325 | JanetVersion::custom(1, 1, 1).partial_cmp(&"1.1.1") 326 | ); 327 | assert_eq!( 328 | Some(Ordering::Equal), 329 | JanetVersion::custom(1, 2, 1).partial_cmp(&"1.2.1") 330 | ); 331 | 332 | assert_eq!( 333 | Some(Ordering::Less), 334 | JanetVersion::custom(1, 1, 1).partial_cmp(&"2.1.1") 335 | ); 336 | assert_eq!( 337 | Some(Ordering::Less), 338 | JanetVersion::custom(1, 1, 1).partial_cmp(&"1.2.1") 339 | ); 340 | assert_eq!( 341 | Some(Ordering::Less), 342 | JanetVersion::custom(1, 1, 1).partial_cmp(&"1.1.2") 343 | ); 344 | 345 | assert_eq!( 346 | Some(Ordering::Greater), 347 | JanetVersion::custom(2, 1, 1).partial_cmp(&"1.1.1") 348 | ); 349 | assert_eq!( 350 | Some(Ordering::Greater), 351 | JanetVersion::custom(1, 2, 1).partial_cmp(&"1.1.1") 352 | ); 353 | assert_eq!( 354 | Some(Ordering::Greater), 355 | JanetVersion::custom(1, 1, 2).partial_cmp(&"1.1.1") 356 | ); 357 | 358 | assert_eq!(None, JanetVersion::custom(1, 1, 2).partial_cmp(&"")); 359 | assert_eq!( 360 | None, 361 | JanetVersion::custom(1, 1, 2).partial_cmp(&"version go brr") 362 | ); 363 | assert_eq!(None, JanetVersion::custom(1, 1, 2).partial_cmp(&"1.1.1.1")); 364 | } 365 | 366 | #[test] 367 | fn config_bits() { 368 | let test0 = JanetBuildConfig::custom(0, 0, 0, 0); 369 | let test1 = JanetBuildConfig::custom(0, 0, 0, 1); 370 | let test2 = JanetBuildConfig::custom(0, 0, 0, 2); 371 | let test3 = JanetBuildConfig::custom(0, 0, 0, 3); 372 | 373 | assert!(!test0.is_nanbox()); 374 | assert!(!test0.is_single_threaded()); 375 | 376 | assert!(test1.is_nanbox()); 377 | assert!(!test1.is_single_threaded()); 378 | 379 | assert!(!test2.is_nanbox()); 380 | assert!(test2.is_single_threaded()); 381 | 382 | assert!(test3.is_nanbox()); 383 | assert!(test3.is_single_threaded()); 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # license_template_path = "Copyright {\\y} Eric Shimizu Karbstein." 2 | binop_separator = "Front" 3 | blank_lines_lower_bound = 0 4 | blank_lines_upper_bound = 3 5 | brace_style = "SameLineWhere" 6 | color = "Auto" 7 | combine_control_expr = true 8 | comment_width = 90 9 | condense_wildcard_suffixes = true 10 | disable_all_formatting = false 11 | edition = "2018" 12 | empty_item_single_line = true 13 | enum_discrim_align_threshold = 6 14 | fn_params_layout = "Compressed" 15 | fn_single_line = false 16 | force_explicit_abi = true 17 | force_multiline_blocks = false 18 | format_code_in_doc_comments = true 19 | format_macro_bodies = true 20 | format_macro_matchers = true 21 | format_strings = true 22 | hard_tabs = false 23 | ignore = [] 24 | imports_granularity = "Crate" 25 | imports_indent = "Block" 26 | imports_layout = "Mixed" 27 | indent_style = "Block" 28 | inline_attribute_width = 0 29 | make_backup = true 30 | match_arm_blocks = true 31 | match_block_trailing_comma = true 32 | max_width = 100 33 | merge_derives = true 34 | newline_style = "Auto" 35 | normalize_comments = true 36 | normalize_doc_attributes = true 37 | overflow_delimited_expr = true 38 | remove_nested_parens = true 39 | reorder_impl_items = true 40 | reorder_imports = true 41 | reorder_modules = true 42 | skip_children = false 43 | space_after_colon = true 44 | space_before_colon = false 45 | spaces_around_ranges = false 46 | struct_field_align_threshold = 6 47 | struct_lit_single_line = true 48 | tab_spaces = 4 49 | trailing_comma = "Vertical" 50 | trailing_semicolon = true 51 | type_punctuation_density = "Wide" 52 | unstable_features = true 53 | use_field_init_shorthand = true 54 | use_small_heuristics = "Default" 55 | use_try_shorthand = true 56 | version = "Two" 57 | where_single_line = false 58 | wrap_comments = true 59 | -------------------------------------------------------------------------------- /src/allocator.rs: -------------------------------------------------------------------------------- 1 | //! This module provides a allocator that uses the Janet scratch memory API to create 2 | //! objects tracked by the Janet Garbage Collector. 3 | //! 4 | //! For more in depth information, you can look at the [Janet memory model documentation] 5 | //! 6 | //! [Janet memory model documentation]: https://janet-lang.org/capi/memory-model.html 7 | use core::{ 8 | alloc::Layout, 9 | ptr::{self, NonNull}, 10 | }; 11 | 12 | #[cfg(feature = "nightly")] 13 | use core::alloc::{AllocError, Allocator}; 14 | 15 | #[cfg(any( 16 | target_arch = "x86", 17 | target_arch = "arm", 18 | target_arch = "mips", 19 | target_arch = "powerpc", 20 | target_arch = "powerpc64", 21 | target_arch = "sparc", 22 | target_arch = "wasm32", 23 | target_arch = "hexagon", 24 | target_arch = "riscv32" 25 | ))] 26 | const MIN_ALIGN: usize = 8; 27 | #[cfg(any( 28 | target_arch = "x86_64", 29 | target_arch = "aarch64", 30 | target_arch = "mips64", 31 | target_arch = "s390x", 32 | target_arch = "sparc64", 33 | target_arch = "riscv64" 34 | ))] 35 | const MIN_ALIGN: usize = 16; 36 | 37 | /// Memory allocator that will certainly be cleaned up in the next Janet Garbage 38 | /// Collection cycle. 39 | /// 40 | /// If this crate are build with the `nightly` feature enabled, this type also implements 41 | /// the [`Allocator`](core::alloc::Allocator) trait. That means that with the `nightly` 42 | /// feature set it's possible to use this allocator with Rust types that uses allocator as 43 | /// parameter, like [`Box`]. 44 | #[derive(Copy, Clone, Default, Debug)] 45 | pub struct Scratch; 46 | 47 | impl Scratch { 48 | /// Attempts to allocate a block of memory. 49 | /// 50 | /// On success, returns a [`NonNull<[u8]>`][NonNull] meeting the size and alignment 51 | /// guarantees of `layout`. 52 | /// 53 | /// The returned block may have a larger size than specified by `layout.size()`, and 54 | /// may or may not have its contents initialized. 55 | #[inline] 56 | pub fn malloc(&self, layout: Layout) -> Option> { 57 | // Allocate size if it fits in the type size and has a alignment smaller than the 58 | // minimum alignment of the architecture. Over allocate otherwise 59 | let (raw_ptr, alloc_mem_size) = 60 | if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { 61 | let size = layout.size(); 62 | 63 | unsafe { (evil_janet::janet_smalloc(size) as *mut u8, size) } 64 | } else { 65 | // MacOS alloc_system is buggy on huge alignments (e.g. an align of `1 << 32`) 66 | #[cfg(target_os = "macos")] 67 | if layout.align() > (1 << 31) { 68 | return None; 69 | } 70 | 71 | let size = layout.size() + layout.align(); 72 | unsafe { (evil_janet::janet_smalloc(size) as *mut u8, size) } 73 | }; 74 | NonNull::new(ptr::slice_from_raw_parts_mut(raw_ptr, alloc_mem_size)) 75 | } 76 | 77 | /// Behaves like `allocate`, but also ensures that the returned memory is 78 | /// zero-initialized. 79 | #[inline] 80 | pub fn calloc(&self, layout: Layout) -> Option> { 81 | // Allocate size if it fits in the type size and has a alignment smaller than the 82 | // minimum alignment of the architecture. Over allocate otherwise 83 | let (raw_ptr, alloc_mem_size) = 84 | if layout.align() <= MIN_ALIGN && layout.align() <= layout.size() { 85 | let size = layout.size(); 86 | 87 | unsafe { (evil_janet::janet_scalloc(1, size) as *mut u8, size) } 88 | } else { 89 | // MacOS alloc_system is buggy on huge alignments (e.g. an align of `1 << 32`) 90 | #[cfg(target_os = "macos")] 91 | if layout.align() > (1 << 31) { 92 | return None; 93 | } 94 | 95 | let size = layout.size() + layout.align(); 96 | unsafe { (evil_janet::janet_scalloc(1, size) as *mut u8, size) } 97 | }; 98 | NonNull::new(ptr::slice_from_raw_parts_mut(raw_ptr, alloc_mem_size)) 99 | } 100 | 101 | /// Shrink or grow a block of memory to the given `new_size`. 102 | /// The block is described by the given `ptr` pointer and `layout`. 103 | /// 104 | /// # Safety 105 | /// 106 | /// This function is unsafe because undefined behavior can result 107 | /// if the caller does not ensure all of the following: 108 | /// 109 | /// * `ptr` must be currently allocated via this allocator, 110 | /// * `layout` must be the same layout that was used to allocate that block of memory, 111 | /// * `new_size` must be greater than zero. 112 | /// * `new_size`, when rounded up to the nearest multiple of `layout.align()`, must 113 | /// not overflow (i.e., the rounded value must be less than `usize::MAX`). 114 | #[inline] 115 | pub unsafe fn realloc( 116 | &self, ptr: NonNull<[u8]>, layout: Layout, new_size: usize, 117 | ) -> Option> { 118 | let new_layout = Layout::from_size_align_unchecked(new_size, layout.align()); 119 | 120 | // Allocate size if it fits in the type size and has a alignment smaller than the 121 | // minimum alignment of the architecture. Over allocate otherwise 122 | let (raw_ptr, alloc_mem_size) = 123 | if layout.align() <= MIN_ALIGN && layout.align() <= new_layout.size() { 124 | let size = new_layout.size(); 125 | 126 | ( 127 | evil_janet::janet_srealloc(ptr.as_ptr() as *mut _, size) as *mut u8, 128 | size, 129 | ) 130 | } else { 131 | // MacOS alloc_system is buggy on huge alignments (e.g. an align of `1 << 32`) 132 | #[cfg(target_os = "macos")] 133 | if layout.align() > (1 << 31) { 134 | return None; 135 | } 136 | 137 | let size = layout.size() + layout.align(); 138 | ( 139 | evil_janet::janet_srealloc(ptr.as_ptr() as *mut _, size) as *mut u8, 140 | size, 141 | ) 142 | }; 143 | NonNull::new(ptr::slice_from_raw_parts_mut(raw_ptr, alloc_mem_size)) 144 | } 145 | 146 | /// Deallocates the memory referenced by `ptr`. 147 | /// 148 | /// # Safety 149 | /// `ptr` must denote a block of memory currently allocated via this allocator. 150 | #[inline] 151 | pub unsafe fn free(&self, ptr: NonNull<[u8]>) { 152 | evil_janet::janet_sfree(ptr.as_ptr() as *mut _) 153 | } 154 | } 155 | 156 | #[cfg(feature = "nightly")] 157 | #[cfg_attr(docsrs, doc(cfg(feature = "nightly")))] 158 | unsafe impl Allocator for Scratch { 159 | fn allocate(&self, layout: Layout) -> Result, AllocError> { 160 | self.malloc(layout).ok_or(AllocError) 161 | } 162 | 163 | unsafe fn deallocate(&self, _ptr: NonNull, _layout: Layout) {} 164 | } 165 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | //! This module implements anything required to run a Janet client. 2 | use core::{ 3 | error::Error as StdError, 4 | fmt::{self, Display}, 5 | sync::atomic::{AtomicBool, Ordering}, 6 | }; 7 | 8 | #[cfg(feature = "std")] 9 | use std::thread_local; 10 | 11 | use crate::{ 12 | Janet, JanetTable, 13 | env::{CFunOptions, DefOptions, JanetEnvironment, VarOptions}, 14 | }; 15 | 16 | // There are platforms where AtomicBool doesn't exist 17 | // At some point it would be awesome to find what targets doesn't have support for atomics 18 | // and for those add something else to substitute the AtomicBool. 19 | #[cfg(feature = "std")] 20 | thread_local! { 21 | static INIT: AtomicBool = const { AtomicBool::new(false) }; 22 | } 23 | 24 | #[cfg(not(feature = "std"))] 25 | static INIT: AtomicBool = AtomicBool::new(false); 26 | 27 | /// The possible errors for the [`JanetClient`]. 28 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] 29 | #[non_exhaustive] 30 | pub enum Error { 31 | /// May happen when trying to initialize two or more [`JanetClient`]. 32 | AlreadyInit, 33 | /// May happen when trying to run code that does not compile. 34 | CompileError, 35 | /// May happen when trying to run a Janet code without a environment table. 36 | EnvNotInit, 37 | /// May happen when trying to run code that failed to be parsed. 38 | ParseError, 39 | /// May happen when the VM errors while running code. 40 | RunError, 41 | } 42 | 43 | impl Display for Error { 44 | #[inline] 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | match self { 47 | Self::AlreadyInit => f.pad("Janet client already initialized"), 48 | Self::CompileError => f.pad("Failed to compile code"), 49 | Self::EnvNotInit => f.pad("The environment table was not initialized"), 50 | Self::ParseError => f.pad("Failed to parse code"), 51 | Self::RunError => f.pad("Runtime VM error"), 52 | } 53 | } 54 | } 55 | 56 | #[cfg(feature = "std")] 57 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 58 | impl StdError for Error {} 59 | 60 | /// Janet client that initialize the Janet runtime. 61 | /// 62 | /// If in a `no_std` environment you can only initialize the runtime through the safe 63 | /// interface only once, since the static atomic global cannot be thread local in a 64 | /// `no_std` environment, if you're on a multi-thread + `no_std` environment refer to use 65 | /// [`init_unchecked`]. 66 | /// 67 | /// [`init_unchecked`]: ./struct.JanetClient.html#method.init_unchecked.html 68 | #[derive(Debug)] 69 | pub struct JanetClient { 70 | env_table: Option, 71 | } 72 | 73 | impl JanetClient { 74 | /// Initialize Janet global state. 75 | /// 76 | /// This must be called only once per thread if using Janet in a multithreaded 77 | /// environment, as all Janet global state is thread local by default. 78 | /// 79 | /// If tried to initialize the client more than once it returns a `Err` variant. 80 | #[inline] 81 | pub fn init() -> Result { 82 | #[cfg(feature = "std")] 83 | let init_state = INIT.with(|i| i.swap(true, Ordering::SeqCst)); 84 | #[cfg(not(feature = "std"))] 85 | let init_state = INIT.swap(true, Ordering::SeqCst); 86 | 87 | if init_state { 88 | return Err(Error::AlreadyInit); 89 | } 90 | 91 | // SAFETY: We use a static AtomicBool to make sure that it is started only once (per 92 | // thread if "std" feature activated) 93 | unsafe { evil_janet::janet_init() }; 94 | Ok(Self { env_table: None }) 95 | } 96 | 97 | /// Initialize Janet global state without checking. 98 | /// 99 | /// This must be called only once per thread if using Janet in a multithreaded 100 | /// environment, as all Janet global state is thread local by default. 101 | /// 102 | /// # Safety 103 | /// If initialized more than once per thread, and more than one drop, you can have a 104 | /// double free, if one drop and another continue to execute, it will crash with 105 | /// segmentation fault. 106 | #[inline] 107 | #[must_use = "function is a constructor associated function"] 108 | pub unsafe fn init_unchecked() -> Self { 109 | evil_janet::janet_init(); 110 | Self { env_table: None } 111 | } 112 | 113 | /// Initialize Janet global state and load the default environment of Janet. 114 | /// 115 | /// This must be called only once per thread if using Janet in a multithreaded 116 | /// environment, as all Janet global state is thread local by default. 117 | /// 118 | /// If tried to initialize the client more than once it returns a `Err` variant. 119 | /// 120 | /// The default environment of Janet contains all the Janet C code as well as the 121 | /// code in [`boot.janet`](https://github.com/janet-lang/janet/blob/master/src/boot/boot.janet). 122 | #[inline] 123 | pub fn init_with_default_env() -> Result { 124 | let mut client = Self::init()?; 125 | client.env_table = Some(JanetEnvironment::default()); 126 | Ok(client) 127 | } 128 | 129 | /// Initialize Janet global state. 130 | /// 131 | /// This must be called only once per thread if using Janet in a multithreaded 132 | /// environment, as all Janet global state is thread local by default. 133 | /// 134 | /// If tried to initialize the client more than once it returns a `Err` variant. 135 | /// 136 | /// If an item in the `replacements` table has the same name as a item in the default 137 | /// environment table, the item is replaced by the newer. 138 | #[inline] 139 | pub fn init_with_replacements(replacements: JanetTable<'static>) -> Result { 140 | let mut client = Self::init()?; 141 | client.env_table = Some(JanetEnvironment::with_replacements(replacements)); 142 | Ok(client) 143 | } 144 | 145 | /// Load the default environment of Janet. 146 | /// 147 | /// The default environment of Janet contains all the Janet C code as well as the 148 | /// code in [`boot.janet`](https://github.com/janet-lang/janet/blob/master/src/boot/boot.janet). 149 | #[inline] 150 | #[must_use] 151 | pub fn load_env_default(mut self) -> Self { 152 | self.env_table = Some(JanetEnvironment::default()); 153 | self 154 | } 155 | 156 | /// Load the default environment of Janet with the given `replacements` table. 157 | /// 158 | /// If an item in the `replacements` table has the same name as a item in the default 159 | /// environment table, the item is replaced by the newer. 160 | #[inline] 161 | #[must_use] 162 | pub fn load_env_replacements(mut self, replacements: JanetTable<'static>) -> Self { 163 | self.env_table = Some(JanetEnvironment::with_replacements(replacements)); 164 | self 165 | } 166 | 167 | /// Add a Janet immutable variable to the client environment if it has one, otherwise 168 | /// creates it with the default one. 169 | /// 170 | /// # Examples 171 | /// ``` 172 | /// use janetrs::{Janet, client::JanetClient, env::DefOptions}; 173 | /// # fn main() -> Result<(), Box> { 174 | /// let mut client = JanetClient::init()?; 175 | /// assert!(client.env().is_none()); 176 | /// 177 | /// client.add_def(DefOptions::new("const", 10)); 178 | /// assert!(client.env().is_some()); 179 | /// 180 | /// let c = client.run("const")?; 181 | /// assert!(!c.is_nil()); 182 | /// # Ok(()) 183 | /// # } 184 | /// ``` 185 | #[inline] 186 | pub fn add_def(&mut self, def_opt: DefOptions) { 187 | if self.env().is_none() { 188 | self.env_table = Some(JanetEnvironment::default()); 189 | } 190 | 191 | if let Some(ref mut env) = self.env_table { 192 | env.add_def(def_opt) 193 | } 194 | } 195 | 196 | /// Add a Janet mutable variable to the client environment if it has one, otherwise 197 | /// creates it with the default one. 198 | /// 199 | /// # Examples 200 | /// ``` 201 | /// use janetrs::{Janet, client::JanetClient, env::VarOptions}; 202 | /// # fn main() -> Result<(), Box> { 203 | /// let mut client = JanetClient::init()?; 204 | /// assert!(client.env().is_none()); 205 | /// 206 | /// client.add_var(VarOptions::new("variable", 10)); 207 | /// assert!(client.env().is_some()); 208 | /// 209 | /// let c = client.run("variable")?; 210 | /// assert!(!c.is_nil()); 211 | /// # Ok(()) 212 | /// # } 213 | /// ``` 214 | #[inline] 215 | pub fn add_var(&mut self, var_opt: VarOptions) { 216 | if self.env().is_none() { 217 | self.env_table = Some(JanetEnvironment::default()); 218 | } 219 | 220 | if let Some(ref mut env) = self.env_table { 221 | env.add_var(var_opt); 222 | } 223 | } 224 | 225 | /// Add a Janet C function to the client environment if it has one and register that 226 | /// function in the Janet VM, otherwise creates it with default one. 227 | /// 228 | /// # Examples 229 | /// ``` 230 | /// use janetrs::{Janet, JanetType, client::JanetClient, env::CFunOptions, janet_fn}; 231 | /// 232 | /// #[janet_fn] 233 | /// fn test(_args: &mut [Janet]) -> Janet { 234 | /// Janet::nil() 235 | /// } 236 | /// 237 | /// # fn main() -> Result<(), Box> { 238 | /// let mut client = JanetClient::init()?; 239 | /// assert!(client.env().is_none()); 240 | /// 241 | /// client.add_c_fn(CFunOptions::new(c"test", test_c)); 242 | /// assert!(client.env().is_some()); 243 | /// 244 | /// let c = client.run("test")?; 245 | /// assert_eq!(c.kind(), JanetType::CFunction); 246 | /// # Ok(()) 247 | /// # } 248 | /// ``` 249 | #[inline] 250 | pub fn add_c_fn(&mut self, cfun_opt: CFunOptions<'static>) { 251 | if self.env().is_none() { 252 | self.env_table = Some(JanetEnvironment::default()); 253 | } 254 | 255 | if let Some(ref mut env) = self.env_table { 256 | env.add_c_fn(cfun_opt); 257 | } 258 | } 259 | 260 | /// Run given Janet `code` bytes and if no errors occurs, returns the output of the 261 | /// given `code`. 262 | /// 263 | /// **This function may trigger a GC collection** 264 | /// 265 | /// # Examples 266 | /// ``` 267 | /// use janetrs::{Janet, client::JanetClient}; 268 | /// # fn main() -> Result<(), Box> { 269 | /// let client = JanetClient::init_with_default_env()?; 270 | /// 271 | /// let out = client.run_bytes(b"(def x 10) (+ x x)")?; 272 | /// 273 | /// assert_eq!(Janet::number(20.0), out); 274 | /// 275 | /// # Ok(()) 276 | /// # } 277 | /// ``` 278 | /// 279 | /// 280 | /// ## TODO: 281 | /// Right now the sourcePath value are hardcoded to `c"main"`. 282 | /// Change that the Client struct holds sourcePath. 283 | #[inline] 284 | pub fn run_bytes(&self, code: impl AsRef<[u8]>) -> Result { 285 | let code = code.as_ref(); 286 | let env = match self.env_table.as_ref() { 287 | Some(e) => e.table(), 288 | None => return Err(Error::EnvNotInit), 289 | }; 290 | 291 | let mut out = Janet::nil(); 292 | 293 | let res = unsafe { 294 | evil_janet::janet_dobytes( 295 | env.raw, 296 | code.as_ptr(), 297 | code.len() as i32, 298 | c"main".as_ptr(), 299 | &mut out.inner, 300 | ) 301 | }; 302 | 303 | match res { 304 | 0x01 => Err(Error::RunError), 305 | 0x02 => Err(Error::CompileError), 306 | 0x04 => Err(Error::ParseError), 307 | _ => Ok(out), 308 | } 309 | } 310 | 311 | /// Run given Janet `code` string and if no errors occurs, returns the output of the 312 | /// given `code`. 313 | /// 314 | /// **This function may trigger a GC collection** 315 | /// 316 | /// # Examples 317 | /// ``` 318 | /// use janetrs::{Janet, client::JanetClient}; 319 | /// # fn main() -> Result<(), Box> { 320 | /// let client = JanetClient::init_with_default_env()?; 321 | /// 322 | /// let out = client.run("(def x 10) (+ x x)")?; 323 | /// 324 | /// assert_eq!(Janet::number(20.0), out); 325 | /// 326 | /// # Ok(()) 327 | /// # } 328 | /// ``` 329 | /// 330 | /// ## TODO: 331 | /// Right now the sourcePath value are hardcoded to `b"main"`, 332 | /// respectively. 333 | /// Change that the Client struct hold sourcePath. 334 | #[inline] 335 | pub fn run(&self, code: impl AsRef) -> Result { 336 | let code = code.as_ref(); 337 | self.run_bytes(code.as_bytes()) 338 | } 339 | 340 | /// Return a reference of the environment table of the runtime if it exist. 341 | #[inline] 342 | #[must_use] 343 | pub const fn env(&self) -> Option<&JanetEnvironment> { 344 | self.env_table.as_ref() 345 | } 346 | 347 | /// Return a unique reference of the environment table of the runtime if it exist. 348 | #[inline] 349 | pub fn env_mut(&mut self) -> Option<&mut JanetEnvironment> { 350 | self.env_table.as_mut() 351 | } 352 | } 353 | 354 | impl Drop for JanetClient { 355 | #[inline] 356 | fn drop(&mut self) { 357 | // Reset the INIT to false 358 | #[cfg(feature = "std")] 359 | INIT.with(|i| i.swap(false, Ordering::SeqCst)); 360 | 361 | #[cfg(not(feature = "std"))] 362 | INIT.swap(false, Ordering::SeqCst); 363 | 364 | unsafe { evil_janet::janet_deinit() } 365 | } 366 | } 367 | 368 | 369 | #[cfg(test)] 370 | mod tests { 371 | use super::*; 372 | 373 | #[test] 374 | fn double_init() { 375 | let c1 = JanetClient::init(); 376 | let c2 = JanetClient::init(); 377 | let c3 = JanetClient::init(); 378 | 379 | assert!(c1.is_ok()); 380 | assert_eq!(Error::AlreadyInit, c2.unwrap_err()); 381 | assert_eq!(Error::AlreadyInit, c3.unwrap_err()); 382 | } 383 | 384 | #[test] 385 | fn env_not_init() -> Result<(), Error> { 386 | let client = JanetClient::init()?; 387 | 388 | let a = client.run("()"); 389 | 390 | assert_eq!(Err(Error::EnvNotInit), a); 391 | 392 | Ok(()) 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /src/env.rs: -------------------------------------------------------------------------------- 1 | //! Module for the Janet VM environment structure, methods and functions. 2 | use core::{ffi::CStr, ptr}; 3 | 4 | use crate::{ 5 | Janet, JanetBuffer, JanetKeyword, JanetString, JanetSymbol, JanetTable, 6 | function::JanetRawCFunction, 7 | }; 8 | 9 | /// Representation of the Janet runtime environment, like global definitions, available 10 | /// functions and macros, etc. 11 | /// 12 | /// The Janet environment is represented as a [`JanetTable`]. Understanding it may prove 13 | /// helpful. 14 | #[derive(Debug)] 15 | #[repr(transparent)] 16 | pub struct JanetEnvironment(JanetTable<'static>); 17 | 18 | impl JanetEnvironment { 19 | /// Creates a new environment with Janet default environment. 20 | #[inline] 21 | #[must_use = "function is a constructor associated function"] 22 | pub fn new() -> Self { 23 | // SAFETY: `janet_core_env` never returns a null pointer 24 | Self(unsafe { JanetTable::from_raw(evil_janet::janet_core_env(ptr::null_mut())) }) 25 | } 26 | 27 | /// Creates a new environment with Janet default environment and the given `env` 28 | /// items. 29 | /// 30 | /// Every item with the same name as the ones in the Janet default environment will 31 | /// replace the original. 32 | #[inline] 33 | #[must_use = "function is a constructor associated function"] 34 | pub fn with_replacements(mut replacements: JanetTable<'static>) -> Self { 35 | // SAFETY: `janet_core_env` never returns a null pointer 36 | Self(unsafe { JanetTable::from_raw(evil_janet::janet_core_env(replacements.as_mut_raw())) }) 37 | } 38 | 39 | /// Add a Janet immutable variable in the environment. 40 | #[inline] 41 | pub fn add_def(&mut self, def_opt: DefOptions) { 42 | let mut def = JanetTable::with_capacity(2); 43 | def.insert(JanetKeyword::new("value"), def_opt.value); 44 | 45 | if let Some(doc) = def_opt.doc { 46 | def.insert(JanetKeyword::new("doc"), doc); 47 | } 48 | 49 | // insert the source information only on 1.17.0 or latter. 50 | if crate::check_janet_version!("1.17.0") { 51 | if let (Some(source_file), Some(source_line)) = 52 | (def_opt.source_file, def_opt.source_line) 53 | { 54 | let source_map = crate::tuple![source_file, source_line as f64, 1]; 55 | def.insert(JanetKeyword::new("source-map"), source_map); 56 | } 57 | } 58 | 59 | 60 | self.0.insert(def_opt.name, def); 61 | } 62 | 63 | /// Add a Janet mutable variable in the environment. 64 | #[inline] 65 | pub fn add_var(&mut self, var_opt: VarOptions) { 66 | let arr = crate::array![var_opt.value]; 67 | let mut var = JanetTable::with_capacity(2); 68 | var.insert(JanetKeyword::new("ref"), arr); 69 | 70 | if let Some(doc) = var_opt.doc { 71 | var.insert(JanetKeyword::new("doc"), doc); 72 | } 73 | 74 | // insert the source information only on 1.17.0 or latter. 75 | if crate::check_janet_version!("1.17.0") { 76 | if let (Some(source_file), Some(source_line)) = 77 | (var_opt.source_file, var_opt.source_line) 78 | { 79 | let source_map = crate::tuple![source_file, source_line as f64, 1]; 80 | var.insert(JanetKeyword::new("source-map"), source_map); 81 | } 82 | } 83 | 84 | self.0.insert(JanetSymbol::new(var_opt.name), var); 85 | } 86 | 87 | /// Add a C function in the environment and register it on the VM. 88 | #[crate::cjvg("1.0.0", "1.17.0")] 89 | #[inline] 90 | pub fn add_c_fn(&mut self, cfun_opt: CFunOptions<'static>) { 91 | let namespace = cfun_opt 92 | .namespace 93 | .map(|s| s.as_ptr()) 94 | .unwrap_or(ptr::null()); 95 | let name = if cfun_opt.name.is_empty() { 96 | ptr::null() 97 | } else { 98 | cfun_opt.name.as_ptr() 99 | }; 100 | let doc = cfun_opt.doc.map(|s| s.as_ptr()).unwrap_or(ptr::null()); 101 | 102 | let reg = [ 103 | crate::lowlevel::JanetReg { 104 | name, 105 | cfun: Some(cfun_opt.value), 106 | documentation: doc, 107 | }, 108 | crate::lowlevel::JanetReg { 109 | name: core::ptr::null(), 110 | cfun: None, 111 | documentation: core::ptr::null(), 112 | }, 113 | ]; 114 | 115 | if cfun_opt.namespace.is_some() { 116 | unsafe { evil_janet::janet_cfuns_prefix(self.0.raw, namespace, reg.as_ptr()) } 117 | } else { 118 | unsafe { evil_janet::janet_cfuns(self.0.raw, namespace, reg.as_ptr()) } 119 | } 120 | } 121 | 122 | /// Add a C function in the environment and register it on the VM. 123 | #[crate::cjvg("1.17.0")] 124 | #[inline] 125 | pub fn add_c_fn(&mut self, cfun_opt: CFunOptions<'static>) { 126 | let namespace = cfun_opt 127 | .namespace 128 | .map(|s| s.as_ptr()) 129 | .unwrap_or(ptr::null()); 130 | let name = if cfun_opt.name.is_empty() { 131 | ptr::null() 132 | } else { 133 | cfun_opt.name.as_ptr() 134 | }; 135 | let doc = cfun_opt.doc.map(|s| s.as_ptr()).unwrap_or(ptr::null()); 136 | 137 | let source_file = cfun_opt 138 | .source_file 139 | .map(|s| s.as_ptr()) 140 | .unwrap_or(ptr::null()); 141 | 142 | let reg = [ 143 | crate::lowlevel::JanetRegExt { 144 | name, 145 | cfun: Some(cfun_opt.value), 146 | documentation: doc, 147 | source_file, 148 | source_line: cfun_opt.source_line.unwrap_or_default() as _, 149 | }, 150 | crate::lowlevel::JanetRegExt { 151 | name: ptr::null(), 152 | cfun: None, 153 | documentation: ptr::null(), 154 | source_file: ptr::null(), 155 | source_line: 0, 156 | }, 157 | ]; 158 | 159 | if cfun_opt.namespace.is_some() { 160 | unsafe { evil_janet::janet_cfuns_ext_prefix(self.0.raw, namespace, reg.as_ptr()) } 161 | } else { 162 | unsafe { evil_janet::janet_cfuns_ext(self.0.raw, namespace, reg.as_ptr()) } 163 | } 164 | } 165 | 166 | /// Search the given `symbol` in the environment and returns the value if found. 167 | #[inline] 168 | pub fn resolve<'a>(&self, symbol: impl Into>) -> Option { 169 | let symbol = symbol.into(); 170 | let mut out = Janet::nil(); 171 | 172 | // SAFETY: `janet_resolve` does not mutate the inner table and should be safe to use 173 | unsafe { 174 | evil_janet::janet_resolve(self.0.as_raw() as *mut _, symbol.as_raw(), &mut out.inner) 175 | }; 176 | 177 | if out.is_nil() { None } else { Some(out) } 178 | } 179 | 180 | /// Get a reference the underlying environment table. 181 | #[inline] 182 | #[must_use] 183 | pub const fn table(&self) -> &JanetTable { 184 | &self.0 185 | } 186 | } 187 | 188 | impl Default for JanetEnvironment { 189 | fn default() -> Self { 190 | Self::new() 191 | } 192 | } 193 | 194 | /// A builder for a Janet immutable variable definition. 195 | /// 196 | /// # Example 197 | pub struct DefOptions<'a> { 198 | name: JanetSymbol<'a>, 199 | value: Janet, 200 | doc: Option<&'a str>, 201 | source_file: Option>, 202 | source_line: Option, 203 | } 204 | 205 | impl<'a> DefOptions<'a> { 206 | /// Creates a new Janet immutable variable definition with given `name` and `value`. 207 | pub fn new(name: impl Into>, value: impl Into) -> Self { 208 | Self { 209 | name: name.into(), 210 | value: value.into(), 211 | doc: None, 212 | source_file: None, 213 | source_line: None, 214 | } 215 | } 216 | 217 | /// Configure the docs of the Janet definition. 218 | #[must_use] 219 | pub fn doc(mut self, doc: &'a str) -> Self { 220 | self.doc = Some(doc); 221 | self 222 | } 223 | 224 | /// Configure the source file of the Janet definition. 225 | #[must_use] 226 | pub fn source_file(mut self, source_file: impl Into>) -> Self { 227 | self.source_file = Some(source_file.into()); 228 | self 229 | } 230 | 231 | /// Configure the source line of the Janet definition. 232 | #[must_use] 233 | pub fn source_line(mut self, source_line: u32) -> Self { 234 | self.source_line = Some(source_line); 235 | self 236 | } 237 | } 238 | 239 | /// A builder for a Janet variable definition. 240 | /// 241 | /// # Example 242 | pub struct VarOptions<'a> { 243 | name: JanetSymbol<'a>, 244 | value: Janet, 245 | doc: Option<&'a str>, 246 | source_file: Option>, 247 | source_line: Option, 248 | } 249 | 250 | impl<'a> VarOptions<'a> { 251 | /// Creates a new Janet mutable variable definition with given `name` and `value`. 252 | pub fn new(name: impl Into>, value: impl Into) -> Self { 253 | Self { 254 | name: name.into(), 255 | value: value.into(), 256 | doc: None, 257 | source_file: None, 258 | source_line: None, 259 | } 260 | } 261 | 262 | /// Configure the docs of the Janet mutable variable definition. 263 | #[must_use] 264 | pub fn doc(mut self, doc: &'a str) -> Self { 265 | self.doc = Some(doc); 266 | self 267 | } 268 | 269 | /// Configure the source file of the Janet mutable variable definition. 270 | #[must_use] 271 | pub fn source_file(mut self, source_file: impl Into>) -> Self { 272 | self.source_file = Some(source_file.into()); 273 | self 274 | } 275 | 276 | /// Configure the source line of the Janet mutable variable definition. 277 | #[must_use] 278 | pub fn source_line(mut self, source_line: u32) -> Self { 279 | self.source_line = Some(source_line); 280 | self 281 | } 282 | } 283 | 284 | pub struct CFunOptions<'a> { 285 | namespace: Option<&'a CStr>, 286 | name: &'a CStr, 287 | value: JanetRawCFunction, 288 | doc: Option<&'a CStr>, 289 | source_file: Option<&'a CStr>, 290 | source_line: Option, 291 | } 292 | 293 | impl<'a> CFunOptions<'a> { 294 | /// Creates a new Janet C function definition with given `name` and `value`. 295 | pub fn new(name: &'a CStr, value: JanetRawCFunction) -> Self { 296 | Self { 297 | namespace: None, 298 | name, 299 | value, 300 | doc: None, 301 | source_file: None, 302 | source_line: None, 303 | } 304 | } 305 | 306 | /// Configure the namespace of the Janet C function definition. 307 | #[must_use] 308 | pub fn namespace(mut self, namespace: &'a CStr) -> Self { 309 | self.namespace = Some(namespace); 310 | self 311 | } 312 | 313 | /// Configure the docs of the Janet C function definition. 314 | #[must_use] 315 | pub fn doc(mut self, doc: &'a CStr) -> Self { 316 | self.doc = Some(doc); 317 | self 318 | } 319 | 320 | /// Configure the source file of the Janet C function definition. 321 | #[must_use] 322 | pub fn source_file(mut self, source_file: &'a CStr) -> Self { 323 | self.source_file = Some(source_file); 324 | self 325 | } 326 | 327 | /// Configure the source line of the Janet C function definition. 328 | #[must_use] 329 | pub fn source_line(mut self, source_line: u32) -> Self { 330 | self.source_line = Some(source_line); 331 | self 332 | } 333 | } 334 | 335 | /// Set a dynamic binding in the VM runtime, binding a `value` to a `keyword`. 336 | /// 337 | /// Once set, you can later retrieve it by using [`Janet::dynamic`] fucntion. 338 | /// 339 | /// # Examples 340 | /// ``` 341 | /// use janetrs::Janet; 342 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 343 | /// 344 | /// assert_eq!(Janet::dynamic("my_dyn_value"), None); 345 | /// janetrs::env::set_dynamic("my_dyn_value", Janet::number(10.0)); 346 | /// assert_eq!(Janet::dynamic("my_dyn_value"), Some(Janet::number(10.0))); 347 | /// ``` 348 | pub fn set_dynamic(keyword: impl AsRef<[u8]>, value: Janet) { 349 | use core::ffi::c_char; 350 | unsafe fn set_dyn(keyword: *const c_char, value: Janet) { 351 | evil_janet::janet_setdyn(keyword, value.into()) 352 | } 353 | 354 | let keyword = keyword.as_ref(); 355 | if keyword.contains(&b'\0') { 356 | unsafe { set_dyn(keyword.as_ptr().cast(), value) } 357 | } else { 358 | let mut keyword: JanetBuffer = keyword.into(); 359 | keyword.push('\0'); 360 | 361 | unsafe { set_dyn(keyword.as_ptr().cast(), value) } 362 | } 363 | } 364 | 365 | 366 | #[cfg(all(test, any(feature = "amalgation", feature = "link-system")))] 367 | mod tests { 368 | use crate::DeepEq; 369 | 370 | use super::*; 371 | 372 | #[test] 373 | fn add_def() -> Result<(), crate::client::Error> { 374 | let mut _client = crate::client::JanetClient::init_with_default_env()?; 375 | let env = _client.env_mut().unwrap(); 376 | 377 | env.add_def(DefOptions::new("test", Janet::number(1.0))); 378 | 379 | let test1 = env.resolve("test").expect("valid def"); 380 | assert_eq!(test1, Janet::number(1.0)); 381 | 382 | Ok(()) 383 | } 384 | 385 | #[test] 386 | fn add_var() -> Result<(), crate::client::Error> { 387 | use crate::array; 388 | let mut _client = crate::client::JanetClient::init_with_default_env()?; 389 | let env = _client.env_mut().unwrap(); 390 | 391 | env.add_var(VarOptions::new("test", Janet::number(1.0))); 392 | 393 | let test1 = env.resolve("test").expect("valid var"); 394 | assert!(test1.deep_eq(&Janet::from(array![1.0]))); 395 | 396 | Ok(()) 397 | } 398 | 399 | #[test] 400 | fn set_dynamic() -> Result<(), crate::client::Error> { 401 | let _client = crate::client::JanetClient::init()?; 402 | 403 | assert_eq!(Janet::dynamic("my_dyn_value"), None); 404 | super::set_dynamic("my_dyn_value", Janet::number(10.0)); 405 | assert_eq!(Janet::dynamic("my_dyn_value"), Some(Janet::number(10.0))); 406 | 407 | Ok(()) 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /src/gc.rs: -------------------------------------------------------------------------------- 1 | use crate::Janet; 2 | 3 | /// The Janet Garbage Collector type. 4 | /// 5 | /// It allows the use of garbage collection operations in the Janet public C API. 6 | #[derive(Debug, Default)] 7 | pub struct JanetGc { 8 | _phantom: core::marker::PhantomData<*const ()>, 9 | } 10 | 11 | impl JanetGc { 12 | /// Obtain the [`JanetGc`]. 13 | #[inline] 14 | #[must_use = "function is a constructor associated function"] 15 | pub fn obtain() -> Self { 16 | Self { 17 | _phantom: core::marker::PhantomData, 18 | } 19 | } 20 | 21 | /// Run the garbage collection if there is nothing locking or suspending the garbage 22 | /// collector, like an active [`JanetGcLockGuard`] or a call to a Janet C API that 23 | /// locks the GC. 24 | /// 25 | /// If there is something locking the garbage collection, it simply does a no-op. 26 | /// 27 | /// # Safety 28 | /// This function will free all memory allocated with the [Janet scratch memory 29 | /// API](crate::allocator::Scratch) and any [non-rooted](JanetGc::root) object 30 | /// that have no reference to a live object (as example, an empty 31 | /// [`JanetTable`](crate::JanetTable) or [`JanetArray`](crate::JanetArray) ) 32 | #[inline] 33 | pub unsafe fn collect(&self) { 34 | evil_janet::janet_collect() 35 | } 36 | 37 | /// Lock the Janet GC and suspend any garbage collection until the guard is dropped. 38 | /// 39 | /// If there is any attempt to manually trigger the garbage collection while there is 40 | /// a [`JanetGcLockGuard`] active (or any unsafe call to the Janet C API locking the 41 | /// GC) 42 | #[inline] 43 | #[must_use = "JanetGcLockGuard will be dropped if the result is not used"] 44 | pub fn lock(&self) -> JanetGcLockGuard { 45 | let handle = unsafe { evil_janet::janet_gclock() }; 46 | JanetGcLockGuard::new(handle) 47 | } 48 | 49 | /// Immediately drops the guard, and consequently unlocks the Janet GC. 50 | /// 51 | /// This function is equivalent to calling [`drop`] on the guard but is more 52 | /// self-documenting. Alternately, the guard will be automatically dropped when it 53 | /// goes out of scope. 54 | /// 55 | /// # Example: 56 | /// 57 | /// ``` 58 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 59 | /// use janetrs::JanetGc; 60 | /// 61 | /// let gc = JanetGc::obtain(); 62 | /// 63 | /// let mut guard = gc.lock(); 64 | /// 65 | /// // do stuff with the Janet GC locked 66 | /// 67 | /// JanetGc::unlock(guard); 68 | /// ``` 69 | #[inline] 70 | pub fn unlock(guard: JanetGcLockGuard) { 71 | drop(guard) 72 | } 73 | 74 | /// Roots the `value` to the GC. This prevents the GC from removing the `value` and 75 | /// all of its children in a garbage collection. 76 | /// 77 | /// The returned guard will [`unroot`](JanetGc::unroot) the `value` when dropped. 78 | #[inline] 79 | #[must_use = "JanetGcRootGuard will be dropped if the result is not used"] 80 | pub fn root(&self, value: Janet) -> JanetGcRootGuard { 81 | JanetGcRootGuard::new(value) 82 | } 83 | 84 | /// Immediately drops the guard, and consequently unlocks the Janet GC. 85 | /// 86 | /// This function is equivalent to calling [`drop`] on the guard but is more 87 | /// self-documenting. Alternately, the guard will be automatically dropped when it 88 | /// goes out of scope. 89 | #[inline] 90 | pub fn unroot(guard: JanetGcRootGuard) { 91 | drop(guard) 92 | } 93 | } 94 | 95 | /// An RAII implementation of a “scoped lock” of the Janet GC. When this structure is 96 | /// dropped (falls out of scope), the lock will be unlocked. 97 | #[derive(Debug)] 98 | pub struct JanetGcLockGuard { 99 | handle: i32, 100 | _phantom: core::marker::PhantomData<*const ()>, 101 | } 102 | 103 | impl JanetGcLockGuard { 104 | #[inline] 105 | pub(crate) fn new(handle: i32) -> Self { 106 | Self { 107 | handle, 108 | _phantom: core::marker::PhantomData, 109 | } 110 | } 111 | } 112 | 113 | impl Drop for JanetGcLockGuard { 114 | #[inline] 115 | fn drop(&mut self) { 116 | unsafe { evil_janet::janet_gcunlock(self.handle) } 117 | } 118 | } 119 | 120 | /// An RAII implementation of a rooted [`Janet`] value. When this structure is dropped 121 | /// (falls out of scope), the rooting will be undone. 122 | #[derive(Debug)] 123 | pub struct JanetGcRootGuard { 124 | value: Janet, 125 | _phantom: core::marker::PhantomData<*const ()>, 126 | } 127 | 128 | impl JanetGcRootGuard { 129 | #[inline] 130 | fn new(value: Janet) -> Self { 131 | unsafe { evil_janet::janet_gcroot(value.inner) }; 132 | Self { 133 | value, 134 | _phantom: core::marker::PhantomData, 135 | } 136 | } 137 | } 138 | 139 | impl Drop for JanetGcRootGuard { 140 | #[inline] 141 | fn drop(&mut self) { 142 | // SAFETY: Since we unrooting the same value we rooted, this should always work. 143 | // For extra safety, below it's add a debug assert to be sure on debug compilations. 144 | let res = unsafe { evil_janet::janet_gcunroot(self.value.inner) }; 145 | 146 | // Assert in debug mode that the result is 1 147 | debug_assert_eq!(res, 1) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Janetrs 2 | //! 3 | //! A crate with high level bindings to Janet C API. 4 | //! 5 | //! ## Goals 6 | //! Provide a safe and ergonomic interface to the Janet C API to create Janet clients and 7 | //! Janet modules/libraries using Rust. 8 | //! 9 | //! This project still are in it's early stages, so breaking changes may happen, there is 10 | //! no minimal supported Rust version (MSRV) yet. 11 | //! 12 | //! Notice that most doc tests will fail if the feature "amalgation" or "link-system" 13 | //! aren't set, because most of then need it for the Janet runtime to function properly. 14 | //! 15 | //! ## Cargo Features 16 | //! 17 | //! - `std` (default): Enable some trait impl for types that only exist on the `std` 18 | //! - `unicode` (default): Enable more methods for JanetString and JanetBuffer 19 | //! - `inline-more`: More aggressive inlining 20 | //! - `amalgation` (default): Link the Janet runtime to the package, enabling to use the 21 | //! client module 22 | //! - `system`: Use system header to get Janet functions 23 | //! - `link-system`: Link the Janet runtime to the package from the system, enabling to 24 | //! use the client module 25 | //! - `nightly`: Enable some parts of the crate that uses nightly features, to use this 26 | //! feature you must compile the crate using a nightly rust version 27 | //! 28 | //! ## Environment variables 29 | //! 30 | //! **These variables are only used when the `amalgation` feature is enabled** 31 | //! 32 | //! It is possible to use environment variables to overwrite some Janet definitions. 33 | //! 34 | //! - `JANET_RECURSION_GUARD=` 35 | //! - `JANET_MAX_PROTO_DEPTH=` 36 | //! - `JANET_MAX_MACRO_EXPAND=` 37 | //! - `JANET_STACK_MAX=` 38 | //! 39 | //! ## Licensing 40 | //! This software is licensed under the terms of the [MIT Public License](./LICENSE). 41 | //! 42 | //! ### TODO: Types/Traits: Lacking or Incomplete 43 | //! - [ ] Marshaling 44 | //! 45 | //! `[ ]: Lacking` 46 | //! `[I]: Incomplete` 47 | //! `[X]: Done` 48 | //! 49 | //! Probably there is much more missing, for that you can use the 50 | //! [`lowlevel`] module to access the raw C API of Janet if needed 51 | //! 52 | //! ### TODO: Lib level 53 | //! - Better docs. 54 | //! - Marshalling mechanism 55 | #![no_std] 56 | #![cfg_attr(feature = "nightly", feature(allocator_api))] 57 | #![cfg_attr(docsrs, feature(doc_cfg))] 58 | 59 | // Cause compilation error when both amalgation and system is set 60 | #[cfg(all(feature = "amalgation", feature = "link-system"))] 61 | compile_error!(r#"You can only use either "amalgation" or "link-system" feature, not both."#); 62 | #[cfg(all(feature = "amalgation", feature = "system"))] 63 | compile_error!(r#"You can only use either "amalgation" or "system" feature, not both."#); 64 | 65 | // Janet requires allocation 66 | extern crate alloc; 67 | 68 | #[cfg(any(test, feature = "std"))] 69 | extern crate std; 70 | 71 | /// This module has a expose the entire Janet C-API structures, constants and functions. 72 | /// 73 | /// This module exists in the case of a functionality of the Janet C-API can't be, or is 74 | /// not yet, implemented in a safe abstraction. 75 | pub mod lowlevel { 76 | pub use evil_janet::*; 77 | } 78 | 79 | pub mod allocator; 80 | #[cfg(any(feature = "amalgation", feature = "link-system"))] 81 | #[cfg_attr(docsrs, doc(cfg(any(feature = "amalgation", feature = "link-system"))))] 82 | pub mod client; 83 | pub mod env; 84 | mod gc; 85 | mod macros; 86 | mod types; 87 | pub mod util; 88 | 89 | pub use types::*; 90 | 91 | pub use gc::{JanetGc, JanetGcLockGuard, JanetGcRootGuard}; 92 | 93 | pub use janetrs_macros::{check_janet_version, cjvg, declare_janet_mod, janet_fn, janet_version}; 94 | -------------------------------------------------------------------------------- /src/types/abstract.rs: -------------------------------------------------------------------------------- 1 | //! Module of the JanetAbstract abstractions. 2 | //! 3 | //! In this module you can find the definitions of types and traits to allow to work with 4 | //! [`JanetAbstract`]. Most of those are re-exported at the supermodule of this module. 5 | #![allow( 6 | unpredictable_function_pointer_comparisons, 7 | reason = "Not a better option until 1.85.0" 8 | )] 9 | use core::{ 10 | cell::Cell, cmp::Ordering, ffi::c_void, fmt, marker::PhantomData, mem::ManuallyDrop, ptr, 11 | }; 12 | 13 | pub use evil_janet::JanetAbstractType; 14 | 15 | /// Possible error trying to get the abstract value. 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 17 | pub enum AbstractError { 18 | /// [`JanetAbstract`] head size information not the same as the requested 19 | /// [`IsJanetAbstract`] type 20 | MismatchedSize, 21 | /// [`JanetAbstract`] head [`JanetAbstractType`] information not the same as the 22 | /// requested [`IsJanetAbstract`] [`JanetAbstractType`] 23 | MismatchedAbstractType, 24 | /// Pointer to the data is NULL 25 | NullDataPointer, 26 | } 27 | 28 | impl fmt::Display for AbstractError { 29 | #[inline] 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | match self { 32 | Self::MismatchedSize => f.pad("Mismatched size between requested type and actual type"), 33 | Self::MismatchedAbstractType => { 34 | f.pad("Mismatched fn pointers between requested type and actual type") 35 | }, 36 | Self::NullDataPointer => f.pad("Data pointer is NULL"), 37 | } 38 | } 39 | } 40 | 41 | impl core::error::Error for AbstractError {} 42 | 43 | /// Type that represents the Janet Abstract type. 44 | /// 45 | /// Janet Abstract types is the way to expose non-native types to the Janet Runtime and 46 | /// allow the Janet user to interact with them. 47 | /// 48 | /// It works like a `*mut c_void` pointer, but the memory it uses are tracked by the Janet 49 | /// Garbage Collector. 50 | #[derive(PartialEq, Eq)] 51 | #[repr(transparent)] 52 | pub struct JanetAbstract { 53 | pub(crate) raw: *mut c_void, 54 | phantom: PhantomData>, 55 | } 56 | 57 | impl JanetAbstract { 58 | /// Creates a `JanetAbstract` using information from the type that can be used as 59 | /// `JanetAbstract`. 60 | /// 61 | /// This function manually wraps the `value` in a [`ManuallyDrop`] to avoid the 62 | /// value being dropped when the `value` is assigned to the `JanetAbstract` internal 63 | /// raw pointer. 64 | /// 65 | /// Note that [`IsJanetAbstract`] is implemented for [`ManuallyDrop`] of any type that 66 | /// implements [`IsJanetAbstract`]. 67 | #[inline] 68 | pub fn new(value: A) -> Self { 69 | let s = Self { 70 | raw: unsafe { evil_janet::janet_abstract(A::type_info(), A::SIZE as _) }, 71 | phantom: PhantomData, 72 | }; 73 | 74 | // SAFETY: The type are the same since `s` was created with `A` type data. 75 | unsafe { 76 | ptr::write(s.raw as *mut _, value); 77 | } 78 | 79 | s 80 | } 81 | 82 | /// Creates a JanetAbstract from a raw pointer 83 | /// 84 | /// # Safety 85 | /// This function doesn't check anything. 86 | #[inline] 87 | pub unsafe fn from_raw(raw: *mut c_void) -> Self { 88 | Self { 89 | raw, 90 | phantom: PhantomData, 91 | } 92 | } 93 | 94 | /// Returns a reference to the abstract type data as `A` 95 | /// 96 | /// # Safety 97 | /// This function doesn't check if the underlying data of the `JanetAbstract` object 98 | /// and the requested type `A` are the same. 99 | #[inline] 100 | #[must_use] 101 | pub unsafe fn get_unchecked(&self) -> &A::Get { 102 | &*(self.raw as *const A::Get) 103 | } 104 | 105 | /// Returns a mutable reference to the abstract type data as `A` 106 | /// 107 | /// # Safety 108 | /// This function doesn't check if the underlying data of the `JanetAbstract` object 109 | /// and the requested type `A` are the same. 110 | #[inline] 111 | pub unsafe fn get_mut_unchecked(&mut self) -> &mut A::Get { 112 | &mut *(self.raw as *mut A::Get) 113 | } 114 | 115 | /// Check if the `JanetAbstract` data is of the type `A`. 116 | #[inline] 117 | #[must_use] 118 | pub fn is(&self) -> bool { 119 | if self.size() != A::SIZE { 120 | return false; 121 | } 122 | let ty_self = self.type_info(); 123 | let ty_a = A::type_info(); 124 | 125 | if ty_self.call != ty_a.call 126 | || ty_self.compare != ty_a.compare 127 | || ty_self.tostring != ty_a.tostring 128 | || ty_self.marshal != ty_a.marshal 129 | || ty_self.unmarshal != ty_a.unmarshal 130 | || ty_self.hash != ty_a.hash 131 | || ty_self.next != ty_a.next 132 | || ty_self.put != ty_a.put 133 | || ty_self.get != ty_a.get 134 | || ty_self.gc != ty_a.gc 135 | || ty_self.gcmark != ty_a.gcmark 136 | { 137 | return false; 138 | } 139 | 140 | true 141 | } 142 | 143 | fn check(&self) -> Result<(), AbstractError> { 144 | if self.size() != A::SIZE { 145 | return Err(AbstractError::MismatchedSize); 146 | } 147 | let ty_self = self.type_info(); 148 | let ty_a = A::type_info(); 149 | 150 | if ty_self.call != ty_a.call 151 | || ty_self.compare != ty_a.compare 152 | || ty_self.tostring != ty_a.tostring 153 | || ty_self.marshal != ty_a.marshal 154 | || ty_self.unmarshal != ty_a.unmarshal 155 | || ty_self.hash != ty_a.hash 156 | || ty_self.next != ty_a.next 157 | || ty_self.put != ty_a.put 158 | || ty_self.get != ty_a.get 159 | || ty_self.gc != ty_a.gc 160 | || ty_self.gcmark != ty_a.gcmark 161 | { 162 | return Err(AbstractError::MismatchedAbstractType); 163 | } 164 | 165 | Ok(()) 166 | } 167 | 168 | /// Returns a reference to value if it's the same kind of abstract. 169 | /// 170 | /// # Error 171 | /// This function may return [`AbstractError::MismatchedSize`] if this object size is 172 | /// different of requested type `A` size, [`AbstractError::MismatchedAbstractType`] 173 | /// if any of the function pointer in the [`JanetAbstractType`] are different, or 174 | /// [`AbstractError::NullDataPointer`] if the JanetAbstract is in a uninitialized 175 | /// state. 176 | #[inline] 177 | pub fn get(&self) -> Result<&A::Get, AbstractError> { 178 | self.check::()?; 179 | 180 | let ptr = self.raw as *const A::Get; 181 | if ptr.is_null() { 182 | return Err(AbstractError::NullDataPointer); 183 | } 184 | 185 | // SAFETY: The pointer was checked if it's NULL 186 | Ok(unsafe { &*ptr }) 187 | } 188 | 189 | /// Returns a exclusive reference to value if it's the same kind of abstract. 190 | /// 191 | /// # Error 192 | /// This function may return [`AbstractError::MismatchedSize`] if this object size is 193 | /// different of requested type `A` size, [`AbstractError::MismatchedAbstractType`] 194 | /// if any of the function pointer in the [`JanetAbstractType`] are different, or 195 | /// [`AbstractError::NullDataPointer`] if the JanetAbstract is in a uninitialized 196 | /// state. 197 | #[inline] 198 | pub fn get_mut(&mut self) -> Result<&mut A::Get, AbstractError> { 199 | self.check::()?; 200 | 201 | let ptr = self.raw as *mut A::Get; 202 | if ptr.is_null() { 203 | return Err(AbstractError::NullDataPointer); 204 | } 205 | 206 | // SAFETY: The pointer was checked if it's NULL 207 | Ok(unsafe { &mut *(ptr) }) 208 | } 209 | 210 | /// Takes the value out of this JanetAbstract, moving it back to an uninitialized 211 | /// state. 212 | /// 213 | /// # Error 214 | /// This function may return [`AbstractError::MismatchedSize`] if this object size is 215 | /// different of requested type `A` size, [`AbstractError::MismatchedAbstractType`] 216 | /// if any of the function pointer in the [`JanetAbstractType`] are different, or 217 | /// [`AbstractError::NullDataPointer`] if the JanetAbstract is in a uninitialized 218 | /// state. 219 | pub fn take(&mut self) -> Result { 220 | self.check::()?; 221 | 222 | let ptr = self.raw as *mut A; 223 | if ptr.is_null() { 224 | return Err(AbstractError::NullDataPointer); 225 | } 226 | 227 | // SAFETY: The pointer was checked if it's NULL 228 | let value = unsafe { ptr::read(ptr) }; 229 | self.raw = ptr::null_mut(); 230 | Ok(value) 231 | } 232 | 233 | /// Extracts the value from the ManuallyDrop container. 234 | /// 235 | /// This allows the value to be dropped again. 236 | /// 237 | /// # Error 238 | /// This function may return [`AbstractError::MismatchedSize`] if this object size is 239 | /// different of requested type `A` size, [`AbstractError::MismatchedAbstractType`] 240 | /// if any of the function pointer in the [`JanetAbstractType`] are different, or 241 | /// [`AbstractError::NullDataPointer`] if the JanetAbstract is in a uninitialized 242 | /// state. 243 | pub fn into_inner(self) -> Result { 244 | self.check::()?; 245 | 246 | let ptr = self.raw as *mut A; 247 | if ptr.is_null() { 248 | return Err(AbstractError::NullDataPointer); 249 | } 250 | 251 | // SAFETY: The pointer was checked if it's NULL 252 | Ok(unsafe { ptr::read(ptr) }) 253 | } 254 | 255 | /// Acquires the underlying pointer as const pointer. 256 | #[allow(clippy::wrong_self_convention)] // false positive lint 257 | #[inline] 258 | #[must_use] 259 | pub const fn as_raw(&self) -> *const c_void { 260 | self.raw 261 | } 262 | 263 | /// Acquires the underlying pointer. 264 | // false positive lint 265 | #[allow(clippy::wrong_self_convention)] 266 | #[inline] 267 | pub fn as_mut_raw(&mut self) -> *mut c_void { 268 | self.raw 269 | } 270 | 271 | /// Casts to a pointer of another type. 272 | #[inline] 273 | pub fn cast(self) -> *mut U { 274 | self.raw as *mut U 275 | } 276 | 277 | /// Return the size of the type it holds. 278 | #[inline] 279 | #[must_use] 280 | pub fn size(&self) -> usize { 281 | unsafe { (*evil_janet::janet_abstract_head(self.raw)).size } 282 | } 283 | 284 | /// Return the struct that holds the type name and all possible polymorphic function 285 | /// pointer that a Abstract type can have in Janet. 286 | #[inline] 287 | #[must_use] 288 | pub fn type_info(&self) -> JanetAbstractType { 289 | unsafe { *(*evil_janet::janet_abstract_head(self.raw)).type_ } 290 | } 291 | 292 | #[inline] 293 | fn raw_type_info(&self) -> *const JanetAbstractType { 294 | unsafe { (*evil_janet::janet_abstract_head(self.raw)).type_ } 295 | } 296 | } 297 | 298 | impl fmt::Debug for JanetAbstract { 299 | #[inline] 300 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 301 | f.debug_struct("JanetAbstract") 302 | .field("mem_size", &self.size()) 303 | .finish() 304 | } 305 | } 306 | 307 | impl PartialOrd for JanetAbstract { 308 | #[inline] 309 | fn partial_cmp(&self, other: &Self) -> Option { 310 | Some(self.cmp(other)) 311 | } 312 | } 313 | 314 | impl Ord for JanetAbstract { 315 | #[inline] 316 | fn cmp(&self, other: &Self) -> Ordering { 317 | if self.raw == other.raw { 318 | return Ordering::Equal; 319 | } 320 | let self_ty = self.raw_type_info(); 321 | let other_ty = self.raw_type_info(); 322 | 323 | if self_ty != other_ty && self_ty > other_ty { 324 | return Ordering::Greater; 325 | } 326 | 327 | let self_ty = self.type_info(); 328 | 329 | if let Some(f) = self_ty.compare { 330 | let res = unsafe { f(self.raw, other.raw) }; 331 | match res { 332 | -1 => Ordering::Less, 333 | 0 => Ordering::Equal, 334 | 1 => Ordering::Greater, 335 | _ => unreachable!(), 336 | } 337 | } else if self.raw > other.raw { 338 | Ordering::Greater 339 | } else { 340 | Ordering::Less 341 | } 342 | } 343 | } 344 | 345 | /// The trait that encodes the information required to instantiate the implementer as 346 | /// [`JanetAbstract`]. 347 | /// 348 | /// # Safety 349 | /// Implementing this trait is not trivial if the type implements [`Drop`] and can cause 350 | /// undefined behavior if not implemented carefully. 351 | /// 352 | /// Usually that can be solved by defining [`Get`](IsJanetAbstract::Get) as a 353 | /// [`ManuallyDrop`]. But in that case, you also become responsible to define 354 | /// [`JanetAbstractType::gc`] function to properly handle the dropping of the type for the 355 | /// Janet-side. 356 | pub unsafe trait IsJanetAbstract { 357 | /// The type that you get when you call [`JanetAbstract::get`] family of functions. 358 | /// 359 | /// This is usually set to `Self` when the type does not implement [`Drop`], or 360 | /// `ManuallyDrop` if the type implements [`Drop`]. 361 | type Get: IsJanetAbstract; 362 | 363 | /// The size of the type that is being transformed as [`JanetAbstract`]. 364 | /// 365 | /// Usually `mem::size_of()` 366 | const SIZE: usize; 367 | 368 | /// Returns the table of the name of the `Self` and all possible polymorphic function 369 | /// pointer that a Abstract type can have in Janet. 370 | fn type_info() -> &'static JanetAbstractType; 371 | } 372 | 373 | /// Register the [`JanetAbstractType`] of a type `T` that implements [`IsJanetAbstract`]. 374 | /// 375 | /// Registering the type is required to be able to marshal the type. 376 | pub fn register() { 377 | let at = T::type_info(); 378 | unsafe { 379 | let syn = evil_janet::janet_wrap_symbol(evil_janet::janet_csymbol(at.name)); 380 | 381 | // If `abs_type_ptr` is NULL, the type is not registered, so we then register it 382 | let abs_type_ptr = evil_janet::janet_get_abstract_type(syn); 383 | if abs_type_ptr.is_null() { 384 | evil_janet::janet_register_abstract_type(at); 385 | } 386 | } 387 | } 388 | 389 | unsafe impl IsJanetAbstract for i64 { 390 | type Get = i64; 391 | 392 | const SIZE: usize = core::mem::size_of::(); 393 | 394 | #[inline] 395 | fn type_info() -> &'static JanetAbstractType { 396 | unsafe { &evil_janet::janet_s64_type } 397 | } 398 | } 399 | 400 | unsafe impl IsJanetAbstract for u64 { 401 | type Get = u64; 402 | 403 | const SIZE: usize = core::mem::size_of::(); 404 | 405 | #[inline] 406 | fn type_info() -> &'static JanetAbstractType { 407 | unsafe { &evil_janet::janet_u64_type } 408 | } 409 | } 410 | 411 | unsafe impl IsJanetAbstract for ManuallyDrop 412 | where 413 | A: IsJanetAbstract, 414 | { 415 | type Get = ManuallyDrop; 416 | 417 | const SIZE: usize = A::SIZE; 418 | 419 | #[inline] 420 | fn type_info() -> &'static JanetAbstractType { 421 | A::type_info() 422 | } 423 | } 424 | 425 | #[cfg(all(test, any(feature = "amalgation", feature = "link-system")))] 426 | mod tests { 427 | use super::*; 428 | 429 | #[test] 430 | fn creation_and_getting_value() -> Result<(), crate::client::Error> { 431 | let _client = crate::client::JanetClient::init()?; 432 | 433 | let test = JanetAbstract::new(10u64); 434 | let test2 = JanetAbstract::new(12i64); 435 | 436 | let val = test.get::(); 437 | let val2 = test2.get::(); 438 | let val3 = test.get::(); 439 | 440 | assert_eq!(Ok(&10), val); 441 | assert_eq!(Ok(&12), val2); 442 | assert_eq!(Err(AbstractError::MismatchedAbstractType), val3); 443 | 444 | Ok(()) 445 | } 446 | 447 | #[test] 448 | fn get_mut() -> Result<(), crate::client::Error> { 449 | let _client = crate::client::JanetClient::init()?; 450 | 451 | let mut test = JanetAbstract::new(10u64); 452 | let mut test2 = JanetAbstract::new(12i64); 453 | 454 | let val = test.get_mut::(); 455 | let val2 = test2.get_mut::(); 456 | 457 | assert_eq!(Ok(&mut 10), val); 458 | assert_eq!(Ok(&mut 12), val2); 459 | 460 | if let Ok(val) = val { 461 | *val = 1000; 462 | assert_eq!(&mut 1000, val); 463 | } 464 | 465 | if let Ok(val2) = val2 { 466 | *val2 = 2000; 467 | assert_eq!(&mut 2000, val2); 468 | } 469 | Ok(()) 470 | } 471 | 472 | #[test] 473 | fn size() -> Result<(), crate::client::Error> { 474 | let _client = crate::client::JanetClient::init()?; 475 | 476 | let test = JanetAbstract::new(10u64); 477 | let test2 = JanetAbstract::new(12i64); 478 | 479 | assert_eq!(u64::SIZE, test.size()); 480 | assert_eq!(u64::SIZE, test2.size()); 481 | 482 | Ok(()) 483 | } 484 | 485 | #[derive(Debug, PartialEq)] 486 | struct TestDrop(bool); 487 | static mut TEST_DROP: JanetAbstractType = JanetAbstractType { 488 | name: c"TestDrop".as_ptr().cast::(), 489 | gc: None, 490 | gcmark: None, 491 | get: None, 492 | put: None, 493 | marshal: None, 494 | unmarshal: None, 495 | tostring: None, 496 | compare: None, 497 | hash: None, 498 | next: None, 499 | call: None, 500 | length: None, 501 | bytes: None, 502 | }; 503 | 504 | unsafe impl IsJanetAbstract for TestDrop { 505 | type Get = ManuallyDrop; 506 | 507 | const SIZE: usize = core::mem::size_of::(); 508 | 509 | #[inline] 510 | fn type_info() -> &'static JanetAbstractType { 511 | unsafe { &*ptr::addr_of!(TEST_DROP) } 512 | } 513 | } 514 | 515 | impl Drop for TestDrop { 516 | fn drop(&mut self) { 517 | self.0 = false; 518 | panic!("Dropping TestNonCopy"); 519 | } 520 | } 521 | 522 | #[test] 523 | fn non_copy() -> Result<(), crate::client::Error> { 524 | let _client = crate::client::JanetClient::init()?; 525 | 526 | let test = JanetAbstract::new(TestDrop(true)); 527 | let val = test.get::(); 528 | assert_eq!(Ok(&ManuallyDrop::new(TestDrop(true))), val); 529 | 530 | Ok(()) 531 | } 532 | 533 | #[derive(Debug, PartialEq)] 534 | struct TestDrop2(bool); 535 | static mut TEST_DROP2: JanetAbstractType = JanetAbstractType { 536 | name: c"TestDrop2".as_ptr().cast::(), 537 | gc: None, 538 | gcmark: None, 539 | get: None, 540 | put: None, 541 | marshal: None, 542 | unmarshal: None, 543 | tostring: None, 544 | compare: None, 545 | hash: None, 546 | next: None, 547 | call: None, 548 | length: None, 549 | bytes: None, 550 | }; 551 | 552 | unsafe impl IsJanetAbstract for TestDrop2 { 553 | type Get = Self; 554 | 555 | const SIZE: usize = core::mem::size_of::(); 556 | 557 | #[inline] 558 | fn type_info() -> &'static JanetAbstractType { 559 | unsafe { &*ptr::addr_of!(TEST_DROP2) } 560 | } 561 | } 562 | 563 | impl Drop for TestDrop2 { 564 | fn drop(&mut self) { 565 | self.0 = false; 566 | panic!("Dropping TestNonCopy"); 567 | } 568 | } 569 | 570 | #[test] 571 | #[should_panic] 572 | fn non_copy_ill_implemented() { 573 | let _client = crate::client::JanetClient::init().unwrap(); 574 | 575 | let test = JanetAbstract::new(TestDrop2(true)); 576 | let val = test.get::(); 577 | assert_eq!(Ok(&TestDrop2(true)), val); 578 | } 579 | } 580 | -------------------------------------------------------------------------------- /src/types/fiber.rs: -------------------------------------------------------------------------------- 1 | //! Janet fibers (soft threads) type. 2 | use core::{iter::FusedIterator, marker::PhantomData}; 3 | 4 | use evil_janet::JanetFiber as CJanetFiber; 5 | use janetrs_macros::cjvg; 6 | 7 | use super::{Janet, JanetFunction, JanetSignal, JanetTable}; 8 | 9 | /// A lightweight green thread in Janet. It does not correspond to operating system 10 | /// threads. 11 | /// 12 | /// Fibers allow a process to stop and resume execution later, essentially enabling 13 | /// multiple returns from a function. 14 | /// 15 | /// Different from traditional coroutines, Janet's fibers implement a signaling mechanism, 16 | /// which is used to differentiate different kinds of returns. When a fiber yields or 17 | /// throws an error, control is returned to the calling fiber. The parent fiber must then 18 | /// check what kind of state the fiber is in to differentiate errors from return values 19 | /// from user-defined signals. 20 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] 21 | #[repr(transparent)] 22 | pub struct JanetFiber<'data> { 23 | pub(crate) raw: *mut CJanetFiber, 24 | phantom: PhantomData<&'data ()>, 25 | } 26 | 27 | impl<'data> JanetFiber<'data> { 28 | /// Create a new [`JanetFiber`] from a [`JanetFunction`] and it's arguments. 29 | /// 30 | /// In case any passed argument is invalid, returns `None`. 31 | pub fn new(capacity: i32, f: &mut JanetFunction, args: impl AsRef<[Janet]>) -> Option { 32 | let args = args.as_ref(); 33 | let raw = unsafe { 34 | evil_janet::janet_fiber( 35 | f.raw, 36 | capacity, 37 | args.len() as i32, 38 | args.as_ptr() as *const _, 39 | ) 40 | }; 41 | if raw.is_null() { 42 | None 43 | } else { 44 | Some(Self { 45 | raw, 46 | phantom: PhantomData, 47 | }) 48 | } 49 | } 50 | 51 | /// Create a new [`JanetFiber`] from a [`JanetFunction`] and it's arguments with a 52 | /// given environments. 53 | /// 54 | /// In case any passed argument is invalid, returns `None`. 55 | #[cfg_attr(feature = "inline-more", inline)] 56 | pub fn with_env( 57 | env: JanetTable, capacity: i32, f: &mut JanetFunction, args: impl AsRef<[Janet]>, 58 | ) -> Option { 59 | Self::new(capacity, f, args).inspect(|f| { 60 | unsafe { (*f.raw).env = env.raw }; 61 | }) 62 | } 63 | 64 | /// Return the current [`JanetFiber`] if it exists. 65 | #[inline] 66 | #[must_use] 67 | pub fn current() -> Option { 68 | let f = unsafe { evil_janet::janet_current_fiber() }; 69 | if f.is_null() { 70 | None 71 | } else { 72 | Some(Self { 73 | raw: f, 74 | phantom: PhantomData, 75 | }) 76 | } 77 | } 78 | 79 | /// Return the root [`JanetFiber`] if it exists. 80 | /// 81 | /// The root fiber is the oldest ancestor that does not have a parent. 82 | #[inline] 83 | #[must_use] 84 | pub fn root() -> Option { 85 | let f = unsafe { evil_janet::janet_root_fiber() }; 86 | if f.is_null() { 87 | None 88 | } else { 89 | Some(Self { 90 | raw: f, 91 | phantom: PhantomData, 92 | }) 93 | } 94 | } 95 | 96 | /// Create a new [`JanetFiber`] with a `raw` pointer. 97 | /// 98 | /// # Safety 99 | /// This function do not check if the given `raw` is `NULL` or not. Use at your 100 | /// own risk. 101 | #[inline] 102 | pub const unsafe fn from_raw(raw: *mut CJanetFiber) -> Self { 103 | Self { 104 | raw, 105 | phantom: PhantomData, 106 | } 107 | } 108 | 109 | /// Returns the fiber status. 110 | #[inline] 111 | #[must_use] 112 | pub fn status(&self) -> FiberStatus { 113 | let raw_status = unsafe { evil_janet::janet_fiber_status(self.raw) }; 114 | FiberStatus::from(raw_status) 115 | } 116 | 117 | /// Returns if the fiber can be resumed. 118 | #[inline] 119 | pub fn can_resume(&self) -> bool { 120 | let status = self.status(); 121 | !matches!( 122 | status, 123 | FiberStatus::Dead 124 | | FiberStatus::Error 125 | | FiberStatus::User0 126 | | FiberStatus::User1 127 | | FiberStatus::User2 128 | | FiberStatus::User3 129 | | FiberStatus::User4 130 | ) 131 | } 132 | 133 | /// Returns if the fiber can be resumed. 134 | #[cjvg("1.28.0")] 135 | #[inline] 136 | pub fn can_resume_native(&self) -> bool { 137 | let resume = unsafe { evil_janet::janet_fiber_can_resume(self.raw) }; 138 | resume != 0 139 | } 140 | 141 | /// Creates a iterator that can execute the fiber function until it's done. 142 | /// 143 | /// # Examples 144 | /// ``` 145 | /// use janetrs::{JanetFiber, JanetFunction, client::JanetClient}; 146 | /// # fn main() -> Result<(), Box> { 147 | /// let _client = JanetClient::init_with_default_env()?; 148 | /// 149 | /// let f = _client.run( 150 | /// "(fn [] 151 | /// (yield 1) 152 | /// (yield 2) 153 | /// (yield 3) 154 | /// (yield 4) 155 | /// 5)", 156 | /// )?; 157 | /// let mut f_concrete: JanetFunction = f.try_unwrap()?; 158 | /// 159 | /// let mut fiber = JanetFiber::new(64, &mut f_concrete, &[]).unwrap(); 160 | /// fiber.exec().for_each(|j| println!("{}", j)); 161 | /// # Ok(()) } 162 | /// ``` 163 | #[inline] 164 | pub fn exec<'a>(&'a mut self) -> Exec<'a, 'data> { 165 | Exec { 166 | fiber: self, 167 | input: Janet::nil(), 168 | } 169 | } 170 | 171 | /// Creates a iterator that can execute the fiber function until it's done, modifying 172 | /// the input to `input`. 173 | /// 174 | /// A `input` of value of Janet nil is the same as calling the [`exec`] method. 175 | /// 176 | /// # Examples 177 | /// ``` 178 | /// use janetrs::{Janet, JanetFiber, JanetFunction, client::JanetClient}; 179 | /// # fn main() -> Result<(), Box> { 180 | /// let _client = JanetClient::init_with_default_env()?; 181 | /// 182 | /// let f = _client.run( 183 | /// "(fn [x] 184 | /// (yield (+ x 1)) 185 | /// (yield (+ x 2)) 186 | /// (yield (* x 2)) 187 | /// (yield (* x 3)) 188 | /// x)", 189 | /// )?; 190 | /// let mut f_concrete: JanetFunction = f.try_unwrap()?; 191 | /// 192 | /// let mut fiber = JanetFiber::new(64, &mut f_concrete, &[10i64.into()]).unwrap(); 193 | /// fiber 194 | /// .exec_input(Janet::integer(12)) 195 | /// .for_each(|j| println!("{}", j)); 196 | /// # Ok(()) } 197 | /// ``` 198 | /// 199 | /// [`exec`]: #method.exec 200 | #[inline] 201 | pub fn exec_input<'a>(&'a mut self, input: Janet) -> Exec<'a, 'data> { 202 | Exec { fiber: self, input } 203 | } 204 | 205 | /// Creates a iterator that can execute the fiber function until it's done, modifying 206 | /// the input with the given function. 207 | /// 208 | /// A `F` that returns the value of Janet nil is the same as calling the [`exec`] 209 | /// method. 210 | /// 211 | /// # Examples 212 | /// ``` 213 | /// use janetrs::{Janet, JanetFiber, JanetFunction, client::JanetClient}; 214 | /// # fn main() -> Result<(), Box> { 215 | /// let _client = JanetClient::init_with_default_env()?; 216 | /// 217 | /// let f = _client.run( 218 | /// "(fn [x] 219 | /// (yield (+ x 1)) 220 | /// (yield (+ x 2)) 221 | /// (yield (* x 2)) 222 | /// (yield (* x 3)) 223 | /// x)", 224 | /// )?; 225 | /// let mut f_concrete: JanetFunction = f.try_unwrap()?; 226 | /// 227 | /// let mut fiber = JanetFiber::new(64, &mut f_concrete, &[10i64.into()]).unwrap(); 228 | /// fiber 229 | /// .exec_with(|| Janet::integer(3)) 230 | /// .for_each(|j| println!("{}", j)); 231 | /// # Ok(()) } 232 | /// ``` 233 | /// [`exec`]: #method.exec 234 | #[inline] 235 | pub fn exec_with<'a, F>(&'a mut self, f: F) -> Exec<'a, 'data> 236 | where 237 | F: FnOnce() -> Janet, 238 | { 239 | Exec { 240 | fiber: self, 241 | input: f(), 242 | } 243 | } 244 | 245 | /// Return a raw pointer to the fiber raw structure. 246 | /// 247 | /// The caller must ensure that the fiber outlives the pointer this function returns, 248 | /// or else it will end up pointing to garbage. 249 | /// 250 | /// If you need to mutate the contents of the slice, use [`as_mut_ptr`]. 251 | /// 252 | /// [`as_mut_ptr`]: #method.as_mut_raw 253 | #[inline] 254 | #[must_use] 255 | pub const fn as_raw(&self) -> *const CJanetFiber { 256 | self.raw 257 | } 258 | 259 | /// Return a raw mutable pointer to the fiber raw structure. 260 | /// 261 | /// The caller must ensure that the fiber outlives the pointer this function returns, 262 | /// or else it will end up pointing to garbage. 263 | #[inline] 264 | pub fn as_mut_raw(&mut self) -> *mut CJanetFiber { 265 | self.raw 266 | } 267 | } 268 | 269 | impl JanetFiber<'_> { 270 | #[inline] 271 | pub(crate) fn display_stacktrace(&mut self, err: Janet) { 272 | unsafe { evil_janet::janet_stacktrace(self.raw, err.inner) } 273 | } 274 | } 275 | 276 | /// An iterator that executes the function related to the fiber until it completes. 277 | /// 278 | /// **Executing this iterator may trigger a GC collection** 279 | #[derive(Debug)] 280 | pub struct Exec<'a, 'data> { 281 | fiber: &'a mut JanetFiber<'data>, 282 | input: Janet, 283 | } 284 | 285 | impl Iterator for Exec<'_, '_> { 286 | type Item = Janet; 287 | 288 | #[inline] 289 | fn next(&mut self) -> Option { 290 | let mut out = Janet::nil(); 291 | let sig = 292 | unsafe { evil_janet::janet_continue(self.fiber.raw, self.input.inner, &mut out.inner) }; 293 | let sig = JanetSignal::from(sig); 294 | if matches!( 295 | sig, 296 | JanetSignal::Ok | JanetSignal::Yield | JanetSignal::User9 297 | ) { 298 | Some(out) 299 | } else { 300 | self.fiber.display_stacktrace(out); 301 | None 302 | } 303 | } 304 | } 305 | 306 | impl FusedIterator for Exec<'_, '_> {} 307 | 308 | /// This type represents a the status of a [`JanetFiber`]. 309 | /// 310 | /// It mostly corresponds to signals. 311 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] 312 | #[repr(u32)] 313 | pub enum FiberStatus { 314 | Dead = evil_janet::JanetFiberStatus_JANET_STATUS_DEAD, 315 | Error = evil_janet::JanetFiberStatus_JANET_STATUS_ERROR, 316 | Debug = evil_janet::JanetFiberStatus_JANET_STATUS_DEBUG, 317 | Pending = evil_janet::JanetFiberStatus_JANET_STATUS_PENDING, 318 | User0 = evil_janet::JanetFiberStatus_JANET_STATUS_USER0, 319 | User1 = evil_janet::JanetFiberStatus_JANET_STATUS_USER1, 320 | User2 = evil_janet::JanetFiberStatus_JANET_STATUS_USER2, 321 | User3 = evil_janet::JanetFiberStatus_JANET_STATUS_USER3, 322 | User4 = evil_janet::JanetFiberStatus_JANET_STATUS_USER4, 323 | User5 = evil_janet::JanetFiberStatus_JANET_STATUS_USER5, 324 | User6 = evil_janet::JanetFiberStatus_JANET_STATUS_USER6, 325 | User7 = evil_janet::JanetFiberStatus_JANET_STATUS_USER7, 326 | User8 = evil_janet::JanetFiberStatus_JANET_STATUS_USER8, 327 | User9 = evil_janet::JanetFiberStatus_JANET_STATUS_USER9, 328 | New = evil_janet::JanetFiberStatus_JANET_STATUS_NEW, 329 | Alive = evil_janet::JanetFiberStatus_JANET_STATUS_ALIVE, 330 | } 331 | 332 | #[allow(non_upper_case_globals)] 333 | impl From for FiberStatus { 334 | #[inline] 335 | fn from(val: u32) -> Self { 336 | match val { 337 | evil_janet::JanetFiberStatus_JANET_STATUS_DEAD => Self::Dead, 338 | evil_janet::JanetFiberStatus_JANET_STATUS_ERROR => Self::Error, 339 | evil_janet::JanetFiberStatus_JANET_STATUS_DEBUG => Self::Debug, 340 | evil_janet::JanetFiberStatus_JANET_STATUS_PENDING => Self::Pending, 341 | evil_janet::JanetFiberStatus_JANET_STATUS_USER0 => Self::User0, 342 | evil_janet::JanetFiberStatus_JANET_STATUS_USER1 => Self::User1, 343 | evil_janet::JanetFiberStatus_JANET_STATUS_USER2 => Self::User2, 344 | evil_janet::JanetFiberStatus_JANET_STATUS_USER3 => Self::User3, 345 | evil_janet::JanetFiberStatus_JANET_STATUS_USER4 => Self::User4, 346 | evil_janet::JanetFiberStatus_JANET_STATUS_USER5 => Self::User5, 347 | evil_janet::JanetFiberStatus_JANET_STATUS_USER6 => Self::User6, 348 | evil_janet::JanetFiberStatus_JANET_STATUS_USER7 => Self::User7, 349 | evil_janet::JanetFiberStatus_JANET_STATUS_USER8 => Self::User8, 350 | evil_janet::JanetFiberStatus_JANET_STATUS_USER9 => Self::User9, 351 | evil_janet::JanetFiberStatus_JANET_STATUS_NEW => Self::New, 352 | evil_janet::JanetFiberStatus_JANET_STATUS_ALIVE => Self::Alive, 353 | _ => unreachable!(), 354 | } 355 | } 356 | } 357 | 358 | impl From for u32 { 359 | #[inline] 360 | fn from(val: FiberStatus) -> Self { 361 | val as u32 362 | } 363 | } 364 | 365 | 366 | #[cfg(all(test, any(feature = "amalgation", feature = "link-system")))] 367 | mod tests { 368 | use super::*; 369 | use crate::client::JanetClient; 370 | 371 | #[test] 372 | fn exec_iterator() -> Result<(), crate::client::Error> { 373 | let client = JanetClient::init_with_default_env()?; 374 | 375 | let fun = client 376 | .run( 377 | "(fn [x] 378 | (yield x) 379 | (yield (+ x 1)) 380 | (yield (+ x 2)) 381 | (yield (* x 2)) 382 | x)", 383 | ) 384 | .unwrap(); 385 | let mut fun = JanetFunction::try_from(fun).unwrap(); 386 | let mut fiber = JanetFiber::new(64, &mut fun, [Janet::number(10.0)]).unwrap(); 387 | let mut iter = fiber.exec(); 388 | assert_eq!(Some(Janet::number(10.0)), iter.next()); 389 | assert_eq!(Some(Janet::number(11.0)), iter.next()); 390 | assert_eq!(Some(Janet::number(12.0)), iter.next()); 391 | assert_eq!(Some(Janet::number(20.0)), iter.next()); 392 | assert_eq!(Some(Janet::number(10.0)), iter.next()); 393 | assert_eq!(None, iter.next()); 394 | assert_eq!(None, iter.next()); 395 | Ok(()) 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /src/types/function.rs: -------------------------------------------------------------------------------- 1 | //! Janet function types. 2 | use core::{ 3 | error, 4 | fmt::{self, Display}, 5 | marker::PhantomData, 6 | ptr, 7 | }; 8 | 9 | #[cfg(not(feature = "std"))] 10 | use core::fmt::Write; 11 | 12 | #[cfg(feature = "std")] 13 | use std::io::{self, Write}; 14 | 15 | use evil_janet::{JanetFunction as CJanetFunction, janet_pcall}; 16 | 17 | use crate::{Janet, JanetFiber, JanetSignal, cjvg}; 18 | 19 | #[cjvg("1.12.2")] 20 | pub use trystate::JanetTryState; 21 | 22 | /// C function pointer that is accepted by Janet as a type. 23 | pub type JanetCFunction = evil_janet::JanetCFunction; 24 | 25 | /// raw C Function 26 | pub type JanetRawCFunction = 27 | unsafe extern "C-unwind" fn(i32, *mut evil_janet::Janet) -> evil_janet::Janet; 28 | 29 | /// Error type that happens when calling a [`JanetFunction`] on the Rust side. 30 | #[derive(Debug)] 31 | pub struct CallError<'data> { 32 | kind: CallErrorKind, 33 | value: Janet, 34 | signal: JanetSignal, 35 | fiber: Option>, 36 | } 37 | 38 | /// Kinds of errors of [`CallError`]. 39 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] 40 | #[non_exhaustive] 41 | pub enum CallErrorKind { 42 | /// Wrong number of parameters passed. 43 | Arity, 44 | /// Fail to run a [`JanetFunction`]. 45 | Run, 46 | /// [`JanetFunction`] yielded. That is not a problem per see, but some methods may 47 | /// expect a [`JanetFunction`] to return instead of yielding a value. 48 | Yield, 49 | } 50 | 51 | impl<'data> CallError<'data> { 52 | #[inline] 53 | const fn new( 54 | kind: CallErrorKind, value: Janet, signal: JanetSignal, fiber: Option>, 55 | ) -> Self { 56 | Self { 57 | kind, 58 | value, 59 | signal, 60 | fiber, 61 | } 62 | } 63 | 64 | /// Returns the kind of the error. 65 | #[inline] 66 | #[must_use = "this returns the result of the operation, without modifying the original"] 67 | pub const fn kind(&self) -> CallErrorKind { 68 | self.kind 69 | } 70 | 71 | /// Returns the error value. 72 | #[inline] 73 | #[must_use = "this returns the result of the operation, without modifying the original"] 74 | pub const fn value(&self) -> Janet { 75 | self.value 76 | } 77 | 78 | /// Returns the [`JanetSignal`] that caused the error. 79 | #[inline] 80 | #[must_use = "this returns the result of the operation, without modifying the original"] 81 | pub const fn signal(&self) -> JanetSignal { 82 | self.signal 83 | } 84 | 85 | /// Get a reference to the fiber that the error happened if it exists. 86 | #[inline] 87 | #[must_use] 88 | pub const fn fiber(&self) -> Option<&JanetFiber> { 89 | self.fiber.as_ref() 90 | } 91 | 92 | /// Get a exclusive reference to the fiber that the error happened if it exists. 93 | #[inline] 94 | pub fn fiber_mut(&mut self) -> Option<&mut JanetFiber<'data>> { 95 | self.fiber.as_mut() 96 | } 97 | 98 | /// Consume the error and return the fiber that the error happened if it exists. 99 | #[inline] 100 | #[must_use = "this consumes self, making it impossible to use afterwards"] 101 | pub const fn take_fiber(self) -> Option> { 102 | self.fiber 103 | } 104 | 105 | /// Display the stacktrace in the given `output` 106 | #[cfg(feature = "std")] 107 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 108 | #[cfg_attr(feature = "inline-more", inline)] 109 | pub fn stacktrace(&mut self, output: &mut W) -> io::Result<()> { 110 | if let CallErrorKind::Run = self.kind { 111 | if let Some(ref mut fiber) = self.fiber { 112 | fiber.display_stacktrace(self.value); 113 | } else { 114 | writeln!(output, "There is no stacktrace.")?; 115 | } 116 | } else { 117 | writeln!(output, "There is no stacktrace.")?; 118 | } 119 | 120 | Ok(()) 121 | } 122 | 123 | /// Display the stacktrace in the given `output` 124 | #[cfg(not(feature = "std"))] 125 | #[cfg_attr(feature = "inline-more", inline)] 126 | pub fn stacktrace(&mut self, output: &mut W) -> fmt::Result { 127 | if let CallErrorKind::Run = self.kind { 128 | if let Some(ref mut fiber) = self.fiber { 129 | fiber.display_stacktrace(self.value); 130 | } else { 131 | output.write_str("There is no stacktrace.\n")?; 132 | } 133 | } else { 134 | output.write_str("There is no stacktrace.\n")?; 135 | } 136 | 137 | Ok(()) 138 | } 139 | } 140 | 141 | impl Display for CallError<'_> { 142 | #[inline] 143 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 144 | match self.kind { 145 | CallErrorKind::Arity => Display::fmt( 146 | &format_args!("{}: Wrong number of arguments", self.value), 147 | f, 148 | ), 149 | CallErrorKind::Yield => f.pad( 150 | "This function can yield more than one result. In those cases it's recommended to \ 151 | create a JanetFiber to execute all its steps", 152 | ), 153 | CallErrorKind::Run { .. } => f.pad("Failed to execute the Janet function."), 154 | } 155 | } 156 | } 157 | 158 | impl error::Error for CallError<'_> {} 159 | 160 | /// A representation of a Janet function defined at the Janet side. 161 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] 162 | #[repr(transparent)] 163 | pub struct JanetFunction<'data> { 164 | pub(crate) raw: *mut CJanetFunction, 165 | phantom: PhantomData<&'data ()>, 166 | } 167 | 168 | impl<'data> JanetFunction<'data> { 169 | /// Create a new [`JanetFunction`] with a `raw` pointer. 170 | /// 171 | /// # Safety 172 | /// This function do not check if the given `raw` is `NULL` or not. Use at your 173 | /// own risk. 174 | #[inline] 175 | pub const unsafe fn from_raw(raw: *mut CJanetFunction) -> Self { 176 | Self { 177 | raw, 178 | phantom: PhantomData, 179 | } 180 | } 181 | 182 | /// Execute the [`JanetFunction`] with the given arguments. 183 | /// 184 | /// **This function may trigger a GC collection.** 185 | /// 186 | /// If the executions was successful returns the output, otherwise return the 187 | /// [`CallError`] with information returned by the call. 188 | #[cfg_attr(feature = "inline-more", inline)] 189 | pub fn call(&mut self, args: impl AsRef<[Janet]>) -> Result> { 190 | let args = args.as_ref(); 191 | let mut out = Janet::nil(); 192 | let fiber = ptr::null_mut(); 193 | let raw_sig = unsafe { 194 | janet_pcall( 195 | self.raw, 196 | args.len() as i32, 197 | args.as_ptr() as *const _, 198 | &mut out.inner, 199 | fiber, 200 | ) 201 | }; 202 | 203 | let sig = raw_sig.into(); 204 | 205 | match sig { 206 | JanetSignal::Ok | JanetSignal::User9 => Ok(out), 207 | JanetSignal::Yield => Err(CallError::new(CallErrorKind::Yield, out, sig, None)), 208 | JanetSignal::Error if out == Janet::from("arity mismatch") => { 209 | Err(CallError::new(CallErrorKind::Arity, out, sig, None)) 210 | }, 211 | _ => { 212 | // SAFETY: We checked if the pointer are null 213 | let fiber = unsafe { 214 | if fiber.is_null() || (*fiber).is_null() { 215 | None 216 | } else { 217 | Some(JanetFiber::from_raw(*fiber)) 218 | } 219 | }; 220 | Err(CallError::new(CallErrorKind::Run, out, sig, fiber)) 221 | }, 222 | } 223 | } 224 | 225 | /// Execute the [`JanetFunction`] with the given arguments wising the given `fiber`. 226 | /// 227 | /// **This function may trigger the a GC collection.** 228 | /// 229 | /// If the executions was successful returns the output, otherwise return the 230 | /// [`CallError`] with information returned by the call. 231 | #[cfg_attr(feature = "inline-more", inline)] 232 | pub fn call_with_fiber<'fiber>( 233 | &mut self, mut fiber: JanetFiber<'fiber>, args: impl AsRef<[Janet]>, 234 | ) -> Result> { 235 | let args = args.as_ref(); 236 | let mut out = Janet::nil(); 237 | let raw_sig = unsafe { 238 | janet_pcall( 239 | self.raw, 240 | args.len() as i32, 241 | args.as_ptr() as *const _, 242 | &mut out.inner, 243 | &mut fiber.raw, 244 | ) 245 | }; 246 | 247 | let sig = raw_sig.into(); 248 | 249 | match sig { 250 | JanetSignal::Ok | JanetSignal::User9 => Ok(out), 251 | JanetSignal::Yield => Err(CallError::new(CallErrorKind::Yield, out, sig, None)), 252 | JanetSignal::Error if out == Janet::from("arity mismatch") => { 253 | Err(CallError::new(CallErrorKind::Arity, out, sig, None)) 254 | }, 255 | _ => { 256 | let fiber = if fiber.raw.is_null() { 257 | None 258 | } else { 259 | Some(unsafe { JanetFiber::from_raw(fiber.raw) }) 260 | }; 261 | Err(CallError::new(CallErrorKind::Run, out, sig, fiber)) 262 | }, 263 | } 264 | } 265 | 266 | /// Execute the [`JanetFunction`] with the given arguments. 267 | /// 268 | /// **This function can not trigger GC collection.** 269 | /// 270 | /// # Janet Panics 271 | /// Panics if anything goes wrong trying to call the function. 272 | #[cfg_attr(feature = "inline-more", inline)] 273 | pub fn call_or_panic(&mut self, args: impl AsRef<[Janet]>) -> Janet { 274 | let args = args.as_ref(); 275 | 276 | unsafe { evil_janet::janet_call(self.raw, args.len() as i32, args.as_ptr() as *const _) } 277 | .into() 278 | } 279 | 280 | /// Return a raw pointer to the function raw structure. 281 | /// 282 | /// The caller must ensure that the function outlives the pointer this function 283 | /// returns, or else it will end up pointing to garbage. 284 | #[inline] 285 | #[must_use] 286 | pub const fn as_raw(&self) -> *const CJanetFunction { 287 | self.raw 288 | } 289 | 290 | /// Return a raw mutable pointer to the function raw structure. 291 | /// 292 | /// The caller must ensure that the function outlives the pointer this function 293 | /// returns, or else it will end up pointing to garbage. 294 | #[inline] 295 | pub fn as_mut_raw(&mut self) -> *mut CJanetFunction { 296 | self.raw 297 | } 298 | } 299 | 300 | impl fmt::Debug for JanetFunction<'_> { 301 | #[inline] 302 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 303 | f.pad("JanetFunction") 304 | } 305 | } 306 | 307 | #[cjvg("1.12.2")] 308 | mod trystate { 309 | use core::mem::MaybeUninit; 310 | 311 | use crate::{Janet, JanetSignal}; 312 | 313 | /// A structure that holds the old and new states of the Janet VM. 314 | /// 315 | /// This can be used to execute a [`JanetCFunction`] and capture its Janet panics. 316 | /// 317 | /// [`JanetCFunction`]: ./types/struct.JanetCFunction.html 318 | pub struct JanetTryState { 319 | inner: evil_janet::JanetTryState, 320 | } 321 | 322 | impl JanetTryState { 323 | /// Initializes the state. 324 | #[inline] 325 | #[must_use = "function is a constructor associated function"] 326 | pub fn init() -> Self { 327 | let mut state = { 328 | let mut state = MaybeUninit::uninit(); 329 | unsafe { 330 | // SAFETY: C-FFI 331 | evil_janet::janet_try_init(state.as_mut_ptr()); 332 | 333 | // SAFETY: The above function initializes the state, therefore it is initialized 334 | // now 335 | state.assume_init() 336 | } 337 | }; 338 | 339 | state.payload = Janet::nil().into(); 340 | 341 | Self { inner: state } 342 | } 343 | 344 | /// Check if the VM have a valid [`JanetFiber`](crate::JanetFiber). 345 | #[inline] 346 | #[must_use = "this returns the result of the operation, without modifying the original"] 347 | pub fn is_valid_to_run(&self) -> bool { 348 | !self.inner.vm_fiber.is_null() 349 | } 350 | 351 | /// Get the [`JanetSignal`] of the state without checking if the environment is 352 | /// set to catch Janet Panics. 353 | /// 354 | /// # Safety 355 | /// If this is called with the invalid environment to catch Janet Panics it will 356 | /// cause undefined behaviour. 357 | #[inline] 358 | pub unsafe fn signal_unchecked(&mut self) -> JanetSignal { 359 | let signal = evil_janet::_setjmp(self.inner.buf.as_mut_ptr()); 360 | 361 | JanetSignal::from(signal as u32) 362 | } 363 | 364 | /// Get the [`JanetSignal`] of the state if the environment is set to catch Janet 365 | /// Panics. 366 | #[inline] 367 | pub fn signal(&mut self) -> Option { 368 | if self.is_valid_to_run() { 369 | Some(unsafe { self.signal_unchecked() }) 370 | } else { 371 | None 372 | } 373 | } 374 | 375 | /// Get the output of the execution. 376 | #[inline] 377 | #[must_use = "this returns the result of the operation, without modifying the original"] 378 | pub fn payload(&self) -> Janet { 379 | self.inner.payload.into() 380 | } 381 | } 382 | 383 | impl Drop for JanetTryState { 384 | fn drop(&mut self) { 385 | unsafe { evil_janet::janet_restore(&mut self.inner) }; 386 | } 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /src/types/io.rs: -------------------------------------------------------------------------------- 1 | //! Module for I/O related types and functions 2 | 3 | use core::{fmt, mem}; 4 | #[cfg(feature = "std")] 5 | use std::io::{self, Read, Seek, SeekFrom, Write}; 6 | #[cfg(all(unix, feature = "std"))] 7 | use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; 8 | #[cfg(all(windows, feature = "std"))] 9 | use std::os::windows::io::{AsRawHandle, IntoRawHandle, RawHandle}; 10 | 11 | use libc::FILE; 12 | 13 | use crate::{IsJanetAbstract, Janet, JanetAbstract}; 14 | 15 | /// Janet internal representation of a file in the Janet C API. 16 | #[repr(transparent)] 17 | pub struct JanetFile { 18 | pub(crate) raw: evil_janet::JanetFile, 19 | } 20 | 21 | impl JanetFile { 22 | /// Open an anonymous temporary file that is removed on close. 23 | #[cfg(feature = "std")] 24 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 25 | pub fn temp() -> io::Result { 26 | let file = unsafe { libc::tmpfile() as *mut evil_janet::FILE }; 27 | 28 | if file.is_null() { 29 | return Err(io::Error::last_os_error()); 30 | } 31 | 32 | Ok(Self { 33 | raw: evil_janet::JanetFile { 34 | file, 35 | flags: (evil_janet::JANET_FILE_WRITE 36 | | evil_janet::JANET_FILE_READ 37 | | evil_janet::JANET_FILE_BINARY) as _, 38 | }, 39 | }) 40 | } 41 | 42 | /// Create a new [`JanetFile`] with a `raw` pointer. 43 | /// 44 | /// # Safety 45 | /// This function do not check if the given `raw` is `NULL` or not. Use at your 46 | /// own risk. 47 | #[inline] 48 | #[must_use = "function is a constructor associated function"] 49 | pub const fn from_raw(raw: evil_janet::JanetFile) -> Self { 50 | Self { raw } 51 | } 52 | 53 | /// Get the [`FILE`] pointer that the JanetFile holds. 54 | #[inline] 55 | pub fn as_file_ptr(&mut self) -> *mut FILE { 56 | self.raw.file as *mut _ 57 | } 58 | 59 | /// Return the flags of the JanetFile. 60 | #[inline] 61 | #[must_use = "this returns the result of the operation, without modifying the original"] 62 | pub const fn flags(&self) -> FileFlags { 63 | FileFlags(self.raw.flags) 64 | } 65 | 66 | /// Returns a exclusive reference to the flags of the JanetFile. 67 | #[inline] 68 | pub fn flags_mut(&mut self) -> &mut FileFlags { 69 | unsafe { &mut *(&mut self.raw.flags as *mut i32 as *mut FileFlags) } 70 | } 71 | 72 | #[cfg(feature = "std")] 73 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 74 | fn last_error(&self) -> Option { 75 | let errno = unsafe { libc::ferror(self.raw.file as _) }; 76 | 77 | if errno != 0 { 78 | return Some(io::Error::from_raw_os_error(errno)); 79 | } 80 | 81 | let err = io::Error::last_os_error(); 82 | match err.raw_os_error() { 83 | Some(errno) if errno != 0 => Some(err), 84 | _ => None, 85 | } 86 | } 87 | 88 | #[cfg(feature = "std")] 89 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 90 | fn position(&self) -> io::Result { 91 | let offset = unsafe { libc::ftell(self.raw.file as _) }; 92 | 93 | if offset < 0 { 94 | if let Some(err) = self.last_error() { 95 | return Err(err); 96 | } 97 | } 98 | 99 | Ok(offset as u64) 100 | } 101 | } 102 | 103 | unsafe impl IsJanetAbstract for JanetFile { 104 | type Get = Self; 105 | 106 | const SIZE: usize = mem::size_of::(); 107 | 108 | #[inline] 109 | fn type_info() -> &'static evil_janet::JanetAbstractType { 110 | unsafe { &evil_janet::janet_file_type } 111 | } 112 | } 113 | 114 | impl From for JanetAbstract { 115 | #[inline] 116 | fn from(value: JanetFile) -> Self { 117 | Self::new(value) 118 | } 119 | } 120 | 121 | impl From for Janet { 122 | #[inline] 123 | fn from(value: JanetFile) -> Self { 124 | Self::j_abstract(JanetAbstract::new(value)) 125 | } 126 | } 127 | 128 | impl fmt::Write for JanetFile { 129 | fn write_str(&mut self, s: &str) -> fmt::Result { 130 | if s.is_empty() { 131 | return Ok(()); 132 | } 133 | 134 | // SAFETY: We check for the errors after the call 135 | let _ = unsafe { 136 | libc::fwrite( 137 | s.as_ptr() as _, 138 | mem::size_of::(), 139 | s.len(), 140 | self.as_file_ptr(), 141 | ) 142 | }; 143 | 144 | let errno = unsafe { libc::ferror(self.as_file_ptr()) }; 145 | 146 | match errno { 147 | 0 => Ok(()), 148 | _ => Err(fmt::Error), 149 | } 150 | } 151 | } 152 | 153 | #[cfg(feature = "std")] 154 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 155 | impl Read for JanetFile { 156 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 157 | if buf.is_empty() { 158 | return Ok(0); 159 | } 160 | 161 | // SAFETY: We check for the errors after the call 162 | let read_bytes = unsafe { 163 | libc::fread( 164 | buf.as_mut_ptr() as _, 165 | mem::size_of::(), 166 | buf.len(), 167 | self.as_file_ptr(), 168 | ) 169 | }; 170 | 171 | match self.last_error() { 172 | Some(err) => Err(err), 173 | None => Ok(read_bytes), 174 | } 175 | } 176 | } 177 | 178 | #[cfg(feature = "std")] 179 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 180 | impl Write for JanetFile { 181 | fn write(&mut self, buf: &[u8]) -> io::Result { 182 | if buf.is_empty() { 183 | return Ok(0); 184 | } 185 | 186 | // SAFETY: We check for the errors after the call 187 | let written_bytes = unsafe { 188 | libc::fwrite( 189 | buf.as_ptr() as _, 190 | mem::size_of::(), 191 | buf.len(), 192 | self.as_file_ptr(), 193 | ) 194 | }; 195 | 196 | match self.last_error() { 197 | Some(err) => Err(err), 198 | None => Ok(written_bytes), 199 | } 200 | } 201 | 202 | fn flush(&mut self) -> io::Result<()> { 203 | // SAFETY: We check for the errors after the call 204 | let res = unsafe { libc::fflush(self.as_file_ptr()) }; 205 | 206 | match (res, self.last_error()) { 207 | (x, Some(err)) if x != 0 => Err(err), 208 | _ => Ok(()), 209 | } 210 | } 211 | } 212 | 213 | #[cfg(feature = "std")] 214 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 215 | impl Seek for JanetFile { 216 | fn seek(&mut self, pos: SeekFrom) -> io::Result { 217 | // SAFETY: We check for the errors after the call 218 | let res = unsafe { 219 | match pos { 220 | SeekFrom::Start(offset) => { 221 | libc::fseek(self.as_file_ptr(), offset as _, libc::SEEK_SET) 222 | }, 223 | SeekFrom::End(offset) => libc::fseek(self.as_file_ptr(), offset, libc::SEEK_END), 224 | SeekFrom::Current(offset) => { 225 | libc::fseek(self.as_file_ptr(), offset, libc::SEEK_CUR) 226 | }, 227 | } 228 | }; 229 | 230 | if res != 0 { 231 | if let Some(err) = self.last_error() { 232 | return Err(err); 233 | } 234 | } 235 | self.position() 236 | } 237 | } 238 | 239 | #[cfg(all(unix, feature = "std"))] 240 | #[cfg_attr(docsrs, doc(cfg(all(unix, feature = "std"))))] 241 | impl AsRawFd for JanetFile { 242 | #[inline] 243 | fn as_raw_fd(&self) -> RawFd { 244 | unsafe { libc::fileno(self.raw.file as *mut _) } 245 | } 246 | } 247 | 248 | #[cfg(all(unix, feature = "std"))] 249 | #[cfg_attr(docsrs, doc(cfg(all(unix, feature = "std"))))] 250 | impl IntoRawFd for JanetFile { 251 | #[inline] 252 | fn into_raw_fd(self) -> RawFd { 253 | self.as_raw_fd() 254 | } 255 | } 256 | 257 | #[cfg(all(windows, feature = "std"))] 258 | #[cfg_attr(docsrs, doc(cfg(all(windows, feature = "std"))))] 259 | impl AsRawHandle for JanetFile { 260 | #[inline] 261 | fn as_raw_handle(&self) -> RawHandle { 262 | extern "C-unwind" { 263 | fn _fileno(file: *mut FILE) -> libc::c_int; 264 | fn _get_osfhandle(fd: libc::c_int) -> RawHandle; 265 | } 266 | unsafe { _get_osfhandle(_fileno(self.raw.file as *mut _)) } 267 | } 268 | } 269 | 270 | #[cfg(all(windows, feature = "std"))] 271 | #[cfg_attr(docsrs, doc(cfg(all(windows, feature = "std"))))] 272 | impl IntoRawHandle for JanetFile { 273 | #[inline] 274 | fn into_raw_handle(self) -> RawHandle { 275 | self.as_raw_handle() 276 | } 277 | } 278 | 279 | impl fmt::Debug for JanetFile { 280 | #[inline] 281 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 282 | #[cfg(windows)] 283 | extern "C-unwind" { 284 | fn _fileno(file: *mut FILE) -> libc::c_int; 285 | } 286 | #[cfg(windows)] 287 | let fd = unsafe { _fileno(self.raw.file as *mut _) }; 288 | #[cfg(unix)] 289 | let fd = unsafe { libc::fileno(self.raw.file as *mut _) }; 290 | if fd < 0 { 291 | return Err(fmt::Error); 292 | } 293 | 294 | 295 | f.debug_struct("JanetFile") 296 | .field("fd", &fd) 297 | .field("read", &self.flags().is_read()) 298 | .field("write", &self.flags().is_write()) 299 | .field("append", &self.flags().is_append()) 300 | .field("binary", &self.flags().is_binary()) 301 | .field("update", &self.flags().is_update()) 302 | .field("closed", &self.flags().is_closed()) 303 | .field("not_closeable", &self.flags().is_not_closeable()) 304 | .field("no_nil", &self.flags().is_no_nil()) 305 | .field("serializable", &self.flags().is_serializable()) 306 | .finish() 307 | } 308 | } 309 | 310 | 311 | 312 | /// Flags related to the [`JanetFile`]. 313 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 314 | #[repr(transparent)] 315 | pub struct FileFlags(i32); 316 | 317 | impl FileFlags { 318 | pub const APPEND: Self = Self(evil_janet::JANET_FILE_APPEND as i32); 319 | pub const BINARY: Self = Self(evil_janet::JANET_FILE_BINARY as i32); 320 | pub const CLOSED: Self = Self(evil_janet::JANET_FILE_CLOSED as i32); 321 | pub const NOT_CLOSEABLE: Self = Self(evil_janet::JANET_FILE_NOT_CLOSEABLE as i32); 322 | pub const NO_NIL: Self = Self(evil_janet::JANET_FILE_NONIL as i32); 323 | #[janetrs_macros::cjvg("1.9.1", "1.22.0")] 324 | pub const PIPED: Self = Self(evil_janet::JANET_FILE_PIPED as i32); 325 | pub const READ: Self = Self(evil_janet::JANET_FILE_READ as i32); 326 | pub const SERIALIZEBLE: Self = Self(evil_janet::JANET_FILE_SERIALIZABLE as i32); 327 | pub const UPDATE: Self = Self(evil_janet::JANET_FILE_UPDATE as i32); 328 | pub const WRITE: Self = Self(evil_janet::JANET_FILE_WRITE as i32); 329 | 330 | /// Check if the flag has the append value. 331 | #[inline] 332 | #[must_use = "this returns the result of the operation, without modifying the original"] 333 | pub const fn is_append(self) -> bool { 334 | (self.0 & Self::APPEND.0) == Self::APPEND.0 335 | } 336 | 337 | /// Check if the flag has the binary value. 338 | #[inline] 339 | #[must_use = "this returns the result of the operation, without modifying the original"] 340 | pub const fn is_binary(self) -> bool { 341 | (self.0 & Self::BINARY.0) == Self::BINARY.0 342 | } 343 | 344 | /// Check if the flag has the closed value. 345 | #[inline] 346 | #[must_use = "this returns the result of the operation, without modifying the original"] 347 | pub const fn is_closed(self) -> bool { 348 | (self.0 & Self::CLOSED.0) == Self::CLOSED.0 349 | } 350 | 351 | /// Check if the flag has the `not_closeable` value. 352 | #[inline] 353 | #[must_use = "this returns the result of the operation, without modifying the original"] 354 | pub const fn is_not_closeable(self) -> bool { 355 | (self.0 & Self::NOT_CLOSEABLE.0) == Self::NOT_CLOSEABLE.0 356 | } 357 | 358 | /// Check if the flag has the `no_nil` value. 359 | #[inline] 360 | #[must_use = "this returns the result of the operation, without modifying the original"] 361 | pub const fn is_no_nil(self) -> bool { 362 | (self.0 & Self::NO_NIL.0) == Self::NO_NIL.0 363 | } 364 | 365 | /// Check if the flag has the `piped` value. 366 | #[inline] 367 | #[janetrs_macros::cjvg("1.9.1", "1.22.0")] 368 | #[must_use = "this returns the result of the operation, without modifying the original"] 369 | pub const fn is_piped(self) -> bool { 370 | (self.0 & Self::PIPED.0) == Self::PIPED.0 371 | } 372 | 373 | /// Check if the flag has the `update` value. 374 | #[must_use = "this returns the result of the operation, without modifying the original"] 375 | pub const fn is_update(self) -> bool { 376 | (self.0 & Self::UPDATE.0) == Self::UPDATE.0 377 | } 378 | 379 | /// Check if the flag has the `read` value. 380 | #[inline] 381 | #[must_use = "this returns the result of the operation, without modifying the original"] 382 | pub const fn is_read(self) -> bool { 383 | (self.0 & Self::READ.0) == Self::READ.0 384 | } 385 | 386 | /// Check if the flag has the `serializable` value. 387 | #[inline] 388 | #[must_use = "this returns the result of the operation, without modifying the original"] 389 | pub const fn is_serializable(self) -> bool { 390 | (self.0 & Self::SERIALIZEBLE.0) == Self::SERIALIZEBLE.0 391 | } 392 | 393 | /// Check if the flag has the `write` value. 394 | #[inline] 395 | #[must_use = "this returns the result of the operation, without modifying the original"] 396 | pub const fn is_write(self) -> bool { 397 | (self.0 & Self::WRITE.0) == Self::WRITE.0 398 | } 399 | 400 | /// Return the inner i32 value of the flags 401 | #[inline] 402 | #[must_use = "this returns the result of the operation, without modifying the original"] 403 | pub const fn as_i32(self) -> i32 { 404 | self.0 405 | } 406 | } 407 | 408 | impl core::ops::BitAnd for FileFlags { 409 | type Output = Self; 410 | 411 | #[inline] 412 | fn bitand(self, rhs: Self) -> Self::Output { 413 | Self(self.0.bitand(rhs.0)) 414 | } 415 | } 416 | 417 | impl core::ops::BitAnd for FileFlags { 418 | type Output = Self; 419 | 420 | #[inline] 421 | fn bitand(self, rhs: i32) -> Self::Output { 422 | Self(self.0.bitand(rhs)) 423 | } 424 | } 425 | 426 | impl core::ops::BitOr for FileFlags { 427 | type Output = Self; 428 | 429 | #[inline] 430 | fn bitor(self, rhs: Self) -> Self::Output { 431 | Self(self.0.bitor(rhs.0)) 432 | } 433 | } 434 | 435 | impl core::ops::BitOr for FileFlags { 436 | type Output = Self; 437 | 438 | #[inline] 439 | fn bitor(self, rhs: i32) -> Self::Output { 440 | Self(self.0.bitor(rhs)) 441 | } 442 | } 443 | 444 | impl core::ops::BitAndAssign for FileFlags { 445 | #[inline] 446 | fn bitand_assign(&mut self, rhs: Self) { 447 | self.0.bitand_assign(rhs.0) 448 | } 449 | } 450 | 451 | impl core::ops::BitAndAssign for FileFlags { 452 | #[inline] 453 | fn bitand_assign(&mut self, rhs: i32) { 454 | self.0.bitand_assign(rhs) 455 | } 456 | } 457 | 458 | impl core::ops::BitOrAssign for FileFlags { 459 | #[inline] 460 | fn bitor_assign(&mut self, rhs: Self) { 461 | self.0.bitor_assign(rhs.0) 462 | } 463 | } 464 | 465 | impl core::ops::BitOrAssign for FileFlags { 466 | #[inline] 467 | fn bitor_assign(&mut self, rhs: i32) { 468 | self.0.bitor_assign(rhs) 469 | } 470 | } 471 | 472 | #[cfg(all(test, any(feature = "amalgation", feature = "link-system")))] 473 | mod tests { 474 | use super::*; 475 | 476 | fn make_tmp() -> Janet { 477 | unsafe { 478 | let tmp = libc::tmpfile(); 479 | evil_janet::janet_makefile( 480 | tmp as _, 481 | (evil_janet::JANET_FILE_WRITE 482 | | evil_janet::JANET_FILE_READ 483 | | evil_janet::JANET_FILE_BINARY) as _, 484 | ) 485 | .into() 486 | } 487 | } 488 | 489 | #[test] 490 | fn it_works() -> Result<(), crate::client::Error> { 491 | let client = crate::client::JanetClient::init_with_default_env()?; 492 | 493 | let stdout_janet = client.env().unwrap().resolve("stdout").unwrap(); 494 | 495 | let stdout: JanetAbstract = stdout_janet.try_unwrap().unwrap(); 496 | 497 | let file = stdout.get::().unwrap(); 498 | let flags = file.flags(); 499 | 500 | assert!(flags.is_append()); 501 | assert!(flags.is_not_closeable()); 502 | assert!(flags.is_serializable()); 503 | 504 | Ok(()) 505 | } 506 | 507 | #[cfg_attr(feature = "std", test)] 508 | fn read_write_seek() -> Result<(), crate::client::Error> { 509 | use std::io::{Read, Seek, SeekFrom, Write}; 510 | let _client = crate::client::JanetClient::init()?; 511 | 512 | let jtmp = make_tmp(); 513 | let mut atmp: JanetAbstract = jtmp.try_unwrap().unwrap(); 514 | let tmp = atmp.get_mut::().unwrap(); 515 | 516 | assert_eq!(tmp.write(b"test").unwrap(), 4); 517 | assert_eq!(tmp.stream_position().unwrap(), 4); 518 | 519 | let mut buff = [0; 4]; 520 | assert_eq!(tmp.read(&mut buff[..]).unwrap(), 0); 521 | assert_eq!(tmp.seek(SeekFrom::Start(0)).unwrap(), 0); 522 | 523 | tmp.flush().unwrap(); 524 | 525 | assert_eq!(tmp.read(&mut buff[..]).unwrap(), 4); 526 | assert_eq!(buff, &b"test"[..]); 527 | 528 | Ok(()) 529 | } 530 | } 531 | -------------------------------------------------------------------------------- /src/types/pointer.rs: -------------------------------------------------------------------------------- 1 | //! Module for the Janet Pointer type. 2 | //! 3 | //! Calvin Rose: 4 | //! > The pointer type should probably almost never be used. Abstract types are almost 5 | //! > always better, but each abstract type requires at least one allocation 6 | use core::{cell::Cell, ffi::c_void, fmt, marker::PhantomData}; 7 | 8 | /// Janet pointer type. 9 | /// 10 | /// This type works and behaves as `*mut c_void`. 11 | /// JanetPointer usage should be avoided, alternatively you can use `JanetAbstract` 12 | /// instead. It is only used by Janet internally for optimization properties and some 13 | /// Janet modules uses it as well for the same purposes. 14 | #[repr(transparent)] 15 | #[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] 16 | pub struct JanetPointer { 17 | pub(crate) inner: *mut c_void, 18 | phantom: PhantomData>, 19 | } 20 | 21 | impl JanetPointer { 22 | /// Creates a new `JanetPointer` 23 | #[inline] 24 | pub const fn new(ptr: *mut c_void) -> Self { 25 | Self { 26 | inner: ptr, 27 | phantom: PhantomData, 28 | } 29 | } 30 | 31 | /// Returns `true` if the pointer is null. 32 | #[inline] 33 | #[must_use = "this returns the result of the operation, without modifying the original"] 34 | pub fn is_null(&self) -> bool { 35 | self.inner.is_null() 36 | } 37 | 38 | /// Acquires the underlying `*mut` pointer. 39 | #[inline] 40 | #[must_use] 41 | pub const fn as_ptr(self) -> *mut c_void { 42 | self.inner 43 | } 44 | 45 | /// Casts to a pointer of another type. 46 | #[inline] 47 | #[must_use] 48 | pub const fn cast(self) -> *mut U { 49 | self.inner as *mut U 50 | } 51 | } 52 | 53 | impl fmt::Debug for JanetPointer { 54 | #[inline] 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | fmt::Debug::fmt(&self.as_ptr(), f) 57 | } 58 | } 59 | 60 | 61 | impl fmt::Pointer for JanetPointer { 62 | #[inline] 63 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 64 | fmt::Pointer::fmt(&self.as_ptr(), f) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/types/random.rs: -------------------------------------------------------------------------------- 1 | use core::{fmt, mem}; 2 | 3 | use rand_core::{RngCore, SeedableRng}; 4 | 5 | use crate::{IsJanetAbstract, Janet, JanetAbstract}; 6 | 7 | /// Type representing the Janet pseudorandom number generator 8 | /// 9 | /// It uses `xorwow` variation of `Xorshift random number generator`, but the algorithm 10 | /// used is not set to stone and can be changed in future versions of Janet. 11 | #[derive(Clone)] 12 | #[repr(transparent)] 13 | pub struct JanetRng { 14 | #[allow(dead_code)] 15 | raw: evil_janet::JanetRNG, 16 | } 17 | 18 | impl fmt::Debug for JanetRng { 19 | #[inline] 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | if f.alternate() { 22 | return fmt::Debug::fmt(&self.raw, f); 23 | } 24 | f.pad("JanetRng") 25 | } 26 | } 27 | 28 | unsafe impl IsJanetAbstract for JanetRng { 29 | type Get = Self; 30 | 31 | const SIZE: usize = mem::size_of::(); 32 | 33 | #[inline] 34 | fn type_info() -> &'static evil_janet::JanetAbstractType { 35 | unsafe { &evil_janet::janet_rng_type } 36 | } 37 | } 38 | 39 | impl From for JanetAbstract { 40 | #[inline] 41 | fn from(value: JanetRng) -> Self { 42 | Self::new(value) 43 | } 44 | } 45 | 46 | impl From for Janet { 47 | #[inline] 48 | fn from(value: JanetRng) -> Self { 49 | Self::j_abstract(JanetAbstract::new(value)) 50 | } 51 | } 52 | 53 | impl SeedableRng for JanetRng { 54 | type Seed = alloc::vec::Vec; 55 | 56 | fn from_seed(mut seed: Self::Seed) -> Self { 57 | unsafe { 58 | let rng = evil_janet::janet_default_rng(); 59 | evil_janet::janet_rng_longseed(rng, seed.as_mut_ptr(), seed.len() as i32); 60 | 61 | let rng = *rng; 62 | Self { raw: rng } 63 | } 64 | } 65 | 66 | fn seed_from_u64(state: u64) -> Self { 67 | unsafe { 68 | let rng = evil_janet::janet_default_rng(); 69 | evil_janet::janet_rng_seed(rng, state as u32); 70 | 71 | let rng = *rng; 72 | Self { raw: rng } 73 | } 74 | } 75 | } 76 | 77 | impl RngCore for JanetRng { 78 | #[inline] 79 | fn next_u32(&mut self) -> u32 { 80 | unsafe { 81 | evil_janet::janet_rng_u32( 82 | // SAFETY: this cast is safe because JanetRng is repr(transparent) over 83 | // evil_janet::JanetRNG 84 | self as *mut JanetRng as *mut _, 85 | ) 86 | } 87 | } 88 | 89 | #[inline] 90 | fn next_u64(&mut self) -> u64 { 91 | self.next_u32() as u64 92 | } 93 | 94 | fn fill_bytes(&mut self, dest: &mut [u8]) { 95 | rand_core::impls::fill_bytes_via_next(self, dest) 96 | } 97 | 98 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { 99 | self.fill_bytes(dest); 100 | Ok(()) 101 | } 102 | } 103 | 104 | 105 | #[cfg(all(test, any(feature = "amalgation", feature = "link-system")))] 106 | mod tests { 107 | use super::*; 108 | 109 | #[test] 110 | fn it_works() -> Result<(), crate::client::Error> { 111 | let _client = crate::client::JanetClient::init()?; 112 | let mut rng = JanetRng::seed_from_u64(11); 113 | let test1 = rng.next_u32(); 114 | let test2 = rng.next_u32(); 115 | 116 | assert_eq!(3469148811, test1); 117 | assert_ne!(test1, test2); 118 | 119 | let mut rng = JanetRng::from_seed(alloc::vec![11, 9, 23, 255]); 120 | let test3 = rng.next_u64(); 121 | let test4 = rng.next_u64(); 122 | 123 | assert_eq!(1887761749, test3); 124 | assert_ne!(test3, test4); 125 | 126 | Ok(()) 127 | } 128 | 129 | #[test] 130 | fn jabstract() -> Result<(), crate::client::Error> { 131 | let _client = crate::client::JanetClient::init()?; 132 | 133 | let rng = JanetRng::seed_from_u64(10); 134 | let mut jabstract = JanetAbstract::new(rng); 135 | 136 | let rng = jabstract.get_mut::().unwrap(); 137 | let test1 = rng.next_u32(); 138 | 139 | assert_eq!(2254829444, test1); 140 | 141 | Ok(()) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/types/symbol.rs: -------------------------------------------------------------------------------- 1 | //! Janet symbols and keywords types. 2 | use core::{ 3 | convert::Infallible, 4 | fmt::{self, Debug, Display, Write}, 5 | marker::PhantomData, 6 | str::FromStr, 7 | }; 8 | 9 | 10 | use bstr::BStr; 11 | 12 | use super::{JanetBuffer, JanetString}; 13 | 14 | /// Janet symbol type. Usually used to name things in Janet. 15 | #[repr(transparent)] 16 | pub struct JanetSymbol<'data> { 17 | pub(crate) raw: *const u8, 18 | phantom: PhantomData<&'data ()>, 19 | } 20 | 21 | impl JanetSymbol<'_> { 22 | /// Create a [`JanetSymbol`] with given `name`. 23 | /// 24 | /// If the given `name` is bigger than [`i32::MAX`] the generated symbol will have a 25 | /// name truncated to that max size. That's unrealistic thought. 26 | /// 27 | /// # Examples 28 | /// ``` 29 | /// use janetrs::JanetSymbol; 30 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 31 | /// 32 | /// let s = JanetSymbol::new("name"); 33 | /// ``` 34 | #[inline] 35 | #[must_use = "function is a constructor associated function"] 36 | pub fn new(name: impl AsRef<[u8]>) -> Self { 37 | let val = name.as_ref(); 38 | 39 | let len = if val.len() > i32::MAX as usize { 40 | i32::MAX 41 | } else { 42 | val.len() as i32 43 | }; 44 | 45 | Self { 46 | raw: unsafe { evil_janet::janet_symbol(val.as_ptr(), len) }, 47 | phantom: PhantomData, 48 | } 49 | } 50 | 51 | /// Generate a unique Janet symbol. This is used in the library function gensym. The 52 | /// symbol will be of the format _XXXXXX, where X is a base64 digit, and prefix is 53 | /// the argument passed. No prefix for speed. 54 | /// 55 | /// # Examples 56 | /// ``` 57 | /// use janetrs::JanetSymbol; 58 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 59 | /// 60 | /// let s = JanetSymbol::unique(); 61 | /// ``` 62 | #[inline] 63 | #[must_use = "function is a constructor associated function"] 64 | pub fn unique() -> Self { 65 | Self { 66 | raw: unsafe { evil_janet::janet_symbol_gen() }, 67 | phantom: PhantomData, 68 | } 69 | } 70 | 71 | /// Create a new [`JanetSymbol`] with a `raw` pointer. 72 | /// 73 | /// # Safety 74 | /// This function do not check if the given `raw` is `NULL` or not. Use at your 75 | /// own risk. 76 | #[inline] 77 | #[must_use] 78 | pub const unsafe fn from_raw(raw: *const u8) -> Self { 79 | Self { 80 | raw, 81 | phantom: PhantomData, 82 | } 83 | } 84 | 85 | /// Returns the length of this [`JanetSymbol`], in bytes, not [`char`]s or graphemes. 86 | /// In other words, it may not be what a human considers the length of the string. 87 | /// 88 | /// # Examples 89 | /// ``` 90 | /// use janetrs::JanetSymbol; 91 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 92 | /// 93 | /// let s = JanetSymbol::new("name"); 94 | /// assert_eq!(s.len(), 4); 95 | /// ``` 96 | #[inline] 97 | #[must_use = "this returns the result of the operation, without modifying the original"] 98 | pub fn len(&self) -> usize { 99 | unsafe { (*evil_janet::janet_string_head(self.raw)).length as usize } 100 | } 101 | 102 | /// Returns `true` if this [`JanetSymbol`] has a length of zero, and `false` 103 | /// otherwise. 104 | /// 105 | /// # Examples 106 | /// ``` 107 | /// use janetrs::JanetSymbol; 108 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 109 | /// 110 | /// let s = JanetSymbol::new("name"); 111 | /// assert!(!s.is_empty()); 112 | /// ``` 113 | #[inline] 114 | #[must_use = "this returns the result of the operation, without modifying the original"] 115 | pub fn is_empty(&self) -> bool { 116 | self.len() == 0 117 | } 118 | 119 | /// Returns a byte slice of the [`JanetString`] contents. 120 | /// 121 | /// # Examples 122 | /// ``` 123 | /// use janetrs::JanetSymbol; 124 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 125 | /// 126 | /// let s = JanetSymbol::new("hello"); 127 | /// 128 | /// assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes()); 129 | /// ``` 130 | #[inline] 131 | #[must_use = "this returns the result of the operation, without modifying the original"] 132 | pub fn as_bytes(&self) -> &[u8] { 133 | unsafe { core::slice::from_raw_parts(self.raw, self.len()) } 134 | } 135 | 136 | /// Return a raw pointer to the symbol raw structure. 137 | /// 138 | /// The caller must ensure that the buffer outlives the pointer this function returns, 139 | /// or else it will end up pointing to garbage. 140 | #[inline] 141 | #[must_use] 142 | pub const fn as_raw(&self) -> *const u8 { 143 | self.raw 144 | } 145 | } 146 | 147 | impl Debug for JanetSymbol<'_> { 148 | #[inline] 149 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 150 | let bstr: &BStr = self.as_bytes().as_ref(); 151 | 152 | Debug::fmt(bstr, f) 153 | } 154 | } 155 | 156 | impl Display for JanetSymbol<'_> { 157 | #[inline] 158 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 159 | let bstr: &BStr = self.as_bytes().as_ref(); 160 | 161 | Display::fmt(bstr, f) 162 | } 163 | } 164 | 165 | impl Clone for JanetSymbol<'_> { 166 | #[inline] 167 | fn clone(&self) -> Self { 168 | Self { 169 | raw: unsafe { evil_janet::janet_symbol(self.raw, self.len() as i32) }, 170 | phantom: PhantomData, 171 | } 172 | } 173 | } 174 | 175 | impl From> for JanetSymbol<'_> { 176 | #[inline] 177 | fn from(string: JanetString<'_>) -> Self { 178 | JanetSymbol::new(string) 179 | } 180 | } 181 | 182 | impl From<&JanetString<'_>> for JanetSymbol<'_> { 183 | #[inline] 184 | fn from(string: &JanetString<'_>) -> Self { 185 | JanetSymbol::new(string) 186 | } 187 | } 188 | 189 | impl From> for JanetSymbol<'_> { 190 | #[inline] 191 | fn from(key: JanetKeyword<'_>) -> Self { 192 | JanetSymbol::new(key) 193 | } 194 | } 195 | 196 | impl From<&JanetKeyword<'_>> for JanetSymbol<'_> { 197 | #[inline] 198 | fn from(key: &JanetKeyword<'_>) -> Self { 199 | JanetSymbol::new(key) 200 | } 201 | } 202 | 203 | impl From> for JanetSymbol<'_> { 204 | #[inline] 205 | fn from(buff: JanetBuffer<'_>) -> Self { 206 | From::<&JanetBuffer<'_>>::from(&buff) 207 | } 208 | } 209 | 210 | impl From<&JanetBuffer<'_>> for JanetSymbol<'_> { 211 | #[inline] 212 | fn from(buff: &JanetBuffer<'_>) -> Self { 213 | let slice = buff.as_bytes(); 214 | JanetSymbol::new(slice) 215 | } 216 | } 217 | 218 | impl AsRef<[u8]> for JanetSymbol<'_> { 219 | #[inline] 220 | fn as_ref(&self) -> &[u8] { 221 | self.as_bytes() 222 | } 223 | } 224 | 225 | impl AsRef for JanetSymbol<'_> { 226 | #[inline] 227 | fn as_ref(&self) -> &BStr { 228 | self.as_bytes().as_ref() 229 | } 230 | } 231 | 232 | impl FromStr for JanetSymbol<'_> { 233 | type Err = Infallible; 234 | 235 | #[inline] 236 | fn from_str(s: &str) -> Result { 237 | Ok(Self::from(s)) 238 | } 239 | } 240 | 241 | /// Janet keyword. Janet being a lisp-like language a keyword is not a especial word of 242 | /// the language, it is a normal string that can be defined by the user. 243 | #[repr(transparent)] 244 | pub struct JanetKeyword<'data> { 245 | pub(crate) raw: *const u8, 246 | phantom: PhantomData<&'data ()>, 247 | } 248 | 249 | impl JanetKeyword<'_> { 250 | /// Create a [`JanetKeyword`] with given `name`. 251 | /// 252 | /// If the given `name` is bigger than i32::MAX the generated symbol will have a name 253 | /// truncated to that max size. That's unrealistic thought. 254 | /// 255 | /// # Examples 256 | /// ``` 257 | /// use janetrs::JanetKeyword; 258 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 259 | /// 260 | /// let k = JanetKeyword::new("name"); 261 | /// ``` 262 | #[inline] 263 | #[must_use = "function is a constructor associated function"] 264 | pub fn new(name: impl AsRef<[u8]>) -> Self { 265 | let val = name.as_ref(); 266 | 267 | let len = if val.len() > i32::MAX as usize { 268 | i32::MAX 269 | } else { 270 | val.len() as i32 271 | }; 272 | 273 | Self { 274 | raw: unsafe { evil_janet::janet_symbol(val.as_ptr(), len) }, 275 | phantom: PhantomData, 276 | } 277 | } 278 | 279 | /// Create a new [`JanetKeyword`] with a `raw` pointer. 280 | /// 281 | /// # Safety 282 | /// This function do not check if the given `raw` is `NULL` or not. Use at your 283 | /// own risk. 284 | #[inline] 285 | #[must_use = "function is a constructor associated function"] 286 | pub const unsafe fn from_raw(raw: *const u8) -> Self { 287 | Self { 288 | raw, 289 | phantom: PhantomData, 290 | } 291 | } 292 | 293 | /// Returns the length of this [`JanetKeyword`], in bytes, not [`char`]s or graphemes. 294 | /// In other words, it may not be what a human considers the length of the string. 295 | /// 296 | /// # Examples 297 | /// ``` 298 | /// use janetrs::JanetKeyword; 299 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 300 | /// 301 | /// let k = JanetKeyword::new("name"); 302 | /// assert_eq!(k.len(), 4); 303 | /// ``` 304 | #[inline] 305 | #[must_use = "this returns the result of the operation, without modifying the original"] 306 | pub fn len(&self) -> usize { 307 | unsafe { (*evil_janet::janet_string_head(self.raw)).length as usize } 308 | } 309 | 310 | /// Returns `true` if this [`JanetKeyword`] has a length of zero, and `false` 311 | /// otherwise. 312 | /// 313 | /// # Examples 314 | /// ``` 315 | /// use janetrs::JanetKeyword; 316 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 317 | /// 318 | /// let k = JanetKeyword::new("name"); 319 | /// assert!(!k.is_empty()); 320 | /// ``` 321 | #[inline] 322 | #[must_use = "this returns the result of the operation, without modifying the original"] 323 | pub fn is_empty(&self) -> bool { 324 | self.len() == 0 325 | } 326 | 327 | /// Returns a byte slice of the [`JanetString`] contents. 328 | /// 329 | /// # Examples 330 | /// ``` 331 | /// use janetrs::JanetKeyword; 332 | /// # let _client = janetrs::client::JanetClient::init().unwrap(); 333 | /// 334 | /// let s = JanetKeyword::new("hello"); 335 | /// 336 | /// assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes()); 337 | /// ``` 338 | #[inline] 339 | #[must_use = "this returns the result of the operation, without modifying the original"] 340 | pub fn as_bytes(&self) -> &[u8] { 341 | unsafe { core::slice::from_raw_parts(self.raw, self.len()) } 342 | } 343 | 344 | /// Return a raw pointer to the keyword raw structure. 345 | /// 346 | /// The caller must ensure that the buffer outlives the pointer this function returns, 347 | /// or else it will end up pointing to garbage. 348 | #[inline] 349 | #[must_use] 350 | pub const fn as_raw(&self) -> *const u8 { 351 | self.raw 352 | } 353 | } 354 | 355 | impl Debug for JanetKeyword<'_> { 356 | #[inline] 357 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 358 | let bstr: &BStr = self.as_bytes().as_ref(); 359 | 360 | Debug::fmt(bstr, f) 361 | } 362 | } 363 | 364 | impl Display for JanetKeyword<'_> { 365 | #[inline] 366 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 367 | let bstr: &BStr = self.as_bytes().as_ref(); 368 | f.write_char(':')?; 369 | Display::fmt(bstr, f) 370 | } 371 | } 372 | 373 | impl Clone for JanetKeyword<'_> { 374 | #[inline] 375 | fn clone(&self) -> Self { 376 | Self { 377 | raw: unsafe { evil_janet::janet_symbol(self.raw, self.len() as i32) }, 378 | phantom: PhantomData, 379 | } 380 | } 381 | } 382 | 383 | impl From> for JanetKeyword<'_> { 384 | #[inline] 385 | fn from(string: JanetString<'_>) -> Self { 386 | JanetKeyword::new(string) 387 | } 388 | } 389 | 390 | impl From<&JanetString<'_>> for JanetKeyword<'_> { 391 | #[inline] 392 | fn from(string: &JanetString<'_>) -> Self { 393 | JanetKeyword::new(string) 394 | } 395 | } 396 | 397 | impl From> for JanetKeyword<'_> { 398 | #[inline] 399 | fn from(sym: JanetSymbol<'_>) -> Self { 400 | JanetKeyword::new(sym) 401 | } 402 | } 403 | 404 | impl From<&JanetSymbol<'_>> for JanetKeyword<'_> { 405 | #[inline] 406 | fn from(sym: &JanetSymbol<'_>) -> Self { 407 | JanetKeyword::new(sym) 408 | } 409 | } 410 | 411 | impl From> for JanetKeyword<'_> { 412 | #[inline] 413 | fn from(buff: JanetBuffer<'_>) -> Self { 414 | From::<&JanetBuffer<'_>>::from(&buff) 415 | } 416 | } 417 | 418 | impl From<&JanetBuffer<'_>> for JanetKeyword<'_> { 419 | #[inline] 420 | fn from(buff: &JanetBuffer<'_>) -> Self { 421 | let slice = buff.as_bytes(); 422 | JanetKeyword::new(slice) 423 | } 424 | } 425 | 426 | impl AsRef<[u8]> for JanetKeyword<'_> { 427 | #[inline] 428 | fn as_ref(&self) -> &[u8] { 429 | self.as_bytes() 430 | } 431 | } 432 | 433 | impl AsRef for JanetKeyword<'_> { 434 | #[inline] 435 | fn as_ref(&self) -> &BStr { 436 | self.as_bytes().as_ref() 437 | } 438 | } 439 | 440 | impl FromStr for JanetKeyword<'_> { 441 | type Err = Infallible; 442 | 443 | #[inline] 444 | fn from_str(s: &str) -> Result { 445 | Ok(Self::from(s)) 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! Module for stuff that are not required either to use in a application or to write 2 | //! janet modules. 3 | use core::fmt; 4 | 5 | use crate::types::Janet; 6 | 7 | pub use janetrs_version::{JanetBuildConfig, JanetVersion}; 8 | 9 | /// Checks if the given `args` have the same amount of expected arguments, if the check 10 | /// fails it panics from the Janet side. 11 | #[inline] 12 | pub fn check_fix_arity(args: &[Janet], fix_arity: usize) { 13 | if args.len() != fix_arity { 14 | crate::jpanic!("arity mismatch, expected {}, got {}", fix_arity, args.len()); 15 | } 16 | } 17 | 18 | /// Check if the given `args` have the expected arguments in the inclusive range, if the 19 | /// check fails it panics from the Janet side. 20 | /// 21 | /// If `max` is `None`, it will disable the maximum check, allowing variadic arguments. 22 | #[inline] 23 | pub fn check_range_arity(args: &[Janet], min: usize, max: Option) { 24 | let arity = args.len(); 25 | 26 | if arity < min { 27 | crate::jpanic!("arity mismatch, expected at least {}, got {}", min, arity); 28 | } 29 | 30 | if let Some(max) = max { 31 | if arity > max { 32 | crate::jpanic!("arity mismatch, expected at most {}, got {}", max, arity); 33 | } 34 | } 35 | } 36 | 37 | /// Just a wrapper for the janet panic function 38 | #[doc(hidden)] 39 | #[inline] 40 | pub fn _panic(msg: Janet) -> ! { 41 | unsafe { evil_janet::janet_panicv(msg.inner) } 42 | } 43 | 44 | #[doc(hidden)] 45 | #[allow(dead_code)] 46 | #[track_caller] 47 | pub fn assert_deep_inner( 48 | op: &'static str, left: &dyn fmt::Debug, right: &dyn fmt::Debug, 49 | args: Option>, 50 | ) -> ! { 51 | match args { 52 | Some(args) => panic!( 53 | r#"assertion `left {op} right` failed: {args} 54 | left: {left:?} 55 | right: {right:?}"# 56 | ), 57 | None => panic!( 58 | r#"assertion `left {op} right` failed 59 | left: {left:?} 60 | right: {right:?}"# 61 | ), 62 | } 63 | } 64 | --------------------------------------------------------------------------------