Showing preview only (362K chars total). Download the full file or copy to clipboard to get everything.
Repository: Anyolite/anyolite
Branch: main
Commit: 744a86638656
Files: 67
Total size: 342.3 KB
Directory structure:
gitextract_2zcw5nvv/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ └── problem-report.md
│ └── workflows/
│ ├── doc.yml
│ ├── mritest.yml
│ ├── test.yml
│ └── wintest.yml
├── .gitignore
├── .gitmodules
├── Changelog.md
├── LICENSE
├── README.md
├── Rakefile.rb
├── anyolite.cr
├── config_files/
│ ├── anyolite_config_external_ruby.json
│ ├── anyolite_config_mri.json
│ └── anyolite_config_mruby.json
├── examples/
│ ├── bytecode_test.rb
│ ├── hp_example.rb
│ ├── mri_test.rb
│ ├── test.rb
│ └── test_framework.rb
├── glue/
│ ├── mri/
│ │ ├── data_helper.c
│ │ ├── error_helper.c
│ │ ├── return_functions.c
│ │ └── script_helper.c
│ └── mruby/
│ ├── data_helper.c
│ ├── error_helper.c
│ ├── return_functions.c
│ └── script_helper.c
├── install.cr
├── shard.yml
├── src/
│ ├── BytecodeCompiler.cr
│ ├── BytecodeGetter.cr
│ ├── Macro.cr
│ ├── Main.cr
│ ├── Preloader.cr
│ ├── RbArgCache.cr
│ ├── RbCast.cr
│ ├── RbClass.cr
│ ├── RbClassCache.cr
│ ├── RbInternal.cr
│ ├── RbInterpreter.cr
│ ├── RbModule.cr
│ ├── RbRefTable.cr
│ ├── RbTypeCache.cr
│ ├── helper_classes/
│ │ ├── AnyolitePointer.cr
│ │ ├── HelperClasses.cr
│ │ └── Regex.cr
│ ├── implementations/
│ │ ├── mri/
│ │ │ ├── FormatString.cr
│ │ │ ├── Implementation.cr
│ │ │ └── RbCore.cr
│ │ └── mruby/
│ │ ├── FormatString.cr
│ │ ├── Implementation.cr
│ │ ├── KeywordArgStruct.cr
│ │ └── RbCore.cr
│ └── macros/
│ ├── ArgConversions.cr
│ ├── ArgTuples.cr
│ ├── FunctionCalls.cr
│ ├── FunctionGenerators.cr
│ ├── ObjectAllocations.cr
│ ├── RubyConversions.cr
│ ├── RubyTypes.cr
│ ├── UnionCasts.cr
│ ├── WrapAll.cr
│ ├── WrapMethodIndex.cr
│ └── Wrappers.cr
├── test.cr
└── utility/
└── mruby_build_config.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/problem-report.md
================================================
---
name: Problem report
about: Something may not work as inteded or requires fixing
title: ''
labels: ''
assignees: ''
---
**Describe the problem**
Please describe your problem in a detailed way here, so others can understand it.
Some tips:
* Be objective
* Make sure you read the Readme, documentation and wiki
* Check if you installed all required programs (especially Rake)
* Include all error messages and warnings potentially related to the problem
* If the problem depends on specific steps, describe them in a way others can reproduce consistently
**To Reproduce**
Please require a minimal working code example on how to reproduce the issue, if possible.
Avoid any unnecessary detail, but make sure the problem still persists in the example. If you are unable to provide a minimal working example, you may also refer to a specific repository instead.
**Expected behavior**
If something does not work the way you would expect it to, please describe the expected behavior shortly. This is only necessary if you don't encounter obvious errors or crashes.
**Platform and version**
Please describe which versions of Crystal and Anyolite you are using, which Anyolite flags you are using and which platform you are on.
**Additional context**
Is there anything else you want to mention that did not fit into the previous sections?
================================================
FILE: .github/workflows/doc.yml
================================================
name: Doc
on:
release:
types: [published, edited, prereleased]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Crystal
uses: oprypin/install-crystal@v1
with:
crystal: nightly
- name: Build documentation
run: ANYOLITE_DOCUMENTATION_MODE=1 crystal docs
- name: Deployment
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
================================================
FILE: .github/workflows/mritest.yml
================================================
name: MRITest
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '45 03 * * 6'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Crystal
uses: oprypin/install-crystal@v1
with:
crystal: nightly
- name: Install Autoconf
run: sudo apt install -y autoconf
- name: Install Ruby
run: sudo apt install -y ruby
- name: Build shard
run: ANYOLITE_CONFIG_PATH="config_files/anyolite_config_mri.json" rake build_shard
- name: Test script
run: crystal run test.cr --error-trace -Danyolite_implementation_ruby_3
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '45 03 * * 6'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Crystal
uses: oprypin/install-crystal@v1
with:
crystal: nightly
- name: Install Ruby
run: sudo apt install -y ruby
- name: Build shard
run: rake build_shard
- name: Test script
run: crystal test.cr
================================================
FILE: .github/workflows/wintest.yml
================================================
name: WinTest
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '45 03 * * 6'
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/setup-ruby@v1
- uses: microsoft/setup-msbuild@v1.1
- uses: seanmiddleditch/gha-setup-vsdevenv@master
- name: Download source
uses: actions/checkout@v2
- name: Install Crystal
uses: crystal-lang/install-crystal@v1
with:
crystal: nightly
- name: Build shard
run: rake build_shard
- name: Test directories
run: dir build/mruby/lib
- name: Test script
run: crystal build test.cr
- name: Run script
run: ./test
================================================
FILE: .gitignore
================================================
build/
utility/mruby_build_config.rb.lock
third_party/
docs/
examples/bytecode_test.mrb
.vscode/
.crystal/
================================================
FILE: .gitmodules
================================================
================================================
FILE: Changelog.md
================================================
# Changelog
## Releases
### Version 1.1.1
NOTE: This version requires recompilation of mruby and the C files.
NOTE: This version will only work with mruby 3.3.0 and above.
#### Usability
* Updated mruby to 3.3.0
* Updated MRI to 3.0.6
* Added error messages when Rake fails at installing
* Added error messages when mruby/MRI is missing
#### Bugfixes
* Fixed Regex wrapper warnings when running Anyolite with newer Crystal versions
* Fixed warning message when compiling glue files
* Fixed some annotations not working properly
### Version 1.1.0
NOTE: This version requires recompilation of mruby and the C files.
NOTE: This version will only work with mruby 3.2.0 and above.
#### Features
* Added direct bindings to `Regex` from Crystal (`Regexp` in Ruby)
* Added option to use a separate Ruby interpreter
* Added safeguard to catch non-fatal Crystal exceptions and raise them as Ruby errors
* Added option to transform procs into bytecode
* Added function to disable external program execution
* Added interpreter depth counter
* Updated to mruby 3.2.0
* Updated to MRI 3.0.5
* Added backtrace function for mruby
* Added option to fully protect Crystal values from the Ruby GC
* Added standalone wrappers for class properties
#### Usability
* Updated mruby config file to use `libucrt` instead of `msvcrt`
* Discarded old and problematic Regex mruby gem
* Made arguments for `Anyolite.call_rb_method_of_object` optional
* Added automatic conversion from Ruby class names to class objects in method callers
* Improved testing script
* Added check for `RbValue` and `RbRef` to some macro methods
* Added ability to pass Ruby blocks via Crystal to Ruby function calls
* Added some internal methods to mruby to handle fibers
* Added some exception checking methods
* Added methods to check reference table size
* Removed now optional `Dir` gem from default build config
* Removed need for `use_general_object_format_chars` flag for MRI
* Added global option to use keyword args for optional arguments only
#### Bugfixes
* Fixed error when running `shards install` on Windows
* Fixed compilation warning messages for Windows
* Fixed problems with Regexes due to PCRE conflicts
* Fixed problems with Anyolite on Crystal 1.5.1
* Unspecified arguments now always correctly throw warnings instead of confusing errors
* Fixed compiletime error when casting to `Char`
* Fixed errors when passing certain name arguments to Macros for calling Ruby functions
* Fixed `Anyolite.call_rb_method_of_object` argument `args` not being optional
* Fixed linker error due to typo in mruby bindings for block functions
* Fixed crash when casting Ruby numbers into objects in some cases
* Fixed script lines not printing exceptions
* Fixed memory leak when calling Ruby scripts and script lines
* Updated tests to account for reworked `inspect` method for enums
* Fixed errors when building Anyolite in a path with spaces
* Fixed problems on Linux when `LD` is set, but `gcc` should compile
* Fixed Crystal functions for accessing global variables in mruby
* Fixed Anyolite to allow enums with types different than `Int32`
### Version 1.0.0
This release marks the first full release of Anyolite, mostly
focussed on code quality, specs and bugfixes.
#### Features
* `Anyolite.eval` can be used to get return values from script lines
* Added check methods for Ruby references
#### Breaking changes
* Compacted most macro function options into hashes
* Adds default `inspect` and `to_s` methods to wrapped enums automatically
* Updated mruby to 3.1.0 and MRI to 3.0.4
* Config files now require `rb_minor` argument for MRI specifically
#### Security
* Closing an interpreter will now correctly clean class and type caches
* Fixed segmentation fault when overwriting Crystal content of a class
* Changed block cache to a stack to avoid overwriting it
#### Usability
* Completed all wiki entries
* Unit tests in test script
* Converted macro body variables to fresh variables wherever possible
* More compatibility between methods accepting `RbRef` and `RbValue`
#### Bugfixes
* Fixed error when passing blocks to certain method types
* Methods `inspect`, `to_s` and `hash` will now correctly respond to annotations
* Fixed UTF-8 problems in MRI tests
* Fixed crash on returning a `RbRef`
### Version 0.17.0
#### Features
* Added annotation to ignore class ancestors
#### Breaking changes
* Renamed `master` branch to `main`
* Changed internal representation of wrapped pointers
* Methods named `==` with no type specification will now return `false` instead of an error if types are incompatible
#### Usability
* Private ancestors will now be ignored automatically
### Version 0.16.1
#### Bugfixes
* Fixed typo in keyword module function wrappers
### Version 0.16.0
#### Features
* Added annotation `DefaultOptionalArgsToKeywordArgs`
* Added option to include bytecode as an array at compiletime
* Added environment variable for changing the mruby config path
#### Usability
* Added more debug information
#### Bugfixes
* Fixed argument error for block methods without arguments
* Fixed build error on Windows while running Github Actions
### Version 0.15.0
#### Features
* Methods for undefining Ruby methods
#### Breaking changes
* Excluding copy methods manually will undefine them from Ruby
* Checks for overflow when casting numbers
#### Usability
* Anyolite now respects exclusions of `dup` and `clone`
* Instance method exclude annotations on classes or modules will exclude them from all inheriting classes
* Include annotations can reverse global exclusions
#### Bugfixes
* Ruby exceptions instead of Crystal exceptions for casting overflows
* Casting to `Number` in mruby produced wrong values
### Version 0.14.0
#### Features
* Support for copying wrapped objects
* Ruby classes and modules can be obtained by name
#### Breaking changes
* All classes and structs automatically wrap the Crystal `dup` function as a copy constructor
* Updates in C glue functions
### Version 0.13.2
#### Features
* Windows support for the default mruby implementation
#### Usability
* CI for MRI (on Linux)
#### Bugfixes
* Fixed macro error for MRI
### Version 0.13.1
#### Bugfixes
* Fixed documentation
### Version 0.13.0
#### Features
* Full MRI Ruby as alternative implementation
* AnyolitePointer helper class for accessing pointers
* Infrastructure to convert script files into bytecode at runtime and compiletime
* Support for setting and getting instance, class and global variables from Crystal
#### Breaking changes
* Changed `RClass*` to `RClassPtr` to allow compatibility with MRI
* Reorganized some macros
* Changed directory structure
* Several code changes for compatibility with MRI
* Block variables for functions definitions have now an underscore in front of them
#### Usability
* Option for defaulting to usage of RbValue as data container for regular arguments
* Alternate build paths are now passed to Anyolite via the environment variable `ANYOLITE_BUILD_PATH`
#### Security
* Error messages for problems when loading scripts or bytecode files
#### Bugfixes
* Alternate build paths are not recognized properly in implementation files
* Fixed typo in name of `rb_str_to_cstr`
* Fixed inconsistent usage of `rb` in many functions
### Version 0.12.0
#### Features
* Automatic wrapping of inherited methods from all non-trivial ancestors
* Direct methods for Ruby error messages
* Usage of `self` as argument type is now allowed
* Option to default to regular args for an entire class
#### Breaking changes
* Renamed `wrap_superclass` to `connect_to_superclass` for clarity
* Excluded wrapping of `dup` and `clone` methods
#### Usability
* Better handling for abstract classes
* Correct handling of `inspect`, `to_s` and `hash` methods
* Enum class method `parse?` is now wrapped automatically
* Better error messages for invalid data pointers
* Default exclusion of unwrappable `<=` class methods for inherited classes
* More consistent debug information
* Error message when trying to wrap slices (for now)
* Added default equality method for structs and enums
#### Bugfixes
* Argument specialization was not possible for operator methods
* Fixed class method exclusions not being recognized
* Fixed config file parsing
* Fixed generic argument parsing for regular arguments
* Fixed error when converting some generics with default arguments
* Default arguments for numeric regular arguments were not processed correctly
* Fixed error when using unions in the style of `Bool?` at some points
### Version 0.11.1
#### Usability
* `RbRef` values can now be used as argument type
* Class inheritance wrapping can be disabled
* Operator methods take arguments using the `ForceKeywordArg` annotations
#### Bugfixes
* Boolean operator methods with default arguments could not be wrapped correctly
* Some wrappers had undocumented options
### Version 0.11.0
#### Features
* Superclass hierarchies will be transferred to Ruby
* Wrapping will skip classes if their superclass was not yet wrapped
* `Anyolite.wrap` will run multiple tries to ensure superclasses being wrapped first
* Classes will only be wrapped twice with `overwrite: true` option
* Objects may check whether they are created in mruby
* Ability to call mruby methods for mruby objects from Crystal by their name
* Ability to call mruby class and module methods from Crystal
* Macros to get the Ruby equivalents of modules and classes
* Checks for Ruby method availability from within Crystal
* Caching of RbValues in the reference table to avoid duplicate objects
* Storing of pure Ruby objects in GC-safe containers
* Annotations to enable obtaining Ruby block arguments
* A method to call contained Ruby procs from their containers in Crystal
#### Breaking changes
* Reference table now has a reference to the interpreter
* Interpreter and reference table operate together now
* Reference table system was reworked completely
#### Usability
* Updated documentation to new features from 0.10.0 and 0.11.0
* If nil is expected, cast everything to it without exceptions
* Simplified internal object casting
### Version 0.10.0
#### Features
* Support for block arguments
* Support for array arguments
* Support for hash arguments
* Support for symbols, arrays and hashes as returned values
* Support for chars
* Experimental (unsafe) casting of pointers to integers and back
#### Breaking changes
* Rename `convert_arg` to `convert_regular_arg`
* Rename `convert_keyword_arg` to `convert_from_ruby_to_crystal`
* Rename `convert_resolved_arg` to `resolve_regular_arg`
* Rename `convert_resolved_keyword_arg` to `resolve_from_ruby_to_crystal`
#### Usability
* Better error messages when casting incompatible values
* Added dummy argument parsing to convert type calls into actual types
* More intelligent conversions (Char <-> String, Int -> Float, Symbol -> String)
#### Bugfixes
* Fixed reference table throwing an error when increasing counter
* Call rb_finalize only if reference counter is going to be 0
* Fixed union type parsing
* Removed possible error when casting unions
### Version 0.9.1
#### Usability
* Allow for a wrapped function to return nil by default
#### Bugfixes
* Fixed broken documentation
### Version 0.9.0
#### Features
* Additional compatibility layer between Anyolite and mruby
* More configuration options
#### Breaking changes
* Renamed `MrbWrap` to `Anyolite`
* Renamed `MrbMacro` to `Anyolite::Macro`
* Renamed `mrb` and `mruby` in the code to `rb` and `ruby`
* Reworked configurations for the Rakefile into a class
* Dropped support for mruby 2
#### Safety
* Warning message when a reference table with values is reset
* Added pedantic setting for reference table (default)
* More reliable internal checks for union arguments
#### Usability
* Split macro source file into smaller parts
* Update documentation to new code
#### Bugfixes
* Enums are now correctly tracked in the reference table
### Version 0.8.1
#### Usability
* Explicitly exclude pointers
* Explicitly exclude procs
* Added recompilation options for the Rakefile
#### Bugfixes
* Fixed exception for class methods with empty regular argument list
* Allow operator methods for class and module methods
* Fixed path resolution for types starting with `::`
* Resolve generics as arguments for generics properly
* Fix broken floats in mruby
### Version 0.8.0
#### Features
* Uses mruby 3.0.0 by default
#### Breaking changes
* Compatibility with mruby 2.1.2 requires additional flag
#### Usability
* Casting methods are more compatible between mruby versions
### Version 0.7.0
#### Features
* Support for wrapping generics using annotations
* Non-keyword arguments allow for union and generic type arguments
* Annotation for non-keyword arguments accepts number as optional argument
#### Breaking changes
* Non-keyword arguments need to be specialized explicitly
* More consistent wrapping of operator methods
#### Usability
* More helpful error messages when path resolution fails
#### Bugfixes
* Specialization to new arguments did not allow non-keyword annotations
* Correct wrapping of most aliased types
* Methods with non-letter-symbols could not be wrapped
* Default arguments with colons were wrongly assumed to be keywords
* Enabled support for regular string argument with default values
* Fixed incomplete resolution of paths
### Version 0.6.1
#### Bugfixes
* Non-public constructor methods could not be wrapped
### Version 0.6.0
#### Features
* Wrappers for unions
* Wrappers for nilable objects
#### Breaking changes
* Wrapping of specific functions has a more consistent syntax using Arrays instead of Hashes
#### Safety
* More useful compiletime errors for macros
* More information when encountering type casting errors
* Use Array(TypeDeclaration) instead of Hash for keywords in internal methods
#### Usability
* Cleaned up some code fragments
#### Bugfixes
* Wrapped struct objects were immutable
### Version 0.5.0
#### Features
* Support for enums
* Ability to rename classes and modules
#### Usability
* Empty argument list for specialization can be specified with nil
* Exclusion message for mruby methods, finalize and to_unsafe
* Exclusion of non-public methods
* Exclusion of to_unsafe
* Non-fatal runtime errors are triggered in mruby instead of Crystal
#### Bugfixes
* Proper resolution of class and module hierarchies
### Version 0.4.1
#### Usability
* Method names in annotations can be given as strings
* More and better verbose information for wrapping
#### Bugfixes
* Setters can be excluded correctly
* Manually wrapped properties work correctly now
* Correct handling of generic function arguments like Int, Number or Float
### Version 0.4.0
#### Features
* Easier wrapping of classes and all of their methods and constants
* Annotation to exclude functions from wrapping
* Annotation to specialize functions for wrapping
* Annotation to rename wrapped functions
* Full wrapping of module and class hierarchies
#### Breaking changes
* Function names with operators do not include the operator into the ruby name anymore
* Unified module and class cache
#### Usability
* Documentation updates for the new wrapping routines
* Functions with only an operator in their name can now be wrapped using `MrbWrap::Empty`
#### Bugfixes
* Nested classes and modules can now be wrapped reliably
### Version 0.3.0
#### Features
* Crystal structs are wrapped using wrapper objects
#### Breaking changes
* Struct hash values as object ID replacements are obsolete
* Option hash for reference table instead of flags
* Consistent naming for mruby hooks
#### Safety
* Structs with equal hash values do not interfere anymore
#### Usability
* MrbModule instances and Crystal modules can both be used in wrapper methods
### Version 0.2.3
#### Usability
* More options for adjusting reference table
#### Bugfixes
* Fixed reference counter not increasing
### Version 0.2.2
#### Usability
* Added more debugging methods
* Allowed for custom object IDs by defining `mruby_object_id` for a class
#### Bugfixes
* Fixed problems with struct wrapping
### Version 0.2.1
#### Usability
* Operator suffixes as general optional argument for MrbWrap functions
* Option to inspect reference table
* Reference counting in reference table
* Reference table can be cleared
#### Bugfixes
* Fixed structs not being able to be wrapped
* Fixed example in documentation
* Fixed memory leak when returning nontrivial objects in mruby
* Removed constructor limitations for types being able to be used as return values
### Version 0.2.0
#### Features
* Keyword argument support
* Support for optional keywords
* Casting from MrbValue objects to closest Crystal values
* Option to use a JSON config file
#### Breaking changes
* Optional arguments are passed using tuples instead of `MrbWrap::Opt`
#### Safety
* Class checks for arguments
* Checks for correct keyword classes
* Module cache analogous to the class cache
#### Usability
* Simplified some macro functions considerably
* Arguments can be specified consistently as arrays or standalone
* Documentation builds only for releases
* Uniform system for passing optional arguments
* Updated examples and documentation for keyword support
#### Bugfixes
* Fixed erros when naming MrbState instances anything other than 'mrb'
### Version 0.1.1
#### Safety
* Added safeguards for reference table access
#### Bugfixes
* Fixed mruby function return values not being cached
* Fixed minor documentation errors
### Version 0.1.0
#### Features
* Basic structure
* Ubuntu support
* Wrappers for classes
* Wrappers for modules
* Support for classes in modules
* Wrappers for properties
* Wrappers for instance methods
* Wrappers for module and class methods
* Wrappers for constants
* Optional values for simple argument types
* Crystal GC respects the mruby GC
* Hooks for mruby object creation and deletion
* Simple examples
* Build tests
* Basic documentation
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Anyolite
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Anyolite
Anyolite is a Crystal shard which adds a fully functional mruby (or even regular Ruby) interpreter to Crystal.




# Description
Anyolite allows for wrapping Crystal classes and functions into Ruby with little effort.
This way, Ruby can be used as a scripting language to Crystal projects, with the major advantage of a similar syntax.
Useful links for an overview:
* Demo project: https://github.com/Anyolite/ScapoLite
* Wiki: https://github.com/Anyolite/anyolite/wiki
* Documentation: https://anyolite.github.io/anyolite
# Features
* Bindings to an mruby interpreter
* Near complete support to regular Ruby as alternative implementation (also known as MRI or CRuby)
* Wrapping of nearly arbitrary Crystal classes and methods to Ruby
* Easy syntax without unnecessary boilerplate code
* Simple system to prevent garbage collector conflicts
* Support for keyword arguments and default values
* Objects, arrays, hashes, structs, enums and unions as function arguments and return values are completely valid
* Ruby methods can be called at runtime as long as all their possible return value types are known
* Ruby closures can be handled as regular variables
* Methods and constants can be excluded, modified or renamed with annotations
* Options to compile scripts directly into the executable
# Prerequisites
You need to have the following programs installed (and in your PATH variable, if you are on Windows):
* Ruby (for building mruby)
* Rake (for building the whole project)
* Git (for downloading mruby)
* GCC or Microsoft Visual Studio 19 (for building the object files required for Anyolite, depending on your OS)
## Using regular Ruby instead of mruby
It is possible to use Anyolite with regular Ruby (MRI) instead of mruby. An instruction to install MRI can be found at [Using Ruby instead of mruby](https://github.com/Anyolite/anyolite/wiki/Using-Ruby-instead-of-mruby) in the wiki.
# Installing
Put this shard as a requirement into your shard.yml project file and then call
```bash
shards install
```
from a terminal.
Alternatively, you can clone this repository into the lib folder of your project and run
```bash
rake build_shard
```
manually from a terminal or the MSVC Developer Console (on Windows) to install the shard without using the crystal shards program.
If you want to use other options for Anyolite, visit [Changing build configurations](https://github.com/Anyolite/anyolite/wiki/Changing-build-configurations) in the wiki.
# How to use
Imagine a Crystal class for a really bad RPG:
```crystal
module RPGTest
class Entity
property hp : Int32
def initialize(@hp : Int32)
end
def damage(diff : Int32)
@hp -= diff
end
def yell(sound : String, loud : Bool = false)
if loud
puts "Entity yelled: #{sound.upcase}"
else
puts "Entity yelled: #{sound}"
end
end
def absorb_hp_from(other : Entity)
@hp += other.hp
other.hp = 0
end
end
end
```
Now, you want to wrap this class in Ruby. All you need to do is to execute the following code in Crystal (current commit; see documentation page for the version of the latest release):
```crystal
require "anyolite"
Anyolite::RbInterpreter.create do |rb|
Anyolite.wrap(rb, RPGTest)
rb.load_script_from_file("examples/hp_example.rb")
end
```
Well, that's it already.
The last line in the block calls the following example script:
```ruby
a = RPGTest::Entity.new(hp: 20)
a.damage(diff: 13)
puts a.hp
b = RPGTest::Entity.new(hp: 10)
a.absorb_hp_from(other: b)
puts a.hp
puts b.hp
b.yell(sound: 'Ouch, you stole my HP!', loud: true)
a.yell(sound: 'Well, take better care of your public attributes!')
```
The example above gives a good overview over the things you can already do with Anyolite.
More features will be added in the future.
# Limitations
See [Limitations and solutions](https://github.com/Anyolite/anyolite/wiki/Limitations-and-solutions) in the Wiki section for a detailed list.
# Why this name?
https://en.wikipedia.org/wiki/Anyolite
In short, it is a rare variant of the crystalline mineral called zoisite, with ruby and other crystal shards (of pargasite) embedded.
The term 'anyoli' means 'green' in the Maasai language, thus naming 'anyolite'.
# Roadmap
## Upcoming releases
### Version 1.1.2
#### Bugfixes
* [X] Fixed broken documentation
* [X] Fixed pointers not working as regular arguments
### Version 2.0.0
IMPORTANT: Version 2.0.0 will introduce breaking changes to improve the general user experience.
This means that the build process and some parts of the API will change. There will be a dedicated
section in the Wiki for migrating from Anyolite 1 to Anyolite 2, so compatibility can easily be
restored.
The list of features for Anyolite 2 will expand over time, while development on Anyolite 1 continues
until Anyolite 2 is released.
#### Breaking changes
* [ ] Discard postinstall option in favor of more sophisticated build script
* [ ] Simplify building with custom options
* [ ] Set optional arguments being keywords for method as default
* [ ] Add option for using either dynamic or static linking
#### Usability
* [ ] Add Wiki section about migrating from Anyolite 1
* [ ] Add error messages when using wrong annotation syntax
### Later releases
* [ ] Automated generation of Ruby documentations for wrapped functions
* [ ] MRI support on Windows (does currently not work for some reason)
* [ ] MRI support for version 3.1.0 and up
* [ ] Support and continuous integration for Mac
* [ ] Support for bindings using `Data` and `Struct` from Ruby
* [ ] Multiple interpreters for mruby
* [ ] Add option to make script calls thread-safe
### Wishlist, entries might not be possible to implement
* [ ] Splat argument and/or arbitrary keyword passing
* [ ] Support for slices and bytes
* [ ] Classes as argument type
* [ ] Resolve context even in generic type union arguments
* [ ] General improvement of type resolution
* [ ] Bignum support
* [ ] Range support
================================================
FILE: Rakefile.rb
================================================
require 'fileutils'
require 'json'
def get_value(env_var_name, default)
ENV[env_var_name] ? ENV[env_var_name] : default
end
def determine_compiler
if ENV["VisualStudioVersion"] || ENV["VSINSTALLDIR"]
return :msvc
else
return :gcc
end
end
class AnyoliteConfig
OPTIONS = {
:implementation => "mruby",
:build_path => "build",
:rb_fork => "https://github.com/mruby/mruby",
:rb_release => "3.3.0",
:rb_minor => "3.3",
:rb_dir => "third_party",
:rb_config => "utility/mruby_build_config.rb",
:glue_dir => "glue/mruby",
:compiler => determine_compiler
}
OPTIONS.each_key{|option| attr_reader option}
def initialize
OPTIONS.each_key{|option| set_option(option, OPTIONS[option])}
end
def load(config_file)
if File.exist?(config_file) then
File.open(config_file, "r") do |f|
content = JSON.load(f)
OPTIONS.each_key {|option| read_option(content, option)}
end
else
puts "Anyolite config file at #{config_file} not found. Default settings are used."
end
end
def read_option(content, name)
read_value = content["ANYOLITE_#{name.to_s.upcase}"] || content[name.to_s]
set_option(name, read_value) if read_value
end
def set_option(name, value)
self.instance_variable_set("@#{name}".to_sym, value)
end
end
task :build_shard => [:load_config, :install_ruby, :build_ruby, :build_glue]
task :recompile_glue => [:load_config, :build_glue]
task :recompile => [:load_config, :build_ruby, :build_glue]
GLUE_FILES = ["return_functions", "data_helper", "script_helper", "error_helper"]
ANYOLITE_COMPILER = determine_compiler
$config = nil
task :load_config do
if !$config
$config = AnyoliteConfig.new
config_file = get_value("ANYOLITE_CONFIG_PATH", "config_anyolite.json")
$config.load(config_file)
end
end
task :reload_config do
$config = AnyoliteConfig.new
config_file = get_value("ANYOLITE_CONFIG_PATH", "config_anyolite.json")
$config.load(config_file)
end
task :install_ruby => [:load_config] do
unless $config.rb_fork == "___EXTERNAL___"
FileUtils.mkdir_p($config.build_path)
unless File.exist?("#{$config.rb_dir}/#{$config.implementation}/README.md")
system "git clone #{$config.rb_fork} --branch #{$config.rb_release} --recursive #{$config.rb_dir}/#{$config.implementation}"
end
end
end
# RUBY_OPT_DIR="C:/vcpkg/installed/x64-windows"
# NOTE: This will be relevant for MRI on Windows
task :build_ruby => [:load_config] do
unless $config.rb_fork == "___EXTERNAL___"
temp_path = Dir.pwd
temp_rb_config_path = get_value("ANYOLITE_RB_CONFIG_RELATIVE_PATH", Dir.pwd)
if $config.implementation == "mruby"
if ANYOLITE_COMPILER == :msvc
system "cd #{$config.rb_dir}/#{$config.implementation} & ruby minirake MRUBY_BUILD_DIR=\"#{temp_path}/#{$config.build_path}/#{$config.implementation}\" MRUBY_CONFIG=\"#{temp_rb_config_path}/#{$config.rb_config}\""
elsif ANYOLITE_COMPILER == :gcc
system "unset LD; cd #{$config.rb_dir}/#{$config.implementation}; ruby minirake MRUBY_BUILD_DIR=\"#{temp_path}/#{$config.build_path}/#{$config.implementation}\" MRUBY_CONFIG=\"#{temp_rb_config_path}/#{$config.rb_config}\""
else
system "cd #{$config.rb_dir}/#{$config.implementation}; ruby minirake MRUBY_BUILD_DIR=\"#{temp_path}/#{$config.build_path}/#{$config.implementation}\" MRUBY_CONFIG=\"#{temp_rb_config_path}/#{$config.rb_config}\""
end
elsif $config.implementation == "mri"
if ANYOLITE_COMPILER == :msvc
raise "MSVC compilation of MRI is not supported yet."
# TODO: Fix MRI on Windows
# FileUtils.cp_r("#{$config.rb_dir}/#{$config.implementation}", "#{temp_path}/#{$config.build_path}/src_#{$config.implementation}")
# FileUtils.cd "#{$config.build_path}/src_#{$config.implementation}"
# system "./win32/configure.bat --prefix=\"#{temp_path}/#{$config.build_path}/#{$config.implementation}\" --with-opt-dir=#{RUBY_OPT_DIR}"
# system "nmake incs"
# system "nmake extract-extlibs"
# system "nmake"
# system "nmake install"
elsif ANYOLITE_COMPILER == :gcc
system "cp -r #{$config.rb_dir}/#{$config.implementation} #{temp_path}/#{$config.build_path}/src_#{$config.implementation}"
system "cd #{$config.build_path}/src_#{$config.implementation}; ./autogen.sh"
system "cd #{$config.build_path}/src_#{$config.implementation}; ./configure --prefix=\"#{temp_path}/#{$config.build_path}/#{$config.implementation}\""
system "cd #{$config.build_path}/src_#{$config.implementation}; make"
system "cd #{$config.build_path}/src_#{$config.implementation}; make install"
else
end
else
raise "Invalid ruby implementation: #{$config.implementation}. Use either \"mruby\" or \"mri\"."
end
end
end
task :build_glue => [:load_config] do
FileUtils.mkdir_p($config.build_path + "/glue/#{$config.implementation}")
if $config.rb_fork == "___EXTERNAL___" && $config.rb_dir == "___EMPTY___"
puts "NOTE: No Ruby directory specified. Glue object files will not be built!"
else
if $config.implementation == "mruby"
if ANYOLITE_COMPILER == :msvc
GLUE_FILES.each do |name|
# NOTE: For later versions, add flag /MD for dynamic linking only
system "cl /I \"#{$config.rb_dir}/#{$config.implementation}/include\" /D MRB_INT64 /c \"#{$config.glue_dir}/#{name}.c\" /Fo\"#{$config.build_path}/glue/#{$config.implementation}/#{name}.obj\""
end
elsif ANYOLITE_COMPILER == :gcc
GLUE_FILES.each do |name|
system "cc -std=c99 -I\"#{$config.rb_dir}/#{$config.implementation}/include\" -DMRB_INT64 -c \"#{$config.glue_dir}/#{name}.c\" -o \"#{$config.build_path}/glue/#{$config.implementation}/#{name}.o\""
end
else
GLUE_FILES.each do |name|
system "#{$config.compiler.to_s} -std=c99 -I\"#{$config.rb_dir}/#{$config.implementation}/include\" -DMRB_INT64 -c \"#{$config.glue_dir}/#{name}.c\" -o \"#{$config.build_path}/glue/#{$config.implementation}/#{name}.o\""
end
end
elsif $config.implementation == "mri"
if ANYOLITE_COMPILER == :msvc
GLUE_FILES.each do |name|
system "cl /I \"#{$config.build_path}/#{$config.implementation}/include/ruby-#{$config.rb_minor}\" /I \"#{$config.build_path}/#{$config.implementation}/include/ruby-#{$config.rb_minor}/x64-mswin64_140\" /c \"#{$config.glue_dir}/#{name}.c\" /Fo\"#{$config.build_path}/glue/#{$config.implementation}/#{name}.obj\""
end
elsif ANYOLITE_COMPILER == :gcc
GLUE_FILES.each do |name|
system "cc -std=c99 -I\"#{$config.build_path}/#{$config.implementation}/include/ruby-#{$config.rb_minor}\" -I\"#{$config.build_path}/#{$config.implementation}/include/ruby-#{$config.rb_minor}/x86_64-linux\" -I\"#{$config.build_path}/#{$config.implementation}/include/ruby-#{$config.rb_minor}/aarch64-linux\" -c \"#{$config.glue_dir}/#{name}.c\" -o \"#{$config.build_path}/glue/#{$config.implementation}/#{name}.o\""
end
else
GLUE_FILES.each do |name|
system "#{$config.compiler.to_s} -std=c99 -I\"#{$config.rb_dir}/#{$config.implementation}/include\" -c \"#{$config.glue_dir}/#{name}.c\" -o \"#{$config.build_path}/glue/#{$config.implementation}/#{name}.o\""
end
end
else
raise "Invalid ruby implementation: #{$config.implementation}. Use either \"mruby\" or \"mri\"."
end
end
end
task :clean => [:load_config] do
temp_path = get_value("ANYOLITE_RB_CONFIG_RELATIVE_PATH", Dir.pwd)
FileUtils.remove_dir($config.rb_dir, force: true) unless $config.rb_fork == "___EXTERNAL___"
FileUtils.remove_dir($config.build_path, force: true)
FileUtils.remove_entry(temp_path + "/" + $config.rb_config + ".lock", force: true)
end
================================================
FILE: anyolite.cr
================================================
require "./src/Main.cr"
================================================
FILE: config_files/anyolite_config_external_ruby.json
================================================
{
"implementation": "mri",
"build_path": "build",
"rb_fork": "___EXTERNAL___",
"rb_release": "",
"rb_minor": "",
"rb_dir": "___EMPTY___",
"rb_config": "",
"glue_dir": "glue/mri"
}
================================================
FILE: config_files/anyolite_config_mri.json
================================================
{
"implementation": "mri",
"build_path": "build",
"rb_fork": "https://github.com/ruby/ruby",
"rb_release": "v3_0_6",
"rb_minor": "3.0.0",
"rb_dir": "third_party",
"rb_config": "",
"glue_dir": "glue/mri"
}
================================================
FILE: config_files/anyolite_config_mruby.json
================================================
{
"implementation": "mruby",
"build_path": "build",
"rb_fork": "https://github.com/mruby/mruby",
"rb_release": "3.3.0",
"rb_minor": "3.3.0",
"rb_dir": "third_party",
"rb_config": "utility/mruby_build_config.rb",
"glue_dir": "glue/mruby"
}
================================================
FILE: examples/bytecode_test.rb
================================================
class BytecodeTestClass
def initialize(str)
@str = str
end
def do_test(some_number)
ret_array = [@str]
some_number.times do |i|
ret_array.push i + 1
end
ret_array
end
end
puts "This is a bytecode test file"
================================================
FILE: examples/hp_example.rb
================================================
a = RPGTest::Entity.new(hp: 20)
a.damage(diff: 13)
puts a.hp
b = RPGTest::Entity.new(hp: 10)
a.absorb_hp_from(other: b)
puts a.hp
puts b.hp
b.yell(sound: 'Ouch, you stole my HP!', loud: true)
a.yell(sound: 'Well, take better care of your public attributes!')
================================================
FILE: examples/mri_test.rb
================================================
require_relative "./hp_example.rb"
require_relative "./test_framework.rb"
require_relative "./test.rb"
================================================
FILE: examples/test.rb
================================================
begin
puts "Initiate testing..."
TestFramework.init
start_time = Time.now
# Testing bytecode if enabled
TestFramework.check(test_no: 1, should_be: ["Hello world", 1, 2, 3, 4, 5]) do
BytecodeTestClass.new("Hello world").do_test(5)
end
# Testing attributes
TestFramework.check(test_no: 2, should_be: 5) do
a = TestModule::Test.new(x: 5)
a.x
end
# Testing instance methods and returned arrays
TestFramework.check(test_no: 3, should_be: [19, false, "Example string", 0.4, 5 + 19]) do
a = TestModule::Test.new(x: 5)
a.test(int: 19, bool: false, str: 'Example string')
end
# Testing attribute changes using an instance method
TestFramework.check(test_no: 4, should_be: 5 + 19) do
a = TestModule::Test.new(x: 5)
a.test(int: 19, bool: false, str: 'Example string')
a.x
end
# Testing instance methods with keyword arguments
TestFramework.check(test_no: 5, should_be: [19, false, "Example string", 0.5, 5 + 19]) do
a = TestModule::Test.new(x: 5)
a.test(int: 19, bool: false, str: 'Example string', float: 0.5)
end
# Testing module methods
TestFramework.check(test_no: 6, should_be: ["Hello", 3]) do
TestModule.test_method(int: 3, str: "Hello")
end
# Testing custom keyword arguments implemented using Anyolite
TestFramework.check(test_no: 7, should_be: ["World", 19]) do
TestModule.test_method(str: "World")
end
# Testing attribute setters
TestFramework.check(test_no: 8, should_be: 123) do
a = TestModule::Test.new(x: 5)
a.x = 113
a.x += 10
a.x
end
# Testing struct attributes
TestFramework.check(test_no: 9, should_be: [-123, -234]) do
ts = TestModule::TestStructRenamed.new
[ts.value, ts.test.x]
end
# Testing returned structs
TestFramework.check(test_no: 10, should_be: [777, 999]) do
some_struct = TestModule::Test.give_me_a_struct
[some_struct.value, some_struct.test.x]
end
# Testing structs as arguments
TestFramework.check(test_no: 11, should_be: ["5 -123 -234", "5 777 999"]) do
a = TestModule::Test.new(x: 5)
ts = TestModule::TestStructRenamed.new
some_struct = TestModule::Test.give_me_a_struct
[a.output_together_with(str: ts), a.output_together_with(str: some_struct)]
end
# Testing class methods
TestFramework.check(test_no: 12, should_be: [2, -15]) do
TestModule::Test.reset_counter
a = TestModule::Test.new(x: 5)
b = TestModule::Test.new(x: 5)
[TestModule::Test.counter, TestModule::Test - 17]
end
# Testing module methods
TestFramework.check(test_no: 13, should_be: "Well, you can't just subtract #{13} from a module...") do
TestModule::Test::UnderTestRenamed::DeepUnderTest - 13
end
# Testing constants
TestFramework.check(test_no: 14, should_be: "Smile! 😊") do
TestModule::SOME_CONSTANT
end
# Testing operator methods
TestFramework.check(test_no: 15, should_be: 37) do
a = TestModule::Test.new(x: 5)
b = TestModule::Test.new(x: 32)
(a + b).x
end
# Testing longer keyword methods
TestFramework.check(test_no: 16, should_be: "str = Hi there, int = -121212, float = -0.313, stringkw = 💎, bool = true, other.x = 32") do
a = TestModule::Test.new(x: 5)
b = TestModule::Test.new(x: 32)
a.keyword_test(strvar: "Hi there", intvar: -121212, floatvar: -0.313, strvarkw: "💎", othervar: b)
end
# Testing constants in a more nested type tree
TestFramework.check(test_no: 17, should_be: "Hello") do
TestModule::Test::RUBY_CONSTANT
end
# Testing methods without keywords
TestFramework.check(test_no: 18, should_be: 120) do
TestModule::Test.without_keywords(12)
end
# Testing deeply nested type trees
TestFramework.check(test_no: 19, should_be: "This is a nested test") do
# The absolute, ultimate and ridiculously complicated nesting test
TestModule::Test::UnderTestRenamed::DeepUnderTest::VeryDeepUnderTest.new.nested_test
end
# Testing structs and enums
TestFramework.check(test_no: 20, should_be: ["DeepTestStruct", true, true, "Seven"]) do
struct_test_var = TestModule::Test::DeepTestStruct.new
enum_test_var = TestModule::Test::TestEnum::Seven
[struct_test_var.to_s, enum_test_var == TestModule::Test::TestEnum.new(7), enum_test_var != TestModule::Test::TestEnum.new(5), enum_test_var.inspect.split("::")[-1]]
end
# Testing specialized method
TestFramework.check(test_no: 21, should_be: "No args") do
a = TestModule::Test.new(x: 5)
a.method_with_various_args
end
# Testing union arguments
TestFramework.check(test_no: 22, should_be: ["\"Test String\"", "12345.0", "true", "nil", "0.6", "A test object with x = 32", "Four", "A generic test", "\"Default String\""]) do
a = TestModule::Test.new(x: 5)
b = TestModule::Test.new(x: 32)
a_1 = a.overload_test(arg: "Test String")
a_2 = a.overload_test(arg: 12345)
a_3 = a.overload_test(arg: true)
a_4 = a.overload_test(arg: nil)
a_5 = a.overload_test(arg: 3.0 / 5.0)
a_6 = a.overload_test(arg: b)
a_7 = a.overload_test(arg: TestModule::Test::TestEnum::Four).split("::")[-1]
a_8 = a.overload_test(arg: TestModule::Test::GTIntInt.new(u: 1, v: 3))
a_9 = a.overload_test
[a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8, a_9]
end
# Testing nilable methods
TestFramework.check(test_no: 23, should_be: ["Received argument 123", "Received argument nil"]) do
a = TestModule::Test.new(x: 5)
[a.nilable_test(arg: 123), a.nilable_test(arg: nil)]
end
# Testing struct attributes
TestFramework.check(test_no: 24, should_be: [-123, 4242]) do
test_struct_thingy = TestModule::TestStructRenamed.new
initial_value = test_struct_thingy.value
test_struct_thingy.value = 4242
modified_value = test_struct_thingy.value
[initial_value, modified_value]
end
# Testing enum values
TestFramework.check(test_no: 25, should_be: 5) do
a = TestModule::Test.new(x: 5)
a.returns_an_enum.value
end
# Testing random return types
TestFramework.check(test_no: 26, should_be: true) do
a = TestModule::Test.new(x: 5)
random_value = a.returns_something_random
puts "Either a string or an int (it's random!): #{random_value}"
(random_value == 3) || (random_value == "Hello")
end
# Testing generic types
TestFramework.check(test_no: 27, should_be: ["u1 = 3 of Int32, v1 = 5.5 of Float32.", "u1 = 3 of Int32, v1 = 9 of Int32."]) do
first = TestModule::Test::GTIntFloat.new(u: 1, v: 0.4).test(u1: 3, v1: 5.5)
second = TestModule::Test::GTIntInt.new(u: 7, v: 10).test(u1: 3, v1: 9)
[first, second]
end
# Testing generic type functions with generics as arguments
TestFramework.check(test_no: 28, should_be: "This has 1 and 10.0, the other has 2 and 5.0.") do
TestModule::Test::GTIntFloat.new(u: 1, v: 10.0).compare(other: TestModule::Test::GTIntFloat.new(u: 2, v: 5.0))
end
results = []
results.push "11 - 0.111 - 0.1 - Hello - 1"
results.push "22 - 0.222 - 0.2 - Hello - 32"
results.push "33 - 0.333 - 0.3 - 5 - 2"
results.push "44 - 0.444 - 0.4 - 32 - 32"
results.push "55 - 0.555 - 0.5 - true - 3"
results.push "66 - 0.666 - 0.6 - false - 32"
results.push "77 - 0.777 - 0.7 - Three - 4"
results.push "88 - 0.888 - 0.8 - Four - 32"
results.push "99 - 0.999 - 0.9 - Cookies - 5"
results.push "100 - 0.0 - 1.0 - Cookies - 32"
results.push "0 - 0.0 - 0.0 - SomeModule::Test::GenericTest(Int32, Int32)(@u=1, @v=1) - 32"
# Testing unions as keyword arguments
TestFramework.check(test_no: 29, should_be: results) do
a = TestModule::Test.new(x: 5)
b = TestModule::Test.new(x: 32)
a_1 = a.complicated_method(11, 0.111, 0.1, "Hello", arg_opt_2: 1)
a_2 = a.complicated_method(22, 0.222, 0.2, "Hello")
a_3 = a.complicated_method(33, 0.333, 0.3, a, arg_opt_2: 2)
a_4 = a.complicated_method(44, 0.444, 0.4, b)
a_5 = a.complicated_method(55, 0.555, 0.5, true, arg_opt_2: 3)
a_6 = a.complicated_method(66, 0.666, 0.6, false)
a_7 = a.complicated_method(77, 0.777, 0.7, TestModule::Test::TestEnum::Three, arg_opt_2: 4)
a_8 = a.complicated_method(88, 0.888, 0.8, TestModule::Test::TestEnum::Four)
a_9 = a.complicated_method(99, 0.999, 0.9, arg_opt_2: 5)
a_10 = a.complicated_method(100, 0.000, 1.0)
a_11 = a.complicated_method(0, 0.0, 0.0, TestModule::Test::GTIntInt.new(u: 1, v: 1))
[a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8, a_9, a_10, a_11]
end
# Testing unicode strings
TestFramework.check(test_no: 30, should_be: "😀 for number 1234567") do
a = TestModule::Test.new(x: 5)
# TODO: For some reason, this does not work in MRI
a.inside_mri? ? "😀 for number 1234567" : a.happy😀emoji😀test😀😀😀(arg: 1234567)
end
# Testing equality methods
TestFramework.check(test_no: 31, should_be: [false, true, false, true, false]) do
a = TestModule::Test.new(x: 5)
b = TestModule::Test.new(x: 32)
same_as_a = TestModule::Test.new(x: a.x)
[a == b, a == same_as_a, a == 10, a == TestModule::Test::TestChild.new(x: a.x), a == TestModule::Test::TestChild.new(x: a.x + 1)]
end
# Testing UInt8 and nil-returning methods
TestFramework.check(test_no: 32, should_be: ["123", NilClass]) do
a = TestModule::Test.new(x: 5)
[a.uint_test(arg: 123), a.noreturn_test.class]
end
# Testing indirect overloads
TestFramework.check(test_no: 33, should_be: ["This was an int", "This was a string"]) do
a = TestModule::Test.new(x: 5)
[a.overload_cheat_test(12334), a.overload_cheat_test("Something")]
end
# Testing classes in modules
TestFramework.check(test_no: 34, should_be: true) do
s = TestModule::Bla.new
s != nil
end
# Testing block calls and storage
TestFramework.check(test_no: 35, should_be: [5, 5, "2010", 2005, 1005, "4010", 2005, 2005, false]) do
a = TestModule::Test.new(x: 5)
before_block_storage = a.x
new_carrier = nil
a.block_store_test do |value|
new_carrier = value
value.x += 1000
value.x * 2
end
carrier = nil
after_block_storage = a.x
result = a.block_test do |value|
carrier = value
value.x += 1000
value.x * 2
end
after_block_test = a.x
block_store_call_result = a.block_store_call
after_block_call = a.x
carrier_result = new_carrier ? new_carrier.x : new_carrier
does_this_have_a_block = a.block_store_test
[before_block_storage, after_block_storage, result, carrier.x, after_block_test, block_store_call_result, after_block_call, carrier_result, does_this_have_a_block]
end
# Testing block methods with yield
TestFramework.check(test_no: 36, should_be: ["1 2", "3", "They said: Hello, There"]) do
a = TestModule::Test.new(x: 5)
other_result = a.block_test_2 do |x, y|
"#{x} #{y}"
end
other_result_2 = a.block_test_2 do |x, y|
x + y
end
other_result_3 = TestModule::Test.block_test_3(arg: "They said") do |x, y|
"#{x}, #{y}"
end
[other_result, other_result_2, other_result_3]
end
# Testing arrays
TestFramework.check(test_no: 37, should_be: [[2, 4, "HelloHello"], ["Not an array"]]) do
a = TestModule::Test.new(x: 5)
[a.array_test(arg: [1, 2, "Hello"]), a.array_test(arg: "Not an array")]
end
# Testing hashes
TestFramework.check(test_no: 38, should_be: {:hello => "Nice", :world => "to see you!", 3 => 15, "test😊" => :very_long_test_symbol}) do
a = TestModule::Test.new(x: 5)
a.hash_return_test
end
# Testing unspecified floats
TestFramework.check(test_no: 39, should_be: 3.0) do
a = TestModule::Test.new(x: 5)
a.float_test(arg: 3)
end
# Testing chars
TestFramework.check(test_no: 40, should_be: "🌈") do
a = TestModule::Test.new(x: 5)
a.char_test(arg: "🌈")
end
results = []
results.push "World"
results.push TestModule::Test.new(x: 32)
results.push "A number"
results.push TestModule::Test.new(x: 5)
results.push "The symbol should become a string"
results.push TestModule::Test::TestEnum::Three
# Testing hash symbols
TestFramework.check(test_no: 41, should_be: results) do
a = TestModule::Test.new(x: 5)
b = TestModule::Test.new(x: 32)
test_hash = {"Hello" => "World", "Test" => b, 12334 => "A number", 999 => a, :test_symbol => "The symbol should become a string", :enum => TestModule::Test::TestEnum::Three}
result = a.hash_test(arg: test_hash).each do |key, value|
puts "Ruby: #{key} -> #{value.is_a?(TestModule::Test) ? "Test with x = #{value.x}" : value.is_a?(TestModule::Test::TestEnum) ? value.value : value}"
end
[result["Hello"], result["Test"], result[12334], result[999], result[":test_symbol"], result[":enum"]]
end
# Testing pointers
TestFramework.check(test_no: 42, should_be: [1002, 1005, 1005, AnyolitePointer]) do
a = TestModule::Test.new(x: 5)
a.x = 1001
ptr = a.ptr_return_test
a_1 = a.ptr_arg_test(arg: ptr)
a_2 = a.ptr_star_arg_test(arg: ptr)
a_3 = a.test_int_or_ptr(arg: ptr)
[a_1, a_2, a_3, ptr.class]
end
class InheritedTest < TestModule::Test
def initialize(x: 0, z: "")
super(x: x)
@y = x * 2
@z = z
end
end
class InheritedContentTest < TestModule::Test::ContentTest
def initialize(content, another_content)
super(content: content)
@another_content = another_content
end
def overloaded_content
content
end
end
# Testing inheriting wrapped classes
TestFramework.check(test_no: 43, should_be: [789789, 3, 4]) do
it = InheritedContentTest.new([InheritedTest.new(x: 123456, z: "Hello"), InheritedTest.new(x: 789789, z: "World")], InheritedTest.new(x: 111, z: "Nice day"))
mt = TestModule::Test::NewContentTest.new(content: [TestModule::Test::TestChild.new(x: 1), InheritedTest.new(x: 2, z: "2")], more_content: [InheritedTest.new(x: 3, z: "3"), InheritedTest.new(x: 4, z: "4")])
# NOTE: This works, but only for methods directly inherited from Test.
# Overloading is therefore possible, but the other content will be cut.
# Overwriting the original content will result in errors.
[it.overloaded_content[1].x, mt.more_content[0].x, mt.more_content[1].x]
end
module TestModule
class Test
def method_only_in_ruby(str, int)
"#{str} #{int}"
end
def self.class_method_in_ruby(str, int)
"Class method with args #{str} and #{int}"
end
end
end
# Testing method calls from Crystal
TestFramework.check(test_no: 44, should_be: ["Hello 3", "Class method with args World and 4", true, false, true]) do
a = TestModule::Test.new(x: 5)
[a.call_test, a.class_call_test, a.response_test('method_only_in_ruby'), a.response_test('method_not_in_ruby'), a.class_response_test('class_method_in_ruby')]
end
# Testing nested checks for Crystal and Ruby
TestFramework.check(test_no: 45, should_be: "Do I have an identity crisis? Yes.") do
a = TestModule::Test.new(x: 5)
# Try to explain in one sentence what that codeline does without losing your brain to the outer gods
"Do I have an identity crisis? #{a.why_would_you_do_this?('am_i_in_ruby?') ? 'Yes' : 'No'}."
end
# Testing operator methods with boolean arguments
TestFramework.check(test_no: 46, should_be: ["true", "true", "false"]) do
a = TestModule::Test.new(x: 5)
[a.bool_setter_test?, a.bool_setter_test?(true), a.bool_setter_test?(false)]
end
# Testing operator methods with keywords
TestFramework.check(test_no: 47, should_be: "6.0") do
a = TestModule::Test.new(x: 5)
a.keyword_operator_arg?(arg: 5)
end
# Testing Ruby value references in Crystal
TestFramework.check(test_no: 48, should_be: "Hello and a reference with 1223 were given.") do
a = TestModule::Test.new(x: 5)
a.ref_test(str: "Hello", ref: 1223)
end
# Testing struct constructors with custom default values
TestFramework.check(test_no: 49, should_be: [5678, 0.5678, "Default", 89, 0.89, "Something"]) do
a_1 = TestModule::Test::ValueStruct.new.i
a_2 = TestModule::Test::ValueStruct.new.f
a_3 = TestModule::Test::ValueStruct.new.s
a_4 = TestModule::Test::ValueStruct.new(89, 0.89, "Something").i
a_5 = TestModule::Test::ValueStruct.new(89, 0.89, "Something").f
a_6 = TestModule::Test::ValueStruct.new(89, 0.89, "Something").s
[a_1, a_2, a_3, a_4, a_5, a_6]
end
# Testing inspect methods
TestFramework.check(test_no: 50, should_be: "x is 5") do
a = TestModule::Test.new(x: 5)
a.inspect
end
# Testing self as argument type
TestFramework.check(test_no: 51, should_be: "Value is 1 and 2.3") do
TestModule::Test::GTIntFloat.self_test(other: TestModule::Test::GTIntFloat.new(u: 1, v: 2.3))
end
# Testing equality methods of enums and structs
TestFramework.check(test_no: 52, should_be: [true, true]) do
[(TestModule::Test::TestEnum.new(3) == TestModule::Test::TestEnum.new(3)), (TestModule::TestStructRenamed.new == TestModule::TestStructRenamed.new)]
end
# Testing inherited contents and inspects
TestFramework.check(test_no: 53, should_be: ["[x is 5, x is 5]", "[x is 32, x is 5, x is 32]"]) do
a = TestModule::Test.new(x: 5)
b = TestModule::Test.new(x: 32)
inherited_content_test = TestModule::Test::NewContentTest.new(content: [a, a], more_content: [b, a, b])
[inherited_content_test.content.inspect, inherited_content_test.more_content.inspect]
end
# Testing custom hashes
TestFramework.check(test_no: 54, should_be: [213345, 213345]) do
a = TestModule::Test.new(x: 5)
b = TestModule::Test.new(x: 32)
[a.hash, b.hash]
end
# Testing access to instance variables
TestFramework.check(test_no: 55, should_be: [nil, 15667]) do
a = TestModule::Test.new(x: 5)
before = a.get_instance_variable(name: "hello")
a.set_instance_variable_to_int(name: "hello", value: 15667)
after = a.get_instance_variable(name: "hello")
[before, after]
end
# Testing Number arguments
TestFramework.check(test_no: 56, should_be: 1.3) do
a = TestModule::Test.new(x: 5)
a.num_test(1.3)
end
# Testing duplications
TestFramework.check(test_no: 57, should_be: [5, 8, 2, 3, 1, 0]) do
a = TestModule::Test.new(x: 5)
a_copy = a.dup
a_1 = a_copy.x
a_copy.x += 3
a.x -= 3
a_2 = a_copy.x
a_3 = a.x
enum_orig = TestModule::Test::TestEnum::Three
enum_copy = enum_orig.dup
a_4 = enum_copy.value
intfloat = TestModule::Test::GTIntFloat.new(u: 1, v: 2.3)
intfloat_copy = intfloat.dup
intfloat_copy.u -= 1
a_5 = intfloat.u
a_6 = intfloat_copy.u
[a_1, a_2, a_3, a_4, a_5, a_6]
end
# Testing regular expressions
TestFramework.check(test_no: 58, should_be: true) do
reg = /([\S]+) [\S]+/
reg.match?("Hello World")
end
# Testing regular expression match data
TestFramework.check(test_no: 59, should_be: "Hello") do
reg = /([\S]+) [\S]+/
reg.match("Hello World")[1]
end
# Testing giving Regex values to Crystal
TestFramework.check(test_no: 60, should_be: "Hello") do
a = TestModule::Test.new(x: 5)
a.check_some_regex(r: /([\S]+) [\S]+/, str: "Hello World")
end
# Testing getting Regex values from Crystal
TestFramework.check(test_no: 61, should_be: "Hello") do
a = TestModule::Test.new(x: 5)
a.give_some_regex.match("Hello World")[1]
end
# Testing passing a Ruby block to Crystal and then to Ruby again
TestFramework.check(test_no: 62, should_be: 11) do
a = TestModule::Test.new(x: 5)
a.pass_a_ruby_block_to_another_method do |value|
value.x + 6
end
end
TestFramework.check(test_no: 63, should_be: 268) do
TestModule.some_class_property += 10
TestModule.some_class_property
end
final_time = Time.now
puts "Tests done."
puts "Total time for Ruby test script: #{(final_time - start_time)} s"
rescue => ex
puts "\e[31m*** FAILURE: Tests failed because of exception:\e[0m"
puts ex.backtrace
raise ex
end
TestFramework.results(raise_if_failures: true)
================================================
FILE: examples/test_framework.rb
================================================
module TestFramework
def self.init
@@failed_tests = []
@@test_count = 0
end
def self.check(test_no: -1, should_be: nil, &block)
result = block.call
if result.is_a?(Array)
result.collect! {|x| x.is_a?(Float) ? x.round(5) : x}
end
if should_be.is_a?(Array)
should_be.collect! {|x| x.is_a?(Float) ? x.round(5) : x}
end
@@test_count += 1
if should_be == result
puts "\e[32m[Test #{test_no}] passed with result #{result.inspect}.\e[0m"
else
@@failed_tests.push test_no
puts "\e[31m*** FAILURE: [Test #{test_no}] was expected to return #{should_be.inspect}, but returned #{result.inspect}.\e[0m"
end
end
def self.results(raise_if_failures: false)
if @@failed_tests.empty?
puts "\e[32mA total number of #{@@test_count} tests was done without issues.\e[0m"
else
puts "\e[31m*** FAILURE: #{@@failed_tests.size} out of #{@@test_count} tests failed.\e[0m"
puts "\e[31m*** FAILURE: The following tests failed: #{@@failed_tests.inspect}.\e[0m"
if raise_if_failures
raise "One or multiple tests failed"
end
end
end
end
================================================
FILE: glue/mri/data_helper.c
================================================
#include <ruby.h>
extern VALUE rb_define_class_under_helper(void* rb, VALUE under, const char* name, VALUE superclass) {
rb_define_class_under(under, name, superclass);
}
extern VALUE rb_define_class_helper(void* rb, const char* name, VALUE superclass) {
rb_define_class(name, superclass);
}
extern VALUE rb_define_module_under_helper(void* rb, VALUE under, const char* name) {
rb_define_module_under(under, name);
}
extern VALUE rb_define_module_helper(void* rb, const char* name) {
rb_define_module(name);
}
extern VALUE rb_define_const_helper(void* rb, VALUE under, const char* name, VALUE value) {
rb_define_const(under, name, value);
}
extern void set_instance_tt_as_data(VALUE ruby_class) {
//! TODO: Is this function required?
//MRB_SET_INSTANCE_TT(ruby_class, MRB_TT_DATA);
}
extern bool rb_obj_is_kind_of_helper(void* rb, VALUE object, VALUE ruby_class) {
rb_obj_is_kind_of(object, ruby_class) == Qtrue ? true : false;
}
extern VALUE rb_obj_class_helper(void* rb, VALUE object) {
rb_obj_class(object);
}
extern const char* rb_class_name_helper(void* rb, VALUE ruby_class) {
VALUE class_name_value = rb_class_name(ruby_class);
rb_string_value_cstr(&class_name_value);
}
extern void* get_data_ptr(VALUE ruby_object) {
return DATA_PTR(ruby_object);
}
//! About the following method...
//! It is highly hacky and just modifies a newly creates Ruby object, but that is okay.
//! It tells the Ruby GC that this object is a pointer and how to free it.
//! Crystal owns the pointer, so Ruby does not have to do anything else besides calling dfree.
extern void set_data_ptr_and_type(VALUE ruby_object, void* data, struct rb_data_type_struct* data_type) {
DATA_PTR(ruby_object) = data;
RDATA(ruby_object)->basic.flags = T_DATA;
RDATA(ruby_object)->dmark = data_type->function.dmark;
RDATA(ruby_object)->dfree = data_type->function.dfree;
}
extern VALUE new_empty_object(void* rb, VALUE ruby_class, void* data_ptr, struct rb_data_type_struct* data_type) {
rb_data_object_wrap(ruby_class, data_ptr, data_type->function.dmark, data_type->function.dfree);
}
extern void rb_define_method_helper(void* rb, VALUE ruby_class, const char* name, VALUE (*func)(int argc, VALUE* argv, VALUE self), int aspec) {
rb_define_method(ruby_class, name, func, -1);
}
extern void rb_define_class_method_helper(void* rb, VALUE ruby_class, const char* name, VALUE (*func)(int argc, VALUE* argv, VALUE self), int aspec) {
rb_define_module_function(ruby_class, name, func, -1);
}
extern void rb_define_module_function_helper(void* rb, VALUE ruby_module, const char* name, VALUE (*func)(int argc, VALUE* argv, VALUE self), int aspec) {
rb_define_module_function(ruby_module, name, func, -1);
}
extern VALUE rb_inspect_helper(void* rb, VALUE value) {
rb_inspect(value);
}
extern VALUE rb_hash_new_helper(void* rb) {
rb_hash_new();
}
extern void rb_hash_set_helper(void* rb, VALUE hash, VALUE key, VALUE value) {
rb_hash_aset(hash, key, value);
}
extern VALUE rb_hash_get_helper(void* rb, VALUE hash, VALUE key) {
rb_hash_aref(hash, key);
}
extern VALUE rb_hash_keys_helper(void* rb, VALUE hash) {
//! NOTE: For some reason rb_hash_keys is not marked as extern, so for now this is a workaround
rb_funcall(hash, rb_intern("keys"), 0);
}
extern int rb_hash_size_helper(void* rb, VALUE hash) {
rb_hash_size(hash);
}
extern VALUE convert_to_rb_sym_helper(void* rb, const char* value) {
rb_intern(value);
}
extern VALUE rb_ary_ref_helper(void* rb, VALUE ary, int pos) {
rb_ary_entry(ary, pos);
}
extern size_t rb_ary_length_helper(VALUE ary) {
size_t return_value = (size_t) RARRAY_LEN(ary);
return_value;
}
extern VALUE rb_ary_new_from_values_helper(void* rb, int size, VALUE* values) {
rb_ary_new_from_values(size, values);
}
extern void rb_gc_register_helper(void* rb, VALUE value) {
rb_gc_register_address(&value);
}
extern void rb_gc_unregister_helper(void* rb, VALUE value) {
rb_gc_unregister_address(&value);
}
extern VALUE rb_yield_helper(void* rb, VALUE value, VALUE arg) {
rb_yield(arg);
}
extern VALUE rb_yield_argv_helper(void* rb, VALUE value, int argc, VALUE* argv) {
rb_yield_values2(argc, argv);
}
extern VALUE rb_call_block_helper(void* rb, VALUE value, VALUE arg) {
rb_proc_call(value, rb_ary_new_from_values(1, &arg));
}
extern VALUE rb_call_block_with_args_helper(void* rb, VALUE value, int argc, VALUE* argv) {
rb_proc_call(value, rb_ary_new_from_values(argc, argv));
}
extern bool rb_respond_to_helper(void* rb, VALUE obj, ID name) {
rb_respond_to(obj, name);
}
extern VALUE get_rb_obj_value(VALUE obj) {
return obj;
}
extern VALUE rb_funcall_argv_helper(void *rb, VALUE value, ID name, int argc, VALUE* argv) {
rb_funcallv(value, name, argc, argv);
}
extern VALUE rb_funcall_argv_with_block_helper(void *rb, VALUE value, ID name, int argc, VALUE* argv, VALUE block) {
rb_funcall_with_block(value, name, argc, argv, block);
}
extern VALUE rb_iv_get_helper(void* rb, VALUE obj, ID sym) {
rb_ivar_get(obj, sym);
}
extern void rb_iv_set_helper(void* rb, VALUE obj, ID sym, VALUE value) {
rb_ivar_set(obj, sym, value);
}
extern VALUE rb_cv_get_helper(void* rb, VALUE mod, ID sym) {
rb_cvar_get(mod, sym);
}
extern void rb_cv_set_helper(void* rb, VALUE mod, ID sym, VALUE value) {
rb_cvar_set(mod, sym, value);
}
extern VALUE rb_gv_get_helper(void* rb, const char* name) {
rb_gv_get(name);
}
extern void rb_gv_set_helper(void* rb, const char* name, VALUE value) {
rb_gv_set(name, value);
}
extern bool does_constant_exist_under(void* rb, VALUE under, const char* name) {
rb_const_defined_at(under, rb_intern(name)) == Qtrue ? 1 : 0;
}
extern bool does_constant_exist(void* rb, const char* name) {
rb_const_defined(rb_cObject, rb_intern(name)) == Qtrue ? 1 : 0;
}
extern VALUE get_constant_under(void* rb, VALUE under, const char* name) {
rb_const_get_at(under, rb_intern(name));
}
extern VALUE get_constant(void* rb, const char* name) {
rb_const_get(rb_cObject, rb_intern(name));
}
extern VALUE rb_undef_method_helper(void* rb, VALUE mod, const char* name) {
rb_undef_method(mod, name);
}
================================================
FILE: glue/mri/error_helper.c
================================================
#include <ruby.h>
extern void rb_raise_runtime_error(void* rb, const char* msg) {
rb_raise(rb_eRuntimeError, "%s", msg);
}
extern void rb_raise_type_error(void* rb, const char* msg) {
rb_raise(rb_eTypeError, "%s", msg);
}
extern void rb_raise_argument_error(void* rb, const char* msg) {
rb_raise(rb_eArgError, "%s", msg);
}
extern void rb_raise_index_error(void* rb, const char* msg) {
rb_raise(rb_eIndexError, "%s", msg);
}
extern void rb_raise_range_error(void* rb, const char* msg) {
rb_raise(rb_eRangeError, "%s", msg);
}
extern void rb_raise_name_error(void* rb, const char* msg) {
rb_raise(rb_eNameError, "%s", msg);
}
extern void rb_raise_script_error(void* rb, const char* msg) {
rb_raise(rb_eScriptError, "%s", msg);
}
extern void rb_raise_not_implemented_error(void* rb, const char* msg) {
rb_raise(rb_eNotImpError, "%s", msg);
}
extern void rb_raise_key_error(void* rb, const char* msg) {
rb_raise(rb_eKeyError, "%s", msg);
}
extern void rb_raise_helper(void* rb, VALUE exc, const char* msg) {
rb_raise(exc, "%s", msg);
}
extern void clear_last_rb_error(void* rb) {
rb_set_errinfo(Qnil);
}
extern VALUE get_last_rb_error(void* rb) {
return rb_errinfo();
}
================================================
FILE: glue/mri/return_functions.c
================================================
#include <ruby.h>
extern VALUE get_object_class(void* rb) {
return rb_cObject;
}
extern VALUE get_nil_value() {
return Qnil;
}
extern VALUE get_false_value() {
return Qfalse;
}
extern VALUE get_true_value() {
return Qtrue;
}
extern VALUE get_fixnum_value(int value) {
return INT2FIX(value);
}
extern VALUE get_bool_value(bool value) {
return (value ? Qtrue : Qfalse);
}
extern VALUE get_float_value(void* mrb, double value) {
return DBL2NUM(value);
}
extern VALUE get_string_value(void* mrb, char* value) {
return rb_utf8_str_new(value, strlen(value));
}
extern VALUE get_symbol_value_of_string(void* mrb, char* value) {
ID sym = rb_intern(value);
return ID2SYM(sym);
}
extern int check_rb_fixnum(VALUE value) {
return FIXNUM_P(value);
}
extern int check_rb_float(VALUE value) {
return RB_FLOAT_TYPE_P(value);
}
extern int check_rb_true(VALUE value) {
return (value == Qtrue);
}
extern int check_rb_false(VALUE value) {
return (value == Qfalse);
}
extern int check_rb_nil(VALUE value) {
return NIL_P(value);
}
extern int check_rb_undef(VALUE value) {
return RB_TYPE_P(value, T_UNDEF);
}
extern int check_rb_string(VALUE value) {
return RB_TYPE_P(value, T_STRING);
}
extern int check_rb_symbol(VALUE value) {
return RB_TYPE_P(value, T_SYMBOL);
}
extern int check_rb_array(VALUE value) {
return RB_TYPE_P(value, T_ARRAY);
}
extern int check_rb_hash(VALUE value) {
return RB_TYPE_P(value, T_HASH);
}
extern int check_rb_data(VALUE value) {
return RB_TYPE_P(value, T_DATA);
}
extern int get_rb_fixnum(VALUE value) {
return FIX2INT(value);
}
extern double get_rb_float(VALUE value) {
return NUM2DBL(value);
}
extern bool get_rb_bool(VALUE value) {
return (value != Qfalse);
}
extern const char* get_rb_string(void* mrb, VALUE value) {
return rb_string_value_cstr(&value);
}
================================================
FILE: glue/mri/script_helper.c
================================================
#include <ruby.h>
extern void* open_interpreter(void) {
static int first_run = 1;
if(!first_run) {
printf("ERROR: Only one Ruby interpreter can be used at this point.\n");
return (void*) 0;
}
RUBY_INIT_STACK;
ruby_init();
first_run = 0;
return (void*) 0;
}
extern void close_interpreter(void* rb) {
static int first_run = 1;
if(!first_run) {
return;
}
first_run = 0;
ruby_cleanup(0);
}
extern void load_script_from_file(void* rb, const char* filename) {
static int first_run = 1;
char* args[2] = {"test", (char*) filename};
if(!first_run) {
printf("ERROR: Ruby scripts can only be run once at this point.\n");
return;
}
void* options = ruby_options(2, args);
int ex_node_status;
int ex_node_return = ruby_executable_node(options, &ex_node_status);
if(!ex_node_return) {
printf("Error: File %s could not be executed.\n", filename);
ruby_cleanup(ex_node_status);
return;
}
int return_value = ruby_exec_node(options);
VALUE exception = rb_errinfo();
if(exception != Qnil) {
VALUE exception_str = rb_inspect(exception);
printf("%s\n", rb_string_value_cstr(&exception_str));
}
first_run = 0;
//! TODO: Fix segfaults at second execution
}
extern VALUE execute_script_line(void* rb, const char* text) {
int status;
VALUE result = rb_eval_string_protect(text, &status);
if(status) {
VALUE exception = rb_errinfo();
VALUE exception_str = rb_inspect(exception);
//! TODO: Are there any internal methods to print this prettier?
printf("%s\n", rb_string_value_cstr(&exception_str));
}
return result;
}
================================================
FILE: glue/mruby/data_helper.c
================================================
#include <mruby.h>
#include <mruby/class.h>
#include <mruby/data.h>
#include <mruby/array.h>
#include <mruby/string.h>
#include <mruby/variable.h>
#include <string.h>
extern const mrb_data_type* data_type(mrb_value value) {
return DATA_TYPE(value);
}
extern void set_instance_tt_as_data(struct RClass* ruby_class) {
MRB_SET_INSTANCE_TT(ruby_class, MRB_TT_DATA);
}
extern mrb_value new_empty_object(mrb_state* mrb, struct RClass* ruby_class, void* data_ptr, const mrb_data_type* data_type) {
return mrb_obj_value(mrb_data_object_alloc(mrb, ruby_class, data_ptr, data_type));
}
extern void* get_data_ptr(mrb_value ruby_object) {
return DATA_PTR(ruby_object);
}
extern void set_data_ptr_and_type(mrb_value ruby_object, void* data, mrb_data_type* data_type) {
DATA_PTR(ruby_object) = data;
DATA_TYPE(ruby_object) = data_type;
}
extern struct RClass* get_class_of_obj(mrb_state* mrb, mrb_value object) {
return mrb_class(mrb, object);
}
extern mrb_sym convert_to_mrb_sym(mrb_state* mrb, const char* str) {
return mrb_intern(mrb, str, strlen(str));
}
extern size_t array_length(mrb_value array) {
return ARY_LEN(mrb_ary_ptr(array));
}
extern mrb_value get_mrb_obj_value(void* p) {
return mrb_obj_value(p);
}
extern mrb_value mrb_gv_get_helper(mrb_state* mrb, const char* name) {
mrb_sym sym = convert_to_mrb_sym(mrb, name);
return mrb_gv_get(mrb, sym);
}
extern void mrb_gv_set_helper(mrb_state* mrb, const char* name, mrb_value value) {
mrb_sym sym = convert_to_mrb_sym(mrb, name);
mrb_gv_set(mrb, sym, value);
}
================================================
FILE: glue/mruby/error_helper.c
================================================
#include <mruby.h>
extern void mrb_raise_runtime_error(mrb_state* mrb, const char* msg) {
mrb_raise(mrb, E_RUNTIME_ERROR, msg);
}
extern void mrb_raise_type_error(mrb_state* mrb, const char* msg) {
mrb_raise(mrb, E_TYPE_ERROR, msg);
}
extern void mrb_raise_argument_error(mrb_state* mrb, const char* msg) {
mrb_raise(mrb, E_ARGUMENT_ERROR, msg);
}
extern void mrb_raise_index_error(mrb_state* mrb, const char* msg) {
mrb_raise(mrb, E_INDEX_ERROR, msg);
}
extern void mrb_raise_range_error(mrb_state* mrb, const char* msg) {
mrb_raise(mrb, E_RANGE_ERROR, msg);
}
extern void mrb_raise_name_error(mrb_state* mrb, const char* msg) {
mrb_raise(mrb, E_NAME_ERROR, msg);
}
extern void mrb_raise_script_error(mrb_state* mrb, const char* msg) {
mrb_raise(mrb, E_SCRIPT_ERROR, msg);
}
extern void mrb_raise_not_implemented_error(mrb_state* mrb, const char* msg) {
mrb_raise(mrb, E_NOTIMP_ERROR, msg);
}
extern void mrb_raise_key_error(mrb_state* mrb, const char* msg) {
mrb_raise(mrb, E_KEY_ERROR, msg);
}
extern void clear_last_mrb_error(mrb_state* mrb) {
mrb->exc = NULL;
}
extern mrb_value get_last_mrb_error(mrb_state* mrb) {
return mrb_obj_value(mrb->exc);
}
================================================
FILE: glue/mruby/return_functions.c
================================================
#include <mruby.h>
#include <mruby/class.h>
#include <mruby/data.h>
#include <mruby/string.h>
#include <string.h>
extern struct RClass* get_object_class(mrb_state* mrb) {
return mrb->object_class;
}
extern mrb_value get_nil_value() {
return mrb_nil_value();
}
extern mrb_value get_false_value() {
return mrb_false_value();
}
extern mrb_value get_true_value() {
return mrb_true_value();
}
extern mrb_value get_fixnum_value(mrb_int value) {
return mrb_fixnum_value(value);
}
extern mrb_value get_bool_value(mrb_bool value) {
return mrb_bool_value(value);
}
extern mrb_value get_float_value(mrb_state* mrb, mrb_float value) {
return mrb_float_value(mrb, value);
}
extern mrb_value get_string_value(mrb_state* mrb, char* value) {
return mrb_str_new(mrb, value, strlen(value));
}
extern mrb_value get_symbol_value_of_string(mrb_state* mrb, char* value) {
mrb_sym sym = mrb_intern(mrb, value, strlen(value));
return mrb_symbol_value(sym);
}
extern int check_mrb_fixnum(mrb_value value) {
return mrb_integer_p(value);
}
extern int check_mrb_float(mrb_value value) {
return mrb_float_p(value);
}
extern int check_mrb_true(mrb_value value) {
return mrb_true_p(value);
}
extern int check_mrb_false(mrb_value value) {
return mrb_false_p(value);
}
extern int check_mrb_nil(mrb_value value) {
return mrb_nil_p(value);
}
extern int check_mrb_undef(mrb_value value) {
return mrb_undef_p(value);
}
extern int check_mrb_string(mrb_value value) {
return mrb_string_p(value);
}
extern int check_mrb_symbol(mrb_value value) {
return mrb_symbol_p(value);
}
extern int check_mrb_array(mrb_value value) {
return mrb_array_p(value);
}
extern int check_mrb_hash(mrb_value value) {
return mrb_hash_p(value);
}
extern int check_mrb_data(mrb_value value) {
return mrb_data_p(value);
}
extern mrb_int get_mrb_fixnum(mrb_value value) {
return mrb_integer(value);
}
extern mrb_float get_mrb_float(mrb_value value) {
return mrb_float(value);
}
extern mrb_bool get_mrb_bool(mrb_value value) {
return mrb_bool(value);
}
extern const char* get_mrb_string(mrb_state* mrb, mrb_value value) {
return mrb_str_to_cstr(mrb, value);
}
================================================
FILE: glue/mruby/script_helper.c
================================================
#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/dump.h>
#include <mruby/proc.h>
#include <mruby/internal.h>
#include <stdlib.h>
extern mrb_value load_script_from_file(mrb_state* mrb, const char* filename) {
mrbc_context* new_context = mrbc_context_new(mrb);
mrbc_filename(mrb, new_context, filename);
FILE* file = fopen(filename, "r");
if(!file) {
mrb_raisef(mrb, E_RUNTIME_ERROR, "Could not load script file: %s", filename);
}
int ai = mrb_gc_arena_save(mrb);
mrb_value status = mrb_load_file_cxt(mrb, file, new_context);
mrb_gc_arena_restore(mrb, ai);
if(file) fclose(file);
if(mrb->exc) mrb_print_error(mrb);
mrbc_context_free(mrb, new_context);
return status;
}
extern mrb_value execute_script_line(mrb_state* mrb, const char* str) {
mrbc_context* new_context = mrbc_context_new(mrb);
mrbc_filename(mrb, new_context, "Script");
int ai = mrb_gc_arena_save(mrb);
mrb_value status = mrb_load_string_cxt(mrb, str, new_context);
mrb_gc_arena_restore(mrb, ai);
if(mrb->exc) mrb_print_error(mrb);
mrbc_context_free(mrb, new_context);
return status;
}
extern mrb_value load_bytecode_from_file(mrb_state* mrb, const char* filename) {
FILE* file = fopen(filename, "r");
if (!file) {
//! TODO: Find out why this does trigger an unstoppable IOT signal
mrb_raisef(mrb, E_RUNTIME_ERROR, "Could not load bytecode file: %s", filename);
}
mrb_value status = mrb_load_irep_file(mrb, file);
if (file) fclose(file);
if(mrb->exc) mrb_print_error(mrb);
return status;
}
extern mrb_value execute_bytecode(mrb_state* mrb, const uint8_t* bytecode) {
int ai = mrb_gc_arena_save(mrb);
mrb_value status = mrb_load_irep(mrb, bytecode);
mrb_gc_arena_restore(mrb, ai);
return status;
}
extern int transform_script_to_bytecode(const char* filename, const char* target_filename) {
mrb_state *mrb = mrb_open_core(NULL, NULL);
mrbc_context* new_context = mrbc_context_new(mrb);
new_context->no_exec = TRUE;
mrbc_filename(mrb, new_context, filename);
FILE* file = fopen(filename, "rb");
if(!file) {
return 1;
}
mrb_value result = mrb_load_file_cxt(mrb, file, new_context);
if (mrb_undef_p(result)) {
return 2;
}
if(file) fclose(file);
const mrb_irep *irep = mrb_proc_ptr(result)->body.irep;
FILE* outfile = fopen(target_filename, "wb");
if (!outfile) {
return 3;
}
mrb_dump_irep_binary(mrb, irep, 4, outfile);
if(outfile) fclose(outfile);
mrbc_context_free(mrb, new_context);
return 0;
}
typedef struct bytecode_container {
uint8_t* content;
size_t size;
int error_code;
int result;
} bytecode_container_t;
extern bytecode_container_t transform_script_to_bytecode_container(const char* filename) {
mrb_state *mrb = mrb_open_core(NULL, NULL);
mrbc_context* new_context = mrbc_context_new(mrb);
new_context->no_exec = TRUE;
mrbc_filename(mrb, new_context, filename);
bytecode_container_t container = {NULL, 0, 0, 0};
FILE* file = fopen(filename, "rb");
if(!file) {
container.error_code = 1;
return container;
}
mrb_value result = mrb_load_file_cxt(mrb, file, new_context);
if (mrb_undef_p(result)) {
container.error_code = 2;
return container;
}
if(file) fclose(file);
const mrb_irep *irep = mrb_proc_ptr(result)->body.irep;
uint8_t *bin = NULL;
size_t bin_size = 0;
container.result = mrb_dump_irep(mrb, irep, 4, &bin, &bin_size);
container.content = (uint8_t*) malloc(bin_size * sizeof(uint8_t));
memcpy(container.content, bin, bin_size);
container.size = bin_size;
return container;
}
extern bytecode_container_t transform_proc_to_bytecode_container(mrb_state* mrb, mrb_value proc_object) {
bytecode_container_t container = {NULL, 0, 0, 0};
const mrb_irep *irep = mrb_proc_ptr(proc_object)->body.irep;
uint8_t *bin = NULL;
size_t bin_size = 0;
container.result = mrb_dump_irep(mrb, irep, 4, &bin, &bin_size);
container.content = (uint8_t*) malloc(bin_size * sizeof(uint8_t));
memcpy(container.content, bin, bin_size);
container.size = bin_size;
return container;
}
extern void free_bytecode_container(bytecode_container_t container) {
free(container.content);
}
extern int mrb_gc_arena_save_helper(mrb_state* mrb) {
return mrb_gc_arena_save(mrb);
}
extern void mrb_gc_arena_restore_helper(mrb_state* mrb, int idx) {
mrb_gc_arena_restore(mrb, idx);
}
================================================
FILE: install.cr
================================================
command = {% if flag?(:win32) %}
# TODO: Is there a better solution?
"cmd /C rake build_shard"
{% else %}
"rake build_shard"
{% end %}
# system() inherits the parent process' IO descriptors.
# Failing here will cause the shards process to output this; if we succeed, it is silenced automatically.
raise Exception.new("Failed to install Anyolite") unless system(command)
================================================
FILE: shard.yml
================================================
name: anyolite
version: 1.1.1
authors:
- Hadeweka
description: |
A library which allows for binding crystal classes and methods to an embedded mruby interpreter.
scripts:
postinstall: crystal run install.cr
license: MIT
crystal: '>= 1.0.0'
================================================
FILE: src/BytecodeCompiler.cr
================================================
require "./Main.cr"
Anyolite::Preloader.transform_script_to_bytecode(ARGV[0], ARGV[1])
================================================
FILE: src/BytecodeGetter.cr
================================================
require "./Main.cr"
puts Anyolite::Preloader.transform_script_to_bytecode_array(ARGV[0])
================================================
FILE: src/Macro.cr
================================================
module Anyolite
# Helper methods which should not be used for trivial cases in the final version
module Macro
end
end
require "./macros/RubyTypes.cr"
require "./macros/ArgTuples.cr"
require "./macros/ArgConversions.cr"
require "./macros/UnionCasts.cr"
require "./macros/RubyConversions.cr"
require "./macros/FunctionCalls.cr"
require "./macros/ObjectAllocations.cr"
require "./macros/Wrappers.cr"
require "./macros/WrapMethodIndex.cr"
require "./macros/WrapAll.cr"
require "./macros/FunctionGenerators.cr"
================================================
FILE: src/Main.cr
================================================
require "./RbInternal.cr"
require "./RbInterpreter.cr"
require "./RbClass.cr"
require "./RbCast.cr"
require "./Macro.cr"
require "./RbClassCache.cr"
require "./RbTypeCache.cr"
require "./RbModule.cr"
require "./RbRefTable.cr"
require "./RbArgCache.cr"
require "./Preloader.cr"
# Main wrapper module, which should be covering most of the use cases.
module Anyolite
# Special struct representing undefined values in mruby.
struct Undefined
# :nodoc:
def initialize
end
end
# Use this special constant in case of a function to wrap, which has only an operator as a name.
struct Empty
# :nodoc:
def initialize
end
end
# Internal class to hide the `Struct` *T* in a special class
# to obtain all class-related properties.
class StructWrapper(T)
@content : T | Nil = nil
def initialize(value)
@content = value
end
def content : T
if c = @content
c
else
# This should not be called theoretically
raise("Content of struct wrapper for #{T} is undefined!")
end
end
def content=(value)
@content = value
end
end
# Class to contain Ruby values in a GC-protected container
class RbRef
@value : RbCore::RbValue
# Create a new container with *value* as content
def initialize(value : RbCore::RbValue)
@value = value
RbCore.rb_gc_register(RbRefTable.get_current_interpreter, value)
end
# Return the contained value
def value
@value
end
# Return `true` if the value is undefined, otherwise `false`
def is_undef?
RbCast.check_for_undef(@value)
end
# Return `true` if the value is a Ruby bool, otherwise `false`
def is_bool?
RbCast.check_for_bool(@value)
end
# Return `true` if the value is a Ruby nil, otherwise `false`
def is_nil?
RbCast.check_for_nil(@value)
end
# Return `true` if the value is a Ruby fixnum, otherwise `false`
def is_fixnum?
RbCast.check_for_fixnum(@value)
end
# Return `true` if the value is a Ruby float, otherwise `false`
def is_float?
RbCast.check_for_float(@value)
end
# Return `true` if the value is a Ruby string, otherwise `false`
def is_string?
RbCast.check_for_string(@value)
end
# Return `true` if the value is a Ruby symbol, otherwise `false`
def is_symbol?
RbCast.check_for_symbol(@value)
end
# Return `true` if the value is a Ruby array, otherwise `false`
def is_array?
RbCast.check_for_array(@value)
end
# Return `true` if the value is a Ruby hash, otherwise `false`
def is_hash?
RbCast.check_for_hash(@value)
end
# Return `true` if the value is a wrapped objects, otherwise `false`
def is_custom?
RbCast.check_for_data(@value)
end
# Return `true` if the value is a wrapped object of class *class_name*, otherwise `false`
def is_custom?(class_name)
RbCast.check_custom_type(RbRefTable.get_current_interpreter, value, class_name)
end
# :nodoc:
def finalize
RbCore.rb_gc_unregister(RbRefTable.get_current_interpreter, value) if RbRefTable.check_interpreter
end
# :nodoc:
def to_unsafe
@value
end
end
# Undefined mruby value.
Undef = Undefined.new
# Returns the current implementation (either `:mruby` or `:mri`) as a `Symbol`.
macro implementation
{% if flag?(:anyolite_implementation_ruby_3) %}
:mri
{% else %}
:mruby
{% end %}
end
# Returns the depth of interpreter calls
def self.get_interpreter_depth
Anyolite::RbRefTable.get_current_interpreter.depth
end
# Wraps any Crystal object into an `RbRef`, securing it from the Ruby GC.
# This prevents the object from being deleted in Ruby, as long as this reference exists in Crystal.
#
# Note that this might lead to memory leaks if you close the Ruby interpreter before
# all of these objects are cleaned up properly using the Crystal GC.
# However, this is only a problem if you open much more than one interpreter while running a program.
def self.create_rb_ref(obj)
Anyolite::RbRef.new(Anyolite::RbCast.return_value(Anyolite::RbRefTable.get_current_interpreter.to_unsafe, obj))
end
# Disables any function that allows for running external programs
def self.disable_program_execution
Anyolite.eval("class IO; def self._popen(command, mode, **opts); raise \"No system commands allowed!\"; end; end")
Anyolite.eval("class IO; def self.popen(command, mode = 'r', **opts, &block); raise \"No system commands allowed!\"; end; end")
end
# Checks whether *value* is referenced in the current reference table.
macro referenced_in_ruby?(value)
!!Anyolite::RbRefTable.is_registered?(Anyolite::RbRefTable.get_object_id({{value}}))
end
# Returns the `RbValue` of the `Class`, `Module` or `String` *crystal_class*.
macro get_rb_class_obj_of(crystal_class)
{% if crystal_class.is_a?(StringLiteral) %}
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbCore.get_rb_obj_value(Anyolite::RbCore.rb_class_get(%rb, {{crystal_class}}))
{% else %}
Anyolite::RbCore.get_rb_obj_value(Anyolite::RbClassCache.get({{crystal_class}}))
{% end %}
end
# Returns a cached block argument (or `nil`, if none given) in form of a `RbRef`, if enabled.
# Otherwise, an error will be triggered.
macro obtain_given_rb_block
%rb = Anyolite::RbRefTable.get_current_interpreter
if %bc = Anyolite::RbArgCache.get_block_cache
if Anyolite::RbCast.check_for_nil(%bc.value)
nil
else
%bc_ref = Anyolite::RbRef.new(%bc.value)
%bc_ref
end
else
raise "This method does not accept block arguments."
end
end
# Calls the Ruby block *block_value*, given as a `RbRef`, with the arguments *args*
# as an `Array` of castable Crystal values (`nil` for none).
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro call_rb_block(block_value, args = nil, cast_to = nil, context = nil)
%rb = Anyolite::RbRefTable.get_current_interpreter
{% options = {:context => context} %}
if %rb_block = {{block_value}}
{% if args %}
%argc = {{args}}.size
%argv = Pointer(Anyolite::RbCore::RbValue).malloc(size: %argc) do |i|
Anyolite::RbCast.return_value(%rb.to_unsafe, {{args}}[i])
end
%block_return_value = Anyolite::RbCore.rb_call_block_with_args(%rb, %rb_block.value, %argc, %argv)
{% else %}
%block_return_value = Anyolite::RbCore.rb_call_block(%rb, %rb_block.value, Anyolite::RbCast.return_nil)
{% end %}
{% if cast_to %}
Anyolite::Macro.convert_from_ruby_to_crystal(%rb.to_unsafe, %block_return_value, {{cast_to}}, options: {{options}})
{% else %}
Anyolite::RbRef.new(%block_return_value)
{% end %}
else
raise "Empty block argument."
end
end
# Casts the `RbRef` *rbref* to the Crystal `Class` *cast_type*.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro cast_to_crystal(rbref, cast_type, context = nil)
%rb = Anyolite::RbRefTable.get_current_interpreter
{% options = {:context => context} %}
Anyolite::Macro.convert_from_ruby_to_crystal(%rb.to_unsafe, {{rbref}}.value, {{cast_type}}, options: {{options}})
end
# Raises a Ruby runtime error with `String` *message*.
macro raise_runtime_error(message)
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbCore.rb_raise_runtime_error(%rb.to_unsafe, {{message}}.to_unsafe)
end
# Raises a Ruby type error with `String` *message*.
macro raise_type_error(message)
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbCore.rb_raise_type_error(%rb.to_unsafe, {{message}}.to_unsafe)
end
# Raises a Ruby argument error with `String` *message*.
macro raise_argument_error(message)
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbCore.rb_raise_argument_error(%rb.to_unsafe, {{message}}.to_unsafe)
end
# Raises a Ruby index error with `String` *message*.
macro raise_index_error(message)
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbCore.rb_raise_index_error(%rb.to_unsafe, {{message}}.to_unsafe)
end
# Raises a Ruby range error with `String` *message*.
macro raise_range_error(message)
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbCore.rb_raise_range_error(%rb.to_unsafe, {{message}}.to_unsafe)
end
# Raises a Ruby name error with `String` *message*.
macro raise_name_error(message)
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbCore.rb_raise_name_error(%rb.to_unsafe, {{message}}.to_unsafe)
end
# Raises a Ruby script error with `String` *message*.
macro raise_script_error(message)
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbCore.rb_raise_script_error(%rb.to_unsafe, {{message}}.to_unsafe)
end
# Raises a Ruby non-implementation error with `String` *message*.
macro raise_not_implemented_error(message)
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbCore.rb_raise_not_implemented_error(%rb.to_unsafe, {{message}}.to_unsafe)
end
# Raises a Ruby key error with `String` *message*.
macro raise_key_error(message)
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbCore.rb_raise_key_error(%rb.to_unsafe, {{message}}.to_unsafe)
end
# Checks whether the Ruby function *name* (`String` or `Symbol`) is defined
# for the Crystal object or `RbRef` *value*.
macro does_obj_respond_to(value, name)
%method_name = {{name}}
if !%method_name.is_a?(Symbol) && !%method_name.is_a?(String)
raise "Given name #{%method_name} is neither a String nor a Symbol."
end
%rb = Anyolite::RbRefTable.get_current_interpreter
%obj = Anyolite::RbCast.convert_to_unsafe_rb_value(%rb.to_unsafe, {{value}})
%name = Anyolite::RbCore.convert_to_rb_sym(%rb, %method_name.to_s)
Anyolite::RbCore.rb_respond_to(%rb, %obj, %name) == 0 ? false : true
end
# Checks whether the Ruby function *name* (`String` or `Symbol`) is defined
# for the Crystal `Class`, `Module` or `String` *crystal_class*.
macro does_class_respond_to(crystal_class, name)
%method_name = {{name}}
if !%method_name.is_a?(Symbol) && !%method_name.is_a?(String)
raise "Given name #{%method_name} is neither a String nor a Symbol."
end
%rb = Anyolite::RbRefTable.get_current_interpreter
%rb_class = Anyolite.get_rb_class_obj_of({{crystal_class}})
%name = Anyolite::RbCore.convert_to_rb_sym(%rb, %method_name.to_s)
Anyolite::RbCore.rb_respond_to(%rb, %rb_class, %name) == 0 ? false : true
end
# TODO: Is it possible to add block args to the two methods below?
# Calls the Ruby method with `String` or `Symbol` *name* for the Crystal object or `RbRef` *value* and the
# arguments *args* as an `Array` of castable Crystal values (`nil` for none).
#
# An additional block can be passed as `RbRef` *block*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro call_rb_method_of_object(value, name, args = nil, block = nil, cast_to = nil, context = nil)
%method_name = {{name}}
if !%method_name.is_a?(Symbol) && !%method_name.is_a?(String)
raise "Given name #{%method_name} is neither a String nor a Symbol."
end
%rb = Anyolite::RbRefTable.get_current_interpreter
%obj = Anyolite::RbCast.convert_to_unsafe_rb_value(%rb.to_unsafe, {{value}})
%name = Anyolite::RbCore.convert_to_rb_sym(%rb, %method_name.to_s)
{% options = {:context => context} %}
{% if args %}
%argc = {{args}}.size
%argv = Pointer(Anyolite::RbCore::RbValue).malloc(size: %argc) do |i|
Anyolite::RbCast.return_value(%rb.to_unsafe, {{args}}[i])
end
{% else %}
%argc = 0
%argv = [] of Anyolite::RbCore::RbValue
{% end %}
%rb.depth += 1
{% if block %}
%call_result = Anyolite::RbCore.rb_funcall_argv_with_block(%rb, %obj, %name, %argc, %argv, {{block}}.not_nil!.to_unsafe)
{% else %}
%call_result = Anyolite::RbCore.rb_funcall_argv(%rb, %obj, %name, %argc, %argv)
{% end %}
%rb.depth -= 1
{% if cast_to %}
Anyolite::Macro.convert_from_ruby_to_crystal(%rb.to_unsafe, %call_result, {{cast_to}}, options: {{options}})
{% else %}
Anyolite::RbRef.new(%call_result)
{% end %}
end
# Calls the Ruby method with `String` or `Symbol` *name* for `self` and the
# arguments *args* as an `Array` of castable Crystal values (`nil` for none).
#
# An additional block can be passed as `RbRef` *block*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro call_rb_method(name, args = nil, block = nil, cast_to = nil, context = nil)
Anyolite.call_rb_method_of_object(self, {{name}}, {{args}}, {{block}}, cast_to: {{cast_to}}, context: {{context}})
end
# Calls the Ruby method with `String` or `Symbol` *name* for `self.class` and the
# arguments *args* as an `Array` of castable Crystal values (`nil` for none).
#
# An additional block can be passed as `RbRef` *block*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro call_rb_class_method(name, args = nil, block = nil, cast_to = nil, context = nil)
Anyolite.call_rb_method_of_class(self.class, {{name}}, {{args}}, {{block}}, cast_to: {{cast_to}}, context: {{context}})
end
# Calls the Ruby method with `String` or `Symbol` *name*
# for the Crystal `Class`, `Module` or `String` *crystal_class* and the
# arguments *args* as an `Array` of castable Crystal values (`nil` for none).
#
# An additional block can be passed as `RbRef` *block*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro call_rb_method_of_class(crystal_class, name, args = nil, block = nil, cast_to = nil, context = nil)
%rb_class = Anyolite.get_rb_class_obj_of({{crystal_class}})
Anyolite.call_rb_method_of_object(%rb_class, {{name}}, {{args}}, {{block}}, cast_to: {{cast_to}}, context: {{context}})
end
# TODO: Block arguments?
# Calls the Ruby expression *str*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro eval(str, cast_to = nil, context = nil)
%rb = Anyolite::RbRefTable.get_current_interpreter
{% options = {:context => context} %}
%call_result = %rb.execute_script_line({{str}})
{% if cast_to %}
Anyolite::Macro.convert_from_ruby_to_crystal(%rb.to_unsafe, %call_result, {{cast_to}}, options: {{options}})
{% else %}
Anyolite::RbRef.new(%call_result)
{% end %}
end
# Returns current object as a `RbRef`
macro self_in_rb
%rb = Anyolite::RbRefTable.get_current_interpreter
Anyolite::RbRef.new(Anyolite::RbCast.return_value(%rb.to_unsafe, self))
end
# Gets the Ruby instance variable with `String` or `Symbol` *name* for the Crystal object or `RbRef` *object*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro get_iv(object, name, cast_to = nil, context = nil)
%rb = Anyolite::RbRefTable.get_current_interpreter
%obj = Anyolite::RbCast.convert_to_unsafe_rb_value(%rb.to_unsafe, {{object}})
%name = Anyolite::RbCore.convert_to_rb_sym(%rb, {{name}}.to_s)
{% options = {:context => context} %}
%result = Anyolite::RbCore.rb_iv_get(%rb, %obj, %name)
{% if cast_to %}
Anyolite::Macro.convert_from_ruby_to_crystal(%rb.to_unsafe, %result, {{cast_to}}, options: {{options}})
{% else %}
Anyolite::RbRef.new(%result)
{% end %}
end
# Sets the Ruby instance variable with `String` or `Symbol` *name* for the Crystal object or `RbRef` *object*
# to the Crystal value *value*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro set_iv(object, name, value)
%rb = Anyolite::RbRefTable.get_current_interpreter
%obj = Anyolite::RbCast.convert_to_unsafe_rb_value(%rb.to_unsafe, {{object}})
%name = Anyolite::RbCore.convert_to_rb_sym(%rb, {{name}}.to_s)
%value = Anyolite::RbCast.return_value(%rb.to_unsafe, {{value}})
Anyolite::RbCore.rb_iv_set(%rb, %obj, %name, %value)
end
# Gets the Ruby class variable with `String` or `Symbol` *name* for the Crystal `Class`, `Module` or `String` *crystal_class*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro get_cv(crystal_class, name, cast_to = nil, context = nil)
%rb = Anyolite::RbRefTable.get_current_interpreter
%rb_class = Anyolite.get_rb_class_obj_of({{crystal_class}})
%name = Anyolite::RbCore.convert_to_rb_sym(%rb, {{name}}.to_s)
{% options = {:context => context} %}
%result = Anyolite::RbCore.rb_cv_get(%rb, %rb_class, %name)
{% if cast_to %}
Anyolite::Macro.convert_from_ruby_to_crystal(%rb.to_unsafe, %result, {{cast_to}}, options: {{options}})
{% else %}
Anyolite::RbRef.new(%result)
{% end %}
end
# Sets the Ruby class variable with `String` or `Symbol` *name* for the Crystal `Class`, `Module` or `String` *crystal_class*
# to the value *value*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro set_cv(crystal_class, name, value)
%rb = Anyolite::RbRefTable.get_current_interpreter
%rb_class = Anyolite.get_rb_class_obj_of({{crystal_class}})
%name = Anyolite::RbCore.convert_to_rb_sym(%rb, {{name}}.to_s)
%value = Anyolite::RbCast.return_value(%rb.to_unsafe, {{value}})
Anyolite::RbCore.rb_cv_set(%rb, %rb_class, %name, %value)
end
# Gets the Ruby global variable with `String` or `Symbol` *name*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro get_gv(name, cast_to = nil, context = nil)
%rb = Anyolite::RbRefTable.get_current_interpreter
{% options = {:context => context} %}
%result = Anyolite::RbCore.rb_gv_get(%rb, {{name}}.to_s.to_unsafe)
{% if cast_to %}
Anyolite::Macro.convert_from_ruby_to_crystal(%rb.to_unsafe, %result, {{cast_to}}, options: {{options}})
{% else %}
Anyolite::RbRef.new(%result)
{% end %}
end
# Sets the Ruby global variable with `String` or `Symbol` *name*
# to the Crystal value *value*.
#
# If *cast_to* is set to a `Class` or similar, it will automatically cast
# the result to a Crystal value of that class, otherwise, it will return
# a `RbRef` value containing the result.
#
# If needed, *context* can be set to a `Path` in order to specify *cast_to*.
macro set_gv(name, value)
%rb = Anyolite::RbRefTable.get_current_interpreter
%value = Anyolite::RbCast.return_value(%rb.to_unsafe, {{value}})
Anyolite::RbCore.rb_gv_set(%rb, {{name}}.to_s.to_unsafe, %value)
end
# Wraps a Crystal class directly into an mruby class.
#
# The Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with *name* as its new designation, returning an `Anyolite::RbClass`.
#
# To inherit from another mruby class, specify an `Anyolite::RbClass` as a *superclass*.
#
# Each class can be defined in a specifiy module by setting *under* to a `Anyolite::RbModule`.
macro wrap_class(rb_interpreter, crystal_class, name, under = nil, superclass = nil)
%new_class = Anyolite::RbClass.new({{rb_interpreter}}, {{name}}, under: Anyolite::RbClassCache.get({{under}}), superclass: Anyolite::RbClassCache.get({{superclass}}))
Anyolite::RbCore.set_instance_tt_as_data(%new_class)
Anyolite::RbClassCache.register({{crystal_class}}, %new_class)
Anyolite::RbClassCache.get({{crystal_class}})
end
# Wraps a Crystal module into an mruby module.
#
# The module *crystal_module* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with *name* as its new designation, returning an `Anyolite::RbModule`.
#
# The parent module can be specified with the module argument *under*.
macro wrap_module(rb_interpreter, crystal_module, name, under = nil)
%new_module = Anyolite::RbModule.new({{rb_interpreter}}, {{name}}, under: Anyolite::RbClassCache.get({{under}}))
Anyolite::RbClassCache.register({{crystal_module}}, %new_module)
Anyolite::RbClassCache.get({{crystal_module}})
end
# Wraps the constructor of a Crystal class into mruby.
#
# The constructor for the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the arguments *proc_args* as an `Array of Class`.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
#
# The arguments *block_arg_number* and *block_return_type* can be set to an `Int` and a `Class`,
# respectively, in order to require a block argument. If *store_block_arg* is set to `true`,
# any block argument given will be stored in a cache.
macro wrap_constructor(rb_interpreter, crystal_class, proc_args = nil, operator = "", context = nil, block_arg_number = nil, block_return_type = nil, store_block_arg = false)
{%
options = {
:context => context,
:block_arg_number => block_arg_number,
:block_return_type => block_return_type,
:store_block_arg => store_block_arg,
}
%}
Anyolite::Macro.wrap_constructor_function_with_args({{rb_interpreter}}, {{crystal_class}}, {{crystal_class}}.new, {{proc_args}}, operator: {{operator}}, options: {{options}})
end
# Wraps the constructor of a Crystal class into mruby, using keyword arguments.
#
# The constructor for the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the arguments *regular_args* as an `Array of Class` and *keyword_args* as an `Array of TypeDeclaration`.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
#
# The arguments *block_arg_number* and *block_return_type* can be set to an `Int` and a `Class`,
# respectively, in order to require a block argument. If *store_block_arg* is set to `true`,
# any block argument given will be stored in a cache.
macro wrap_constructor_with_keywords(rb_interpreter, crystal_class, keyword_args, regular_args = nil, operator = "", context = nil, block_arg_number = nil, block_return_type = nil, store_block_arg = false)
{%
options = {
:context => context,
:block_arg_number => block_arg_number,
:block_return_type => block_return_type,
:store_block_arg => store_block_arg,
}
%}
Anyolite::Macro.wrap_constructor_function_with_keyword_args({{rb_interpreter}}, {{crystal_class}}, {{crystal_class}}.new, {{keyword_args}}, {{regular_args}}, operator: {{operator}}, options: {{options}})
end
# Wraps a module function into mruby.
#
# The function *proc* under the module *under_module* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the arguments *proc_args* as an `Array of Class`.
#
# Its new name will be *name*.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
# The value *return_nil* will override any returned value with `nil`.
#
# The arguments *block_arg_number* and *block_return_type* can be set to an `Int` and a `Class`,
# respectively, in order to require a block argument. If *store_block_arg* is set to `true`,
# any block argument given will be stored in a cache.
macro wrap_module_function(rb_interpreter, under_module, name, proc, proc_args = nil, operator = "", context = nil, return_nil = false, block_arg_number = nil, block_return_type = nil, store_block_arg = false)
{%
options = {
:context => context,
:return_nil => return_nil,
:block_arg_number => block_arg_number,
:block_return_type => block_return_type,
:store_block_arg => store_block_arg,
}
%}
Anyolite::Macro.wrap_module_function_with_args({{rb_interpreter}}, {{under_module}}, {{name}}, {{proc}}, {{proc_args}}, options: {{options}})
end
# Wraps a module function into mruby, using keyword arguments.
#
# The function *proc* under the module *under_module* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the arguments *regular_args* as an `Array of Class` and *keyword_args* as an `Array of TypeDeclaration`.
#
# Its new name will be *name*.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
# The value *return_nil* will override any returned value with `nil`.
#
# The arguments *block_arg_number* and *block_return_type* can be set to an `Int` and a `Class`,
# respectively, in order to require a block argument. If *store_block_arg* is set to `true`,
# any block argument given will be stored in a cache.
macro wrap_module_function_with_keywords(rb_interpreter, under_module, name, proc, keyword_args, regular_args = nil, operator = "", context = nil, return_nil = false, block_arg_number = nil, block_return_type = nil, store_block_arg = false)
{%
options = {
:context => context,
:return_nil => return_nil,
:block_arg_number => block_arg_number,
:block_return_type => block_return_type,
:store_block_arg => store_block_arg,
}
%}
Anyolite::Macro.wrap_module_function_with_keyword_args({{rb_interpreter}}, {{under_module}}, {{name}}, {{proc}}, {{keyword_args}}, {{regular_args}}, operator: {{operator}}, options: {{options}})
end
# Wraps a class method into mruby.
#
# The class method *proc* of the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the arguments *proc_args* as an `Array of Class`.
#
# Its new name will be *name*.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
# The value *return_nil* will override any returned value with `nil`.
#
# The arguments *block_arg_number* and *block_return_type* can be set to an `Int` and a `Class`,
# respectively, in order to require a block argument. If *store_block_arg* is set to `true`,
# any block argument given will be stored in a cache.
macro wrap_class_method(rb_interpreter, crystal_class, name, proc, proc_args = nil, operator = "", context = nil, return_nil = false, block_arg_number = nil, block_return_type = nil, store_block_arg = false)
{%
options = {
:context => context,
:return_nil => return_nil,
:block_arg_number => block_arg_number,
:block_return_type => block_return_type,
:store_block_arg => store_block_arg,
}
%}
Anyolite::Macro.wrap_class_method_with_args({{rb_interpreter}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_args}}, operator: {{operator}}, options: {{options}})
end
# Wraps a class method into mruby, using keyword arguments.
#
# The class method *proc* of the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the arguments *regular_args* as an `Array of Class` and *keyword_args* as an `Array of TypeDeclaration`.
#
# Its new name will be *name*.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
# The value *return_nil* will override any returned value with `nil`.
#
# The arguments *block_arg_number* and *block_return_type* can be set to an `Int` and a `Class`,
# respectively, in order to require a block argument. If *store_block_arg* is set to `true`,
# any block argument given will be stored in a cache.
macro wrap_class_method_with_keywords(rb_interpreter, crystal_class, name, proc, keyword_args, regular_args = nil, operator = "", context = nil, return_nil = false, block_arg_number = nil, block_return_type = nil, store_block_arg = false)
{%
options = {
:context => context,
:return_nil => return_nil,
:block_arg_number => block_arg_number,
:block_return_type => block_return_type,
:store_block_arg => store_block_arg,
}
%}
Anyolite::Macro.wrap_class_method_with_keyword_args({{rb_interpreter}}, {{crystal_class}}, {{name}}, {{proc}}, {{keyword_args}}, {{regular_args}}, operator: {{operator}}, options: {{options}})
end
# Wraps an instance method into mruby.
#
# The instance method *proc* of the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the arguments *proc_args* as an `Array of Class`.
#
# Its new name will be *name*.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
# The value *return_nil* will override any returned value with `nil`.
#
# The arguments *block_arg_number* and *block_return_type* can be set to an `Int` and a `Class`,
# respectively, in order to require a block argument. If *store_block_arg* is set to `true`,
# any block argument given will be stored in a cache.
macro wrap_instance_method(rb_interpreter, crystal_class, name, proc, proc_args = nil, operator = "", context = nil, return_nil = false, block_arg_number = nil, block_return_type = nil, store_block_arg = false)
{%
options = {
:context => context,
:return_nil => return_nil,
:block_arg_number => block_arg_number,
:block_return_type => block_return_type,
:store_block_arg => store_block_arg,
}
%}
Anyolite::Macro.wrap_instance_function_with_args({{rb_interpreter}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_args}}, operator: {{operator}}, options: {{options}})
end
# Wraps an instance method into mruby, using keyword arguments.
#
# The instance method *proc* of the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the arguments *regular_args* as an `Array of Class` and *keyword_args* as an `Array of TypeDeclaration`.
#
# Its new name will be *name*.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
# The value *return_nil* will override any returned value with `nil`.
#
# The arguments *block_arg_number* and *block_return_type* can be set to an `Int` and a `Class`,
# respectively, in order to require a block argument. If *store_block_arg* is set to `true`,
# any block argument given will be stored in a cache.
macro wrap_instance_method_with_keywords(rb_interpreter, crystal_class, name, proc, keyword_args, regular_args = nil, operator = "", context = nil, return_nil = false, block_arg_number = nil, block_return_type = nil, store_block_arg = false)
{%
options = {
:context => context,
:return_nil => return_nil,
:block_arg_number => block_arg_number,
:block_return_type => block_return_type,
:store_block_arg => store_block_arg,
}
%}
Anyolite::Macro.wrap_instance_function_with_keyword_args({{rb_interpreter}}, {{crystal_class}}, {{name}}, {{proc}}, {{keyword_args}}, {{regular_args}}, operator: {{operator}}, options: {{options}})
end
# Wraps a setter into mruby.
#
# The setter *proc* (without the `=`) of the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the argument *proc_arg* as its respective `Class`.
#
# Its new name will be *name*.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
macro wrap_setter(rb_interpreter, crystal_class, name, proc, proc_arg, operator = "=", context = nil)
{% options = {:context => context} %}
Anyolite::Macro.wrap_instance_function_with_args({{rb_interpreter}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_arg}}, operator: {{operator}}, options: {{options}})
end
# Wraps a getter into mruby.
#
# The getter *proc* of the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*.
#
# Its new name will be *name*.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
macro wrap_getter(rb_interpreter, crystal_class, name, proc, operator = "", context = nil)
{% options = {:context => context} %}
Anyolite::Macro.wrap_instance_function_with_args({{rb_interpreter}}, {{crystal_class}}, {{name}}, {{proc}}, operator: {{operator}}, options: {{options}})
end
# Wraps a property into mruby.
#
# The property *proc* of the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the argument *proc_arg* as its respective `Class`.
#
# Its new name will be *name*.
#
# The values *operator_getter* and *operator_setter* will append the specified `String`
# to the final names and *context* can give the function a `Path` for resolving types correctly.
macro wrap_property(rb_interpreter, crystal_class, name, proc, proc_arg, operator_getter = "", operator_setter = "=", context = nil)
Anyolite.wrap_getter({{rb_interpreter}}, {{crystal_class}}, {{name}}, {{proc}}, operator: {{operator_getter}}, context: {{context}})
Anyolite.wrap_setter({{rb_interpreter}}, {{crystal_class}}, {{name + "="}}, {{proc}}, {{proc_arg}}, operator: {{operator_setter}}, context: {{context}})
end
# Wraps a class setter into mruby.
#
# The class setter *proc* (without the `=`) of the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the argument *proc_arg* as its respective `Class`.
#
# Its new name will be *name*.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
macro wrap_class_setter(rb_interpreter, crystal_class, name, proc, proc_arg, operator = "=", context = nil)
{% options = {:context => context} %}
Anyolite::Macro.wrap_module_function_with_args({{rb_interpreter}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_arg}}, operator: {{operator}}, options: {{options}})
end
# Wraps a class getter into mruby.
#
# The class getter *proc* of the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*.
#
# Its new name will be *name*.
#
# The value *operator* will append the specified `String`
# to the final name and *context* can give the function a `Path` for resolving types correctly.
macro wrap_class_getter(rb_interpreter, crystal_class, name, proc, operator = "", context = nil)
{% options = {:context => context} %}
Anyolite::Macro.wrap_module_function_with_args({{rb_interpreter}}, {{crystal_class}}, {{name}}, {{proc}}, operator: {{operator}}, options: {{options}})
end
# Wraps a class property into mruby.
#
# The class property *proc* of the Crystal `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the argument *proc_arg* as its respective `Class`.
#
# Its new name will be *name*.
#
# The values *operator_getter* and *operator_setter* will append the specified `String`
# to the final names and *context* can give the function a `Path` for resolving types correctly.
macro wrap_class_property(rb_interpreter, crystal_class, name, proc, proc_arg, operator_getter = "", operator_setter = "=", context = nil)
Anyolite.wrap_class_getter({{rb_interpreter}}, {{crystal_class}}, {{name}}, {{proc}}, operator: {{operator_getter}}, context: {{context}})
Anyolite.wrap_class_setter({{rb_interpreter}}, {{crystal_class}}, {{name + "="}}, {{proc}}, {{proc_arg}}, operator: {{operator_setter}}, context: {{context}})
end
# Wraps a constant value under a module into mruby.
#
# The value *crystal_value* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the name *name* and the parent module *under_module*.
macro wrap_constant(rb_interpreter, under_module, name, crystal_value)
Anyolite::RbCore.rb_define_const({{rb_interpreter}}, Anyolite::RbClassCache.get({{under_module}}), {{name}}, Anyolite::RbCast.return_value({{rb_interpreter}}.to_unsafe, {{crystal_value}}))
end
# Wraps a constant value under a class into mruby.
#
# The value *crystal_value* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the name *name* and the parent `Class` *under_class*.
macro wrap_constant_under_class(rb_interpreter, under_class, name, crystal_value)
Anyolite::RbCore.rb_define_const({{rb_interpreter}}, Anyolite::RbClassCache.get({{under_class}}), {{name}}, Anyolite::RbCast.return_value({{rb_interpreter}}.to_unsafe, {{crystal_value}}))
end
# NOTE: Annotations like SpecializeConstant are not defined for obvious reasons
# TODO: Annotations for constants are currently not obtainable with macros (?)
# Excludes the function from wrapping.
annotation Exclude; end
# Excludes the instance method given as the first argument from wrapping.
# Use it on `Object` to exclude the named method from all classes.
annotation ExcludeInstanceMethod; end
# Excludes the class method given as the first argument from wrapping.
annotation ExcludeClassMethod; end
# Excludes the constant given as the first argument from wrapping.
annotation ExcludeConstant; end
# Overrides `ExcludeInstanceMethod` on `Object`.
annotation Include; end
# Overrides `ExcludeInstanceMethod` on `Object`
# the instance method given as the first argument.
annotation IncludeInstanceMethod; end
# Excludes all definitions of this function besides this one from wrapping.
# The optional first argument overwrites the original argument array.
annotation Specialize; end
# Excludes all definitions of the instance method given as the first argument
# besides the one with the arguments given in the second argument (`nil` for none) from wrapping.
# The optional third argument overwrites the original argument array.
annotation SpecializeInstanceMethod; end
# Excludes all definitions of the class method given as the first argument
# besides the one with the arguments given in the second argument (`nil` for none) from wrapping.
# The optional third argument overwrites the original argument array.
annotation SpecializeClassMethod; end
# Renames the function to the first argument if wrapped.
annotation Rename; end
# Renames the instane method given as the first argument
# to the second argument if wrapped.
annotation RenameInstanceMethod; end
# Renames the class method given as the first argument
# to the second argument if wrapped.
annotation RenameClassMethod; end
# Renames the constant given as the first argument
# to the second argument if wrapped.
annotation RenameConstant; end
# Renames the class to the first argument if wrapped.
annotation RenameClass; end
# Renames the module to the first argument if wrapped.
annotation RenameModule; end
# Wraps all arguments of the function to positional arguments.
# The optional argument limits the number of arguments to wrap as positional
# arguments (`-1` for all arguments).
annotation WrapWithoutKeywords; end
# Wraps all arguments of the instance method given as the first argument
# to positional arguments.
# The optional seconds argument limits the number of arguments to wrap as positional
# arguments (`-1` for all arguments).
annotation WrapWithoutKeywordsInstanceMethod; end
# Wraps all arguments of the class method given as the first argument
# to positional arguments.
# The optional seconds argument limits the number of arguments to wrap as positional
# arguments (`-1` for all arguments).
annotation WrapWithoutKeywordsClassMethod; end
# Lets the function always return `nil`.
annotation ReturnNil; end
# Lets the instance method given as the first argument always return `nil`.
annotation ReturnNilInstanceMethod; end
# Lets the class method given as the first argument always return `nil`.
annotation ReturnNilClassMethod; end
# Specifies the generic type names for the following class as its argument,
# in form of an `Array` of their names.
annotation SpecifyGenericTypes; end
# Specifies the method to require a block argument with the first argument
# being the number of values yielded and the second argument the return
# type of the block.
annotation AddBlockArg; end
# Specifies the instance method given as the first argument
# to require a block argument with the second argument
# being the number of values yielded and the third argument the return
# type of the block.
annotation AddBlockArgInstanceMethod; end
# Specifies the class method given as the first argument
# to require a block argument with the second argument
# being the number of values yielded and the third argument the return
# type of the block.
annotation AddBlockArgClassMethod; end
# Instructs the function to cache an incoming Ruby block argument if given.
annotation StoreBlockArg; end
# Instructs the instance method given as the first argument to cache an incoming Ruby block argument if given.
annotation StoreBlockArgInstanceMethod; end
# Instructs the class method given as the first argument to cache an incoming Ruby block argument if given.
annotation StoreBlockArgClassMethod; end
# Forces the method to use keyword arguments (especially for operator methods) if given.
annotation ForceKeywordArg; end
# Forces the instance method given as the first argument to use
# keyword arguments (especially for operator methods) if given.
annotation ForceKeywordArgInstanceMethod; end
# Forces the class method given as the first argument to use
# keyword arguments (especially for operator methods) if given.
annotation ForceKeywordArgClassMethod; end
# The methods of the annotated class or module will not
# be wrapped with keyword arguments unless `ForceKeywordArg`
# or similar was used.
annotation NoKeywordArgs; end
# All methods of the respective class have their required arguments
# wrapped as regular arguments and their optional arguments wrapped
# as keyword arguments.
#
# The annotation can be overwritten with the respective
# `WrapWithoutKeywords` annotations for specific methods.
annotation DefaultOptionalArgsToKeywordArgs; end
# Specifies that only the directly defined methods of the respective
# class are wrapped, and no inherited methods.
annotation IgnoreAncestorMethods; end
# Wraps a whole class structure under a module into mruby.
#
# The `Class` *crystal_class* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the optional parent module *under*.
# Methods or constants to be excluded can be specified as
# `Symbol` or `String` in the `Array`
# *instance_method_exclusions* (for instance methods),
# *class_method_exclusions* (for class methods) or
# *constant_exclusions* (for constants).
#
# Enum classes can be wrapped by setting *use_enum_methods*.
# If *wrap_equality_method* is set, the `==` method will be wrapped
# automatically.
# Setting *connect_to_superclass* to `false` will force the algorithm
# to ignore any superclass.
# Setting *include_ancestor_methods* will include any methods
# from nontrivial ancestor classes.
# The option *overwrite* will iterate through all functions and
# constants again if set to `true`.
# If *verbose* is set, wrapping information will be displayed.
macro wrap_class_with_methods(rb_interpreter, crystal_class, under = nil,
instance_method_exclusions = [] of String | Symbol,
class_method_exclusions = [] of String | Symbol,
constant_exclusions = [] of String | Symbol,
use_enum_methods = false,
wrap_equality_method = false,
connect_to_superclass = true,
include_ancestor_methods = true,
overwrite = false,
verbose = false)
{% if verbose %}
{% puts ">>> Going into class #{crystal_class} under #{under}\n\n" %}
{% end %}
{% if crystal_class.is_a?(Generic) %}
{% puts "> Wrapping of generics not supported, thus skipping #{crystal_class}\e[0m\n\n" if verbose %}
{% else %}
{% resolved_class = crystal_class.resolve %}
{% new_context = crystal_class %}
{% if resolved_class.annotation(Anyolite::RenameClass) %}
{% actual_name = resolved_class.annotation(Anyolite::RenameClass)[0] %}
{% else %}
{% actual_name = crystal_class.names.last.stringify %}
{% end %}
{% if connect_to_superclass %}
{% superclass = [Reference, Object, Struct, Enum, Value, Comparable(Enum), Enumerable].includes?(resolved_class.superclass) ? nil : resolved_class.superclass %}
{% else %}
{% superclass = nil %}
{% end %}
{% if superclass %}
if !Anyolite::RbClassCache.check({{superclass.resolve}})
puts "Note: Superclass {{superclass}} to {{resolved_class}} was not wrapped before. Trying to find it..."
_needs_more_iterations.push("{{superclass}}") if _needs_more_iterations
else
{% else %}
if false
else
{% end %}
if {{overwrite}} || !Anyolite::RbClassCache.check({{resolved_class}})
Anyolite.wrap_class({{rb_interpreter}}, {{resolved_class}}, {{actual_name}}, under: {{under}}, superclass: {{superclass}})
{% if include_ancestor_methods %}
{% reversed_ancestors = [] of TypeNode %}
{% for ancestor in resolved_class.ancestors.reject { |ancestor| [Object, Reference, Struct, Enum, Value, Comparable(Enum), Enumerable].includes?(ancestor) } %}
{% reversed_ancestors = [ancestor] + reversed_ancestors %}
{% end %}
{% puts "> Ancestors for #{resolved_class}: #{reversed_ancestors}" if !reversed_ancestors.empty? && verbose %}
{% if resolved_class.annotation(Anyolite::IgnoreAncestorMethods) %}
{% puts "> Ignoring ancestors due to annotation." if !reversed_ancestors.empty? && verbose %}
{% else %}
{% for ancestor, ancestor_index in reversed_ancestors %}
{% puts "> Going into ancestor #{ancestor} for #{resolved_class}..." if verbose %}
{% later_ancestors = reversed_ancestors[ancestor_index + 1..-1] %}
Anyolite::Macro.wrap_all_instance_methods({{rb_interpreter}}, {{crystal_class}}, {{instance_method_exclusions}},
verbose: {{verbose}}, context: {{new_context}}, use_enum_methods: {{use_enum_methods}}, wrap_equality_method: {{wrap_equality_method}},
other_source: {{ancestor}}, later_ancestors: {{later_ancestors.empty? ? nil : later_ancestors}})
{% end %}
{% end %}
{% end %}
Anyolite::Macro.wrap_all_instance_methods({{rb_interpreter}}, {{crystal_class}}, {{instance_method_exclusions}},
verbose: {{verbose}}, context: {{new_context}}, use_enum_methods: {{use_enum_methods}}, wrap_equality_method: {{wrap_equality_method}})
Anyolite::Macro.wrap_all_class_methods({{rb_interpreter}}, {{crystal_class}}, {{class_method_exclusions}}, verbose: {{verbose}}, context: {{new_context}})
Anyolite::Macro.wrap_all_constants({{rb_interpreter}}, {{crystal_class}}, {{constant_exclusions}}, verbose: {{verbose}}, context: {{new_context}})
end
end
{% end %}
end
# Wraps a whole module structure under a module into mruby.
#
# The module *crystal_module* will be integrated into the `RbInterpreter` *rb_interpreter*,
# with the optional parent module *under*.
# Methods or constants to be excluded can be specified as
# `Symbol` or `String` in the `Array`
# *class_method_exclusions* (for class methods) or
# *constant_exclusions* (for constants).
# The option *overwrite* will iterate through all functions and
# constants again if set to `true`.
# If *verbose* is set, wrapping information will be displayed.
macro wrap_module_with_methods(rb_interpreter, crystal_module, under = nil,
class_method_exclusions = [] of String | Symbol,
constant_exclusions = [] of String | Symbol,
overwrite = false,
verbose = false)
{% if verbose %}
{% puts ">>> Going into module #{crystal_module} under #{under}\n\n" %}
{% end %}
{% new_context = crystal_module %}
{% if crystal_module.resolve.annotation(Anyolite::RenameModule) %}
{% actual_name = crystal_module.resolve.annotation(Anyolite::RenameModule)[0] %}
{% else %}
{% actual_name = crystal_module.names.last.stringify %}
{% end %}
if {{overwrite}} || !Anyolite::RbClassCache.check({{crystal_module.resolve}})
Anyolite.wrap_module({{rb_interpreter}}, {{crystal_module.resolve}}, {{actual_name}}, under: {{under}})
Anyolite::Macro.wrap_all_class_methods({{rb_interpreter}}, {{crystal_module}}, {{class_method_exclusions}}, verbose: {{verbose}}, context: {{new_context}})
Anyolite::Macro.wrap_all_constants({{rb_interpreter}}, {{crystal_module}}, {{constant_exclusions}}, verbose: {{verbose}}, overwrite: {{overwrite}}, context: {{new_context}})
end
end
# Wraps a whole class or module structure under a module into mruby.
#
# The class or module *crystal_module_or_class* will be integrated
# into the `RbInterpreter` *rb_interpreter*,
# with the optional parent module *under*.
# Methods or constants to be excluded can be specified as
# `Symbol` or `String` in the `Array`
# *class_method_exclusions* (for class methods) or
# *constant_exclusions* (for constants).
#
# If *wrap_equality_method* is set, the `==` method will be wrapped
# automatically.
# Setting *connect_to_superclass* to `false` will force the algorithm
# to ignore any superclass.
# Setting *include_ancestor_methods* will include any methods
# from nontrivial ancestor classes.
# The option *overwrite* will iterate through all functions and
# constants again if set to `true`.
# If *verbose* is set, wrapping information will be displayed.
macro wrap(rb_interpreter, crystal_module_or_class, under = nil,
instance_method_exclusions = [] of String | Symbol,
class_method_exclusions = [] of String | Symbol,
constant_exclusions = [] of String | Symbol,
connect_to_superclass = false,
include_ancestor_methods = true,
use_enum_methods = false,
wrap_equality_method = false,
overwrite = false,
verbose = false)
_needs_more_iterations = [] of String
%previous_iterations = [] of String
%first_run = true
while %first_run || !_needs_more_iterations.empty?
%previous_iterations = _needs_more_iterations
_needs_more_iterations = [] of String
{% if !crystal_module_or_class.is_a?(Path) %}
{% puts "\e[31m> WARNING: Object #{crystal_module_or_class} of #{crystal_module_or_class.class_name.id} is neither a class nor module, so it will be skipped\e[0m" %}
{% elsif crystal_module_or_class.resolve.module? %}
Anyolite.wrap_module_with_methods({{rb_interpreter}}, {{crystal_module_or_class}}, under: {{under}},
class_method_exclusions: {{class_method_exclusions}},
constant_exclusions: {{constant_exclusions}},
overwrite: {{overwrite}},
verbose: {{verbose}}
)
{% elsif crystal_module_or_class.resolve.class? || crystal_module_or_class.resolve.struct? %}
Anyolite.wrap_class_with_methods({{rb_interpreter}}, {{crystal_module_or_class}}, under: {{under}},
instance_method_exclusions: {{instance_method_exclusions}},
class_method_exclusions: {{class_method_exclusions}},
constant_exclusions: {{constant_exclusions}},
use_enum_methods: {{use_enum_methods}},
wrap_equality_method: {{wrap_equality_method || crystal_module_or_class.resolve.struct?}},
connect_to_superclass: {{connect_to_superclass}},
include_ancestor_methods: {{include_ancestor_methods}},
overwrite: {{overwrite}},
verbose: {{verbose}}
)
{% elsif crystal_module_or_class.resolve.union? %}
{% puts "\e[31m> WARNING: Wrapping of unions not supported, thus skipping #{crystal_module_or_class}\e[0m" %}
{% elsif crystal_module_or_class.resolve < Enum %}
Anyolite.wrap_class_with_methods({{rb_interpreter}}, {{crystal_module_or_class}}, under: {{under}},
instance_method_exclusions: {{instance_method_exclusions}},
class_method_exclusions: {{class_method_exclusions}},
constant_exclusions: {{constant_exclusions}},
use_enum_methods: true,
wrap_equality_method: true,
connect_to_superclass: {{connect_to_superclass}},
include_ancestor_methods: {{include_ancestor_methods}},
overwrite: {{overwrite}},
verbose: {{verbose}}
)
{% elsif crystal_module_or_class.resolve.is_a?(TypeNode) %}
Anyolite.wrap_class_with_methods({{rb_interpreter}}, {{crystal_module_or_class}}, under: {{under}},
instance_method_exclusions: {{instance_method_exclusions}},
class_method_exclusions: {{class_method_exclusions}},
constant_exclusions: {{constant_exclusions}},
use_enum_methods: {{use_enum_methods}},
wrap_equality_method: {{wrap_equality_method}},
connect_to_superclass: {{connect_to_superclass}},
include_ancestor_methods: {{include_ancestor_methods}},
overwrite: {{overwrite}},
verbose: {{verbose}}
)
{% else %}
{% puts "\e[31m> WARNING: Could not resolve #{crystal_module_or_class}, so it will be skipped\e[0m" %}
{% end %}
%first_run = false
if !_needs_more_iterations.empty? && _needs_more_iterations == %previous_iterations
raise "Could not wrap the following classes: #{_needs_more_iterations.inspect}"
end
end
end
end
require "./helper_classes/HelperClasses.cr"
================================================
FILE: src/Preloader.cr
================================================
module Anyolite
module Preloader
@@content = {} of String => Array(UInt8)
module AtCompiletime
# Caches the bytecode from *filename*, so it is automatically included
# into the final application.
macro load_bytecode_file(filename)
{% file_content = read_file?(filename) %}
{% if file_content %}
Anyolite::Preloader.add_content({{filename}}, {{file_content}}.bytes)
{% else %}
puts "Could not find #{filename}"
{% end %}
end
# Caches the Ruby script in *filename* directly as bytecode.
macro load_bytecode_array_from_file(filename)
{% ary = run("./BytecodeGetter.cr", filename) %}
Anyolite::Preloader.add_content({{filename}}, {{ary}}.map(&.to_u8))
end
# Converts the Ruby script in *filename* to bytecode, which is
# then stored in *target_filename*.
macro transform_script_to_bytecode(filename, target_filename)
{% run("./BytecodeCompiler.cr", filename, target_filename) %}
end
# Converts the Ruby script in *filename* to bytecode.
macro transform_script_to_bytecode_string(filename)
{% run("./BytecodeGetter.cr", filename) %}
end
end
# Caches the bytecode file *filename*
def self.add_content(filename : String, bytes : Array(UInt8))
@@content[filename] = bytes
end
# Executes the bytecode file *filename* in context of the `RbInterpreter` *rb*.
#
# If it was already cached, it will be taken from the cache instead.
def self.execute_bytecode_from_cache_or_file(rb : RbInterpreter, filename : String)
if @@content[filename]?
rb.execute_bytecode(@@content[filename])
else
rb.load_bytecode_from_file(filename)
end
end
# Converts the Ruby script in *filename* to bytecode, which is
# then stored in *target_filename*.
def self.transform_script_to_bytecode(filename : String, target_filename : String)
error_code = RbCore.transform_script_to_bytecode(filename, target_filename)
case error_code
when 1 then raise "Could not load script file #{filename}"
when 2 then raise "Error when loading script file #{filename}"
when 3 then raise "Could not write to target file #{target_filename}"
end
end
# Converts the Ruby script in *filename* to bytecode.
def self.transform_script_to_bytecode_array(filename : String)
container = RbCore.transform_script_to_bytecode_container(filename)
case container.error_code
when 1 then raise "Could not load script file #{filename}"
when 2 then raise "Error when loading script file #{filename}"
end
safe_result = String.new(container.content, container.size)
RbCore.free_bytecode_container(container)
safe_result.bytes
end
end
end
================================================
FILE: src/RbArgCache.cr
================================================
module Anyolite
module RbArgCache
@@block_cache : Deque(Pointer(RbCore::RbValue)) = Deque(Pointer(RbCore::RbValue)).new
def self.get_block_cache
if @@block_cache.size > 0
@@block_cache.last
else
nil
end
end
def self.push_block_cache(value)
@@block_cache.push(value)
end
def self.pop_block_cache
@@block_cache.pop
end
end
end
================================================
FILE: src/RbCast.cr
================================================
module Anyolite
# Module for specific casts of Crystal values into mruby values
module RbCast
# Explicit return methods
def self.return_nil
return RbCore.get_nil_value
end
def self.return_true
return RbCore.get_true_value
end
def self.return_false
return RbCore.get_false_value
end
def self.return_fixnum(value)
return RbCore.get_fixnum_value(value)
end
def self.return_bool(value)
return RbCore.get_bool_value(value ? 1 : 0)
end
def self.return_float(rb : RbCore::State*, value)
return RbCore.get_float_value(rb, value)
end
def self.return_string(rb : RbCore::State*, value)
return RbCore.get_string_value(rb, value)
end
def self.return_array(rb : RbCore::State*, value)
array_size = value.size
array_values = Pointer(RbCore::RbValue).malloc(size: array_size) do |i|
RbCast.return_value(rb, value[i])
end
return RbCore.rb_ary_new_from_values(rb, array_size, array_values)
end
def self.return_symbol(rb : RbCore::State*, value)
return RbCore.get_symbol_value_of_string(rb, value.to_s)
end
def self.return_regex(rb : RbCore::State*, value)
Anyolite::Macro.convert_regex_from_crystal_to_ruby(rb, value)
end
def self.return_hash(rb : RbCore::State*, value)
hash_size = value.size
rb_hash = RbCore.rb_hash_new(rb)
value.each do |index, element|
rb_element = RbCast.return_value(rb, element)
rb_key = RbCast.return_value(rb, index)
RbCore.rb_hash_set(rb, rb_hash, rb_key, rb_element)
end
return rb_hash
end
def self.return_struct_or_enum(rb : RbCore::State*, value : Struct | Enum)
# NOTE: Equal structs do not have the same object IDs, so they are not cached
ruby_class = RbClassCache.get(value.class)
destructor = RbTypeCache.destructor_method(typeof(value))
ptr = Pointer(Anyolite::StructWrapper(typeof(value))).malloc(size: 1, value: StructWrapper(typeof(value)).new(value))
new_ruby_object = RbCore.new_empty_object(rb, ruby_class, ptr.as(Void*), RbTypeCache.register(value.class, destructor))
RbRefTable.add(RbRefTable.get_object_id(ptr.value), ptr.as(Void*), new_ruby_object)
return new_ruby_object
end
def self.return_object(rb : RbCore::State*, value : Object)
ruby_class = RbClassCache.get(value.class)
value_id = RbRefTable.get_object_id(value)
if RbRefTable.is_registered?(value_id)
return RbRefTable.get_rb_value(value_id)
else
destructor = RbTypeCache.destructor_method(typeof(value))
ptr = Pointer(typeof(value)).malloc(size: 1, value: value)
new_ruby_object = RbCore.new_empty_object(rb, ruby_class, ptr.as(Void*), RbTypeCache.register(value.class, destructor))
RbRefTable.add(value_id, ptr.as(Void*), new_ruby_object)
return new_ruby_object
end
end
def self.return_value(rb : RbCore::State*, value : Object)
if value.is_a?(Nil)
RbCast.return_nil
elsif value.is_a?(Bool)
value ? RbCast.return_true : RbCast.return_false
elsif value.is_a?(Int)
RbCast.return_fixnum(value)
elsif value.is_a?(Float)
RbCast.return_float(rb, value)
elsif value.is_a?(Char)
RbCast.return_string(rb, value.to_s)
elsif value.is_a?(String)
RbCast.return_string(rb, value)
elsif value.is_a?(Symbol)
RbCast.return_symbol(rb, value)
elsif value.is_a?(Regex)
RbCast.return_regex(rb, value)
elsif value.is_a?(Array)
RbCast.return_array(rb, value)
elsif value.is_a?(Hash)
RbCast.return_hash(rb, value)
elsif value.is_a?(Pointer)
RbCast.return_object(rb, HelperClasses::AnyolitePointer.new(value))
elsif value.is_a?(RbRef)
value.to_unsafe
elsif value.is_a?(Struct) || value.is_a?(Enum)
RbCast.return_struct_or_enum(rb, value)
else
RbCast.return_object(rb, value)
end
end
# Class check methods
def self.check_for_undef(value : RbCore::RbValue)
RbCore.check_rb_undef(value) != 0
end
def self.check_for_nil(value : RbCore::RbValue)
RbCore.check_rb_nil(value) != 0
end
def self.check_for_true(value : RbCore::RbValue)
RbCore.check_rb_true(value) != 0
end
def self.check_for_false(value : RbCore::RbValue)
RbCore.check_rb_false(value) != 0
end
def self.check_for_bool(value : RbCore::RbValue)
RbCast.check_for_true(value) || RbCast.check_for_false(value)
end
def self.check_for_fixnum(value : RbCore::RbValue)
RbCore.check_rb_fixnum(value) != 0
end
def self.check_for_float(value : RbCore::RbValue)
RbCore.check_rb_float(value) != 0
end
def self.check_for_string(value : RbCore::RbValue)
RbCore.check_rb_string(value) != 0
end
def self.check_for_symbol(value : RbCore::RbValue)
RbCore.check_rb_symbol(value) != 0
end
def self.check_for_array(value : RbCore::RbValue)
RbCore.check_rb_array(value) != 0
end
def self.check_for_hash(value : RbCore::RbValue)
RbCore.check_rb_hash(value) != 0
end
def self.check_for_data(value : RbCore::RbValue)
RbCore.check_rb_data(value) != 0
end
def self.casting_error(rb : RbCore::State*, value, crystal_class, rescue_value)
rb_inspect_string = RbCore.rb_inspect(rb, value)
rb_class = RbCore.get_class_of_obj(rb, value)
class_name = String.new(RbCore.rb_class_name(rb, rb_class))
value_debug = RbCast.cast_to_string(rb, rb_inspect_string)
Anyolite.raise_argument_error("Could not cast value #{value_debug} of class #{class_name} to #{crystal_class}.")
rescue_value
end
def self.cast_to_nil(rb : RbCore::State*, value : RbCore::RbValue)
# NOTE: If a nil class is expected, there is no point in rejecting objects
# TODO: Maybe add an option for this
if RbCast.check_for_nil(value) || true
nil
else
RbCast.casting_error(rb, value, Nil, nil)
end
end
def self.cast_to_bool(rb : RbCore::State*, value : RbCore::RbValue)
if RbCast.check_for_true(value)
true
elsif RbCast.check_for_false(value)
false
else
RbCast.casting_error(rb, value, Bool, false)
end
end
def self.cast_to_int(rb : RbCore::State*, value : RbCore::RbValue)
if RbCast.check_for_fixnum(value)
RbCore.get_rb_fixnum(value)
else
RbCast.casting_error(rb, value, Int, 0)
end
end
def self.cast_to_float(rb : RbCore::State*, value : RbCore::RbValue)
if RbCast.check_for_float(value)
RbCore.get_rb_float(value)
elsif RbCast.check_for_fixnum(value)
RbCore.get_rb_fixnum(value).to_f
else
RbCast.casting_error(rb, value, Float, 0.0)
end
end
def self.cast_to_char(rb : RbCore::State*, value : RbCore::RbValue)
if RbCast.check_for_string(value)
str = String.new(RbCore.get_rb_string(rb, value))
# TODO: Maybe also exclude longer strings to avoid confusion?
if str.empty?
RbCast.casting_error(rb, value, Char, '\0')
else
str[0]
end
else
RbCast.casting_error(rb, value, Char, '\0')
end
end
# TODO: Maybe add an option for implicit casts?
def self.cast_to_string(rb : RbCore::State*, value : RbCore::RbValue)
if RbCast.check_for_string(value)
String.new(RbCore.get_rb_string(rb, value))
elsif RbCast.check_for_symbol(value)
String.new(RbCore.get_rb_string(rb, RbCore.rb_inspect(rb, value)))
else
RbCast.casting_error(rb, value, String, "")
end
end
macro check_custom_type(rb, value, crystal_type)
Anyolite::RbCore.rb_obj_is_kind_of({{rb}}, {{value}}, Anyolite::RbClassCache.get({{crystal_type}})) != 0
end
def self.is_undef?(value) # Excludes non-RbValue types as well
if value.is_a?(RbCore::RbValue)
Anyolite::RbCast.check_for_undef(value)
else
false
end
end
def self.convert_to_unsafe_rb_value(rb : RbCore::State*, value)
if value.is_a?(Anyolite::RbRef)
value.to_unsafe
elsif value.is_a?(Anyolite::RbCore::RbValue)
value
else
RbCast.return_value(rb, value)
end
end
# TODO: Conversions of other objects like arrays and hashes
end
end
================================================
FILE: src/RbClass.cr
================================================
module Anyolite
# Reference to a mruby class
class RbClass
@class_ptr : RbCore::RClassPtr
def initialize(@rb : RbInterpreter, @name : String, superclass : RbModule | RbClass | Nil = nil, @under : RbModule | RbClass | Nil = nil)
if superclass.is_a?(RbModule)
raise "Super class #{superclass} of #{@name} is a RbModule."
end
actual_superclass = superclass ? superclass.to_unsafe : RbCore.get_object_class(@rb)
if mod = @under
@class_ptr = RbCore.rb_define_class_under(@rb, mod, @name, actual_superclass)
else
@class_ptr = RbCore.rb_define_class(@rb, @name, actual_superclass)
end
end
def initialize(@rb : RbInterpreter, @class_ptr : RbCore::RClassPtr, @under : RbModule | RbClass | Nil = nil)
@name = String.new(RbCore.rb_class_name(@rb, @class_ptr))
end
def self.get_from_ruby_name(rb : RbInterpreter, name : String, under : RbModule | RbClass | Nil = nil)
if under
available = (RbCore.rb_class_defined_under(rb, under, name) != 0)
else
available = (RbCore.rb_class_defined(rb, name) != 0)
end
return nil if !available
if under
ruby_class = RbCore.rb_class_get_under(rb, under, name)
else
ruby_class = RbCore.rb_class_get(rb, name)
end
self.new(rb, ruby_class, under) if ruby_class
end
def to_rb_obj
RbCore.get_rb_obj_value(@class_ptr)
end
def to_unsafe
return @class_ptr
end
end
end
================================================
FILE: src/RbClassCache.cr
================================================
module Anyolite
# Cache for mruby class and module references
module RbClassCache
@@cache = {} of String => RbClass | RbModule
def self.register(crystal_class : Class, rb_class : RbClass)
@@cache[crystal_class.name] = rb_class
end
def self.register(crystal_module : Class, rb_module : RbModule)
@@cache[crystal_module.name] = rb_module
end
def self.get(n : Nil)
nil
end
def self.get(ruby_module : RbModule)
ruby_module
end
def self.get(crystal_class : Class)
if @@cache[crystal_class.name]?
return @@cache[crystal_class.name]
else
raise "Uncached class or module: #{crystal_class}"
end
end
def self.check(crystal_class : Class | RbModule | Nil)
if crystal_class.is_a?(Class)
@@cache[crystal_class.name]?
else
crystal_class
end
end
def self.reset
@@cache.clear
end
end
end
================================================
FILE: src/RbInternal.cr
================================================
{% if flag?(:anyolite_implementation_mruby_3) %}
require "./implementations/mruby/Implementation.cr"
{% elsif flag?(:anyolite_implementation_ruby_3) %}
require "./implementations/mri/Implementation.cr"
{% else %}
# Default is mruby 3
require "./implementations/mruby/Implementation.cr"
{% end %}
================================================
FILE: src/RbInterpreter.cr
================================================
module Anyolite
# Wrapper for an mruby state reference
# NOTE: Do not create more than one at a time!
class RbInterpreter
@rb_ptr : RbCore::State*
property depth : UInt32 = 0
def self.create
rb = self.new
Anyolite::HelperClasses.load_all(rb)
yield rb
rb.close
end
def initialize
@rb_ptr =
{% if flag?(:anyolite_external_ruby) %}
Pointer(Anyolite::RbCore::State).null
{% else %}
RbCore.rb_open
{% end %}
RbRefTable.set_current_interpreter(self)
end
def close
{% unless flag?(:anyolite_external_ruby) %}
RbCore.rb_close(@rb_ptr)
{% end %}
RbRefTable.reset
RbTypeCache.reset
RbClassCache.reset
end
def to_unsafe
return @rb_ptr
end
def execute_script_line(str : String, clear_error : Bool = true)
@depth += 1
value = RbCore.execute_script_line(@rb_ptr, str)
@depth -= 1
RbCore.clear_last_rb_error(@rb_ptr) if clear_error
value
end
def load_script_from_file(filename : String)
@depth += 1
RbCore.load_script_from_file(@rb_ptr, filename)
@depth -= 1
end
def execute_bytecode(bytecode : Array(UInt8))
@depth += 1
RbCore.execute_bytecode(@rb_ptr, bytecode)
@depth -= 1
end
def load_bytecode_from_file(filename : String)
@depth += 1
RbCore.load_bytecode_from_file(@rb_ptr, filename)
@depth -= 1
end
# TODO: Use internal mruby arg count in future versions
def define_method(name : String, c : RbClass | RbModule, proc : RbCore::RbFunc)
if c.is_a?(RbModule)
raise "Tried to define method #{name} for RbModule #{c}"
else
RbCore.rb_define_method(@rb_ptr, c, name, proc, 1)
end
end
def define_module_function(name : String, mod : RbModule | RbClass, proc : RbCore::RbFunc)
if mod.is_a?(RbModule)
RbCore.rb_define_module_function(@rb_ptr, mod, name, proc, 1)
else
RbCore.rb_define_class_method(@rb_ptr, mod, name, proc, 1)
end
end
def define_class_method(name : String, c : RbClass | RbModule, proc : RbCore::RbFunc)
if c.is_a?(RbModule)
RbCore.rb_define_module_function(@rb_ptr, c, name, proc, 1)
else
RbCore.rb_define_class_method(@rb_ptr, c, name, proc, 1)
end
end
end
end
================================================
FILE: src/RbModule.cr
================================================
module Anyolite
# Reference to a mruby module
class RbModule
@module_ptr : RbCore::RClassPtr
def initialize(@rb : RbInterpreter, @name : String, @under : RbModule | RbClass | Nil = nil)
if mod = @under
@module_ptr = RbCore.rb_define_module_under(@rb, mod, @name)
else
@module_ptr = RbCore.rb_define_module(@rb, @name)
end
end
def initialize(@rb : RbInterpreter, @module_ptr : RbCore::RClassPtr, @under : RbModule | RbClass | Nil = nil)
@name = String.new(RbCore.rb_class_name(@rb, @module_ptr))
end
def self.get_from_ruby_name(rb : RbInterpreter, name : String, under : RbModule | RbClass | Nil = nil)
if under
available = (RbCore.rb_module_defined_under(rb, under, name) != 0)
else
available = (RbCore.rb_module_defined(rb, name) != 0)
end
return nil if !available
if under
ruby_module = RbCore.rb_module_get_under(rb, under, name)
else
ruby_module = RbCore.rb_module_get(rb, name)
end
# Does not work yet, why?
self.new(rb, ruby_module, under) if ruby_module
end
def to_rb_obj
RbCore.get_rb_obj_value(@class_ptr)
end
def to_unsafe
return @module_ptr
end
end
end
================================================
FILE: src/RbRefTable.cr
================================================
module Anyolite
# This is a very simple approach to generate artificial references to the wrapped objects.
# Therefore, the GC won't delete the wrapped objects until necessary.
# Furthermore, this is only possible as a module due to C closure limitations.
#
# TODO: Add compilation option for ignoring entry checks
# TODO: Put the hash value union into a struct
module RbRefTable
@@content = {} of UInt64 => Tuple(Void*, Int64, RbCore::RbValue | Nil)
@@current_interpreter : RbInterpreter | Nil = nil
@@options = {
# Log every change in the reference table
:logging => false,
# Display warning messages
:warnings => true,
# Throw an exception if any warning occurs
:pedantic => true,
# If true, values with same object IDs can overwrite each other
:replace_conflicting_pointers => false,
}
def self.get(identification)
return @@content[identification][0]
end
def self.get_rb_value(identification)
if rb_value = @@content[identification][2]
return rb_value
else
raise "No ruby value given for object id #{identification}."
end
end
def self.set_current_interpreter(interpreter : RbInterpreter)
if @@current_interpreter
puts "WARNING: One interpreter was already registered and will be overwritten!" if option_active?(:warnings)
raise "Current interpreter was overwritten." if option_active?(:pedantic)
else
@@current_interpreter = interpreter
end
end
def self.check_interpreter
!!@@current_interpreter
end
def self.get_current_interpreter : RbInterpreter
if c = @@current_interpreter
c
else
raise "No interpreter instance available."
end
end
def self.add(identification, value, rb_value)
# TODO: Clean this up a bit
puts "> Added reference #{identification} -> #{value}" if option_active?(:logging)
if @@content[identification]?
if value != @@content[identification][0]
if option_active?(:replace_conflicting_pointers)
puts "WARNING: Value #{identification} replaced pointers (#{value} vs #{@@content[identification][0]})." if option_active?(:warnings)
raise "Corrupted reference table" if option_active?(:pedantic)
@@content[identification] = {value, @@content[identification][1] + 1, rb_value}
else
if rb_value == @@content[identification][2]
@@content[identification] = {@@content[identification][0], @@content[identification][1] + 1, rb_value}
else
puts "WARNING: Ruby value for #{identification} got updated." if option_active?(:warnings)
raise "Corrupted reference table" if option_active?(:pedantic)
end
end
else
@@content[identification] = {value, @@content[identification][1] + 1, rb_value}
end
else
@@content[identification] = {value, 1i64, rb_value}
end
end
def self.delete(identification)
puts "> Removed reference #{identification}" if option_active?(:logging)
if @@content[identification]?
@@content.delete(identification)
else
puts "WARNING: Tried to remove unregistered object #{identification} from reference table." if option_active?(:warnings)
raise "Corrupted reference table" if option_active?(:pedantic)
end
nil
end
def self.is_registered?(identification)
return @@content[identification]?
end
def self.may_delete?(identification)
@@content[identification][1] <= 1
end
def self.inspect
@@content.inspect
end
def self.number_of_references
@@content.size
end
def self.reset
if !@@content.empty?
puts "WARNING: Reference table is not empty (#{@@content.size} elements will be deleted)." if option_active?(:warnings)
raise "Corrupted reference table" if option_active?(:pedantic)
end
@@content.clear
@@current_interpreter = nil
end
# TODO: If a struct wrapper is given here, call the struct methods instead of the wrapper methods
def self.get_object_id(value)
if value.responds_to?(:rb_ref_id)
value.rb_ref_id.to_u64
elsif value.responds_to?(:object_id)
value.object_id.to_u64
else
value.hash.to_u64
end
end
def self.option_active?(symbol)
if @@options[symbol]?
@@options[symbol]
else
false
end
end
def self.set_option(symbol)
@@options[symbol] = true
end
def self.unset_option(symbol)
@@options[symbol] = false
end
end
end
================================================
FILE: src/RbTypeCache.cr
================================================
module Anyolite
# Cache for mruby data types, holding the destructor methods
module RbTypeCache
@@cache = {} of String => RbCore::RbDataType*
def self.register(crystal_class : Class, destructor : RbCore::RbDataFunc)
unless @@cache[crystal_class.name]?
new_type = RbCore::RbDataType.new(struct_name: crystal_class.name, dfree: destructor)
@@cache[crystal_class.name] = Pointer(RbCore::RbDataType).malloc(size: 1, value: new_type)
end
return @@cache[crystal_class.name]
end
def self.register_custom_destructor(crystal_class : Class, destructor : RbCore::RbDataFunc)
new_type = RbCore::RbDataType.new(struct_name: crystal_class.name, dfree: destructor)
Pointer(RbCore::RbDataType).malloc(size: 1, value: new_type)
end
macro destructor_method(crystal_class)
Anyolite::Macro.new_rb_data_func do
if {{crystal_class}} <= Struct || {{crystal_class}} <= Enum
%crystal_ptr = __ptr.as(Anyolite::StructWrapper({{crystal_class}})*)
%obj_id = Anyolite::RbRefTable.get_object_id(%crystal_ptr.value)
# Call optional mruby callback
if (%crystal_value = %crystal_ptr.value.content).responds_to?(:rb_finalize)
if Anyolite::RbRefTable.may_delete?(%obj_id)
%crystal_value.rb_finalize(__rb)
end
end
else
%crystal_ptr = __ptr.as({{crystal_class}}*)
%obj_id = Anyolite::RbRefTable.get_object_id(%crystal_ptr.value)
if (%crystal_value = %crystal_ptr.value).responds_to?(:rb_finalize)
if Anyolite::RbRefTable.may_delete?(%obj_id)
%crystal_value.rb_finalize(__rb)
end
end
end
# Delete the Crystal reference to this object
Anyolite::RbRefTable.delete(%obj_id)
end
end
def self.reset
@@cache.clear
end
end
end
================================================
FILE: src/helper_classes/AnyolitePointer.cr
================================================
module Anyolite
module HelperClasses
class AnyolitePointer
property ptr : Void* = Pointer(Void).null
@[Anyolite::Specialize]
def initialize
Anyolite.raise_runtime_error("Crystal pointers can not be created in Ruby")
end
def initialize(obj)
@ptr = Box.box(obj)
end
def to_s
@ptr.address.to_s
end
@[Anyolite::Exclude]
def retrieve_ptr
if !@ptr
raise "Anyolite pointer was not initialized"
else
@ptr
end
end
end
end
end
================================================
FILE: src/helper_classes/HelperClasses.cr
================================================
require "./AnyolitePointer.cr"
require "./Regex.cr"
module Anyolite
module HelperClasses
macro load_helper_class(rb, helper_class)
Anyolite.wrap({{rb}}, Anyolite::HelperClasses::{{helper_class}})
end
def self.load_all(rb)
load_helper_class(rb, AnyolitePointer)
# We don't need two conflicting Regex classes in MRI
{% unless flag?(:anyolite_implementation_ruby_3) %}
Anyolite.wrap(rb, Regex)
{% end %}
end
end
end
================================================
FILE: src/helper_classes/Regex.cr
================================================
{% if flag?(:anyolite_implementation_ruby_3) %}
{% skip_file %}
{% end %}
@[Anyolite::ExcludeConstant("SPECIAL_CHARACTERS")]
{% if compare_versions(Crystal::VERSION, "1.7.3") > 0 %}
{% if compare_versions(Crystal::VERSION, "1.9.0") > 0 %}
@[Anyolite::ExcludeInstanceMethod("match!")]
{% end %}
@[Anyolite::SpecializeInstanceMethod("initialize", [source : String, options : Options = Options::None], [source : String])]
@[Anyolite::SpecializeInstanceMethod("match", [str : String, pos : Int32 = 0, options : Regex::MatchOptions = :none], [str : String, pos : Int32 = 0])]
@[Anyolite::SpecializeInstanceMethod("matches?", [str : String, pos : Int32 = 0, options : Regex::MatchOptions = :none], [str : String, pos : Int32 = 0])]
@[Anyolite::SpecializeInstanceMethod("match_at_byte_index", [str : String, byte_index : Int32 = 0, options : Regex::MatchOptions = :none], [str : String, byte_index : Int32 = 0])]
@[Anyolite::SpecializeInstanceMethod("matches_at_byte_index?", [str : String, byte_index : Int32 = 0, options : Regex::MatchOptions = :none], [str : String, byte_index : Int32 = 0])]
{% else %}
@[Anyolite::SpecializeInstanceMethod("initialize", [source : String, options : Options = Options::None], [source : String])]
@[Anyolite::SpecializeInstanceMethod("match", [str, pos = 0, options = Regex::Options::None], [str : String, pos : Int32 = 0])]
@[Anyolite::SpecializeInstanceMethod("matches?", [str, pos = 0, options = Regex::Options::None], [str : String, pos : Int32 = 0])]
@[Anyolite::SpecializeInstanceMethod("match_at_byte_index", [str, byte_index = 0, options = Regex::Options::None], [str : String, byte_index : Int32 = 0])]
@[Anyolite::SpecializeInstanceMethod("matches_at_byte_index?", [str, byte_index = 0, options = Regex::Options::None], [str : String, byte_index : Int32 = 0])]
{% end %}
@[Anyolite::SpecializeInstanceMethod("+", [other], [other : Regex])]
@[Anyolite::SpecializeInstanceMethod("=~", [other], [other : String | Regex])]
@[Anyolite::SpecializeInstanceMethod("===", [other : String])]
@[Anyolite::ExcludeInstanceMethod("each_capture_group")]
@[Anyolite::ExcludeClassMethod("union")]
@[Anyolite::ExcludeClassMethod("append_source")]
@[Anyolite::SpecializeClassMethod("error?", [source], [source : String])]
@[Anyolite::SpecializeClassMethod("escape", [str], [str : String])]
@[Anyolite::SpecializeClassMethod("needs_escape?", [str : String], [str : String | Char])]
@[Anyolite::DefaultOptionalArgsToKeywordArgs]
@[Anyolite::RenameClass("Regexp")]
@[Anyolite::ExcludeConstant("Engine")]
@[Anyolite::ExcludeConstant("PCRE2")]
@[Anyolite::ExcludeConstant("Error")]
@[Anyolite::ExcludeConstant("Options")]
{% if compare_versions(Crystal::VERSION, "1.7.3") > 0 %}
{% if compare_versions(Crystal::VERSION, "1.9.0") > 0 %}
@[Anyolite::ExcludeClassMethod("literal")]
{% end %}
@[Anyolite::ExcludeConstant("MatchOptions")]
@[Anyolite::ExcludeConstant("CompileOptions")]
{% if compare_versions(Crystal::VERSION, "1.8.0") > 0 %}
@[Anyolite::ExcludeInstanceMethod("each_named_capture_group")]
{% end %}
{% end %}
class Regex
@[Anyolite::SpecializeInstanceMethod("[]?", [n : Int])]
@[Anyolite::SpecializeInstanceMethod("[]", [n : Int])]
@[Anyolite::SpecializeInstanceMethod("byte_begin", [n = 0], [n : Int32 = 0])]
@[Anyolite::SpecializeInstanceMethod("byte_end", [n = 0], [n : Int32 = 0])]
@[Anyolite::ExcludeInstanceMethod("begin")]
@[Anyolite::ExcludeInstanceMethod("end")]
@[Anyolite::ExcludeInstanceMethod("pretty_print")]
@[Anyolite::SpecializeInstanceMethod("to_s", [io : IO])]
@[Anyolite::DefaultOptionalArgsToKeywordArgs]
struct MatchData
{% if compare_versions(Crystal::VERSION, "1.7.3") > 0 %}
@[Anyolite::Specialize]
def initialize(@regex : ::Regex, @code : LibPCRE2::Code*, @string : String, @pos : Int32, @ovector : LibC::SizeT*, @group_size : Int32)
super
end
{% elsif compare_versions(Crystal::VERSION, "1.6.2") > 0 %}
@[Anyolite::Specialize]
def initialize(@regex : ::Regex, @code : LibPCRE::Pcre, @string : String, @pos : Int32, @ovector : Int32*, @group_size : Int32)
super
end
{% end %}
end
{% if compare_versions(Crystal::VERSION, "1.6.2") > 0 %}
def initialize(source : String, options : Options = Options::None)
super(_source: source, _options: Regex::Options::None)
end
{% end %}
def self.compile(str : String)
self.new(str)
end
def match?(str : String, pos : Int32 = 0)
matches?(str, pos)
end
def match_at_byte_index?(str : String, byte_index : Int32 = 0)
matches_at_byte_index?(str, byte_index)
end
end
================================================
FILE: src/implementations/mri/FormatString.cr
================================================
{% unless flag?(:anyolite_implementation_ruby_3) %}
{% skip_file %}
{% end %}
module Anyolite
module Macro
macro format_string(args, options = {} of Symbol => NoReturn)
{% if args %}
{% required_counter = 0 %}
{% optional_counter = 0 %}
{% optional_args = false %}
{% for arg in args %}
{% if arg.is_a?(TypeDeclaration) %}
{% if arg.value || optional_args %}
{% optional_counter += 1 %}
{% optional_args = true %}
{% else %}
{% required_counter += 1 %}
{% end %}
{% else %}
{% if optional_args %}
{% optional_counter += 1 %}
{% else %}
{% required_counter += 1 %}
{% end %}
{% end %}
{% end %}
"{{required_counter}}{{optional_counter > 0 ? optional_counter : "".id}}"
{% else %}
""
{% end %}
end
end
end
================================================
FILE: src/implementations/mri/Implementation.cr
================================================
{% unless flag?(:anyolite_implementation_ruby_3) %}
{% skip_file %}
{% end %}
require "./RbCore.cr"
require "./FormatString.cr"
ANYOLITE_INTERNAL_FLAG_USE_GENERAL_OBJECT_FORMAT_CHARS = true
module Anyolite
module Macro
macro new_rb_func(&b)
Anyolite::RbCore::RbFunc.new do |_argc, _argv, _obj|
_rb = Pointer(Anyolite::RbCore::State).null
begin
{{b.body}}
rescue ex : ArgumentError
Anyolite.raise_argument_error("#{ex.inspect_with_backtrace} (raised from Crystal)")
Anyolite::RbCast.return_nil
rescue ex : DivisionByZeroError
Anyolite.raise_runtime_error("#{ex.inspect_with_backtrace} (raised from Crystal)")
Anyolite::RbCast.return_nil
rescue ex : IndexError
Anyolite.raise_index_error("#{ex.inspect_with_backtrace} (raised from Crystal)")
Anyolite::RbCast.return_nil
rescue ex : KeyError
Anyolite.raise_key_error("#{ex.inspect_with_backtrace} (raised from Crystal)")
Anyolite::RbCast.return_nil
rescue ex : NilAssertionError
Anyolite.raise_runtime_error("#{ex.inspect_with_backtrace} (raised from Crystal)")
Anyolite::RbCast.return_nil
rescue ex : NotImplementedError
Anyolite.raise_not_implemented_error("#{ex.inspect_with_backtrace} (raised from Crystal)")
Anyolite::RbCast.return_nil
rescue ex : OverflowError
Anyolite.raise_runtime_error("#{ex.inspect_with_backtrace} (raised from Crystal)")
Anyolite::RbCast.return_nil
rescue ex : RuntimeError
Anyolite.raise_runtime_error("#{ex.inspect_with_backtrace} (raised from Crystal)")
Anyolite::RbCast.return_nil
rescue ex : TypeCastError
Anyolite.raise_type_error("#{ex.inspect_with_backtrace} (raised from Crystal)")
Anyolite::RbCast.return_nil
rescue ex
Anyolite.raise_runtime_error("#{ex.inspect_with_backtrace} (raised from Crystal)")
Anyolite::RbCast.return_nil
end
end
end
macro new_rb_data_func(&b)
Anyolite::RbCore::RbDataFunc.new do |__ptr|
__rb = Pointer(Anyolite::RbCore::State).null
{{b.body}}
end
end
macro convert_regex_from_ruby_to_crystal(rb, arg, arg_type)
{{arg_type}}.new(Anyolite.call_rb_method_of_object({{arg}}, :to_s, cast_to: String))
end
# TODO: This can potentially be done in a simpler way, but for now this is okay
# Why are you passing Regex values between Crystal and Ruby anyway?
macro convert_regex_from_crystal_to_ruby(rb, value)
%rb_class = Anyolite.get_rb_class_obj_of("Regexp")
%new_method = Anyolite::RbCore.convert_to_rb_sym({{rb}}, "new")
%args = [Anyolite::RbCast.return_string({{rb}}, value.source)]
Anyolite::RbCore.rb_funcall_argv({{rb}}, %rb_class, %new_method, 1, %args)
end
macro set_default_args_for_regular_args(args, regular_arg_tuple, number_of_args)
{% c = 0 %}
{% if args %}
{% for arg in args %}
{% if arg.is_a? TypeDeclaration %}
{% if arg.value %}
if {{number_of_args}} <= {{c}} && Anyolite::RbCast.check_for_nil({{regular_arg_tuple}}[{{c}}].value)
{{regular_arg_tuple}}[{{c}}].value = Anyolite::RbCast.return_value(_rb, {{arg.value}})
end
{% end %}
{% elsif arg.is_a? Path %}
# No default argument was given, so no action is required here
{% else %}
{% raise "Not a TypeDeclaration or a Path: #{arg} of #{arg.class_name}" %}
{% end %}
{% c += 1 %}
{% end %}
{% end %}
end
macro load_args_into_vars(args, format_string, regular_arg_tuple, block_ptr = nil)
{% if block_ptr %}
%number_of_args = Anyolite::RbCore.rb_get_args(_argc, _argv, {{format_string}}, *{{regular_arg_tuple}}, {{block_ptr}})
{% else %}
%number_of_args = Anyolite::RbCore.rb_get_args(_argc, _argv, {{format_string}}, *{{regular_arg_tuple}})
{% end %}
Anyolite::Macro.set_default_args_for_regular_args({{args}}, {{regular_arg_tuple}}, %number_of_args)
end
macro load_kw_args_into_vars(regular_args, keyword_args, format_string, regular_arg_tuple, block_ptr = nil)
%kw_ptr = Pointer(Anyolite::RbCore::RbValue).malloc(size: 1, value: Anyolite::RbCast.return_nil)
{% if block_ptr %}
%number_of_args = Anyolite::RbCore.rb_get_args(_argc, _argv, {{format_string}}, *{{regular_arg_tuple}}, %kw_ptr, {{block_ptr}})
{% else %}
%number_of_args = Anyolite::RbCore.rb_get_args(_argc, _argv, {{format_string}}, *{{regular_arg_tuple}}, %kw_ptr)
{% end %}
# TODO: Is number_of_args for the regular arg function correct here?
Anyolite::Macro.set_default_args_for_regular_args({{regular_args}}, {{regular_arg_tuple}}, %number_of_args)
# TODO: This is relatively complicated and messy, so can this be simplified?
if Anyolite::RbCast.check_for_nil(%kw_ptr.value)
%hash_key_values = [] of String
else
%rb_hash_key_values = Anyolite::RbCore.rb_hash_keys(_rb, %kw_ptr.value)
%hash_key_values = Anyolite::Macro.convert_from_ruby_to_crystal(_rb, %rb_hash_key_values, k : Array(String))
end
%return_hash = {} of Symbol => Anyolite::RbCore::RbValue
{% for keyword_arg in keyword_args %}
{% if keyword_arg.is_a? TypeDeclaration %}
if %hash_key_values.includes?(":{{keyword_arg.var.id}}")
%ruby_hash_value = Anyolite::RbCore.rb_hash_get(_rb, %kw_ptr.value, Anyolite::RbCore.get_symbol_value_of_string(_rb, "{{keyword_arg.var.id}}"))
%return_hash[:{{keyword_arg.var.id}}] = %ruby_hash_value
else
{% if !keyword_arg.value.is_a? Nop %}
%return_hash[:{{keyword_arg.var.id}}] = Anyolite::RbCast.return_value(_rb, {{keyword_arg.value}})
{% else %}
Anyolite.raise_argument_error("Keyword #{"{{keyword_arg.var.id}}"} was not defined.")
{% end %}
end
{% elsif arg.is_a? Path %}
# No default argument was given, so no action is required here
{% else %}
{% raise "Not a TypeDeclaration or a Path: #{keyword_arg} of #{keyword_arg.class_name}" %}
{% end %}
{% end %}
%return_hash
end
end
end
================================================
FILE: src/implementations/mri/RbCore.cr
================================================
{% unless flag?(:anyolite_implementation_ruby_3) %}
{% skip_file %}
{% end %}
module Anyolite
macro link_libraries
{% build_path = env("ANYOLITE_BUILD_PATH") ? env("ANYOLITE_BUILD_PATH") : "build" %}
{% if flag?(:win32) %}
{% libruby_path = "#{__DIR__}/../../../#{build_path.id}/mri/lib/x64-vcruntime140-ruby300-static.lib" %}
{% else %}
{% libruby_path = "#{__DIR__}/../../../#{build_path.id}/mri/lib/libruby-static.a" %}
{% end %}
{% if !file_exists?(libruby_path) %}
{% raise "File #{libruby_path} not found. Was MRI installed correctly?" %}
{% end %}
{% if flag?(:win32) %}
@[Link(ldflags: {{libruby_path.stringify + " msvcrt.lib Ws2_32.lib iphlpapi.lib dbghelp.lib Shell32.lib User32.lib"}})]
@[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mri/return_functions.obj\"")]
@[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mri/data_helper.obj\"")]
@[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mri/script_helper.obj\"")]
@[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mri/error_helper.obj\"")]
{% else %}
@[Link(ldflags: {{libruby_path.stringify + " -lgmp -lcrypt -lz"}})]
@[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mri/return_functions.o\" -lgmp -lcrypt -lz")]
@[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mri/data_helper.o\" -lgmp -lcrypt -lz")]
@[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mri/script_helper.o\" -lgmp -lcrypt -lz")]
@[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mri/error_helper.o\" -lgmp -lcrypt -lz")]
{% end %}
end
{% unless flag?(:anyolite_external_ruby) %}
Anyolite.link_libraries
{% end %}
lib RbCore
alias RbFunc = Proc(RbInt, RbValue*, RbValue, RbValue) # argc, argv, self -> VALUE
alias RbDataFunc = Proc(Void*, Nil)
type State = Void
alias RClassPtr = RbValue
alias RbFloat = LibC::Double
alias RbInt = Int64
alias RbBool = UInt8
alias RbSymbol = LibC::ULong
enum MrbVType
MRB_TT_FALSE = 0
MRB_TT_TRUE
MRB_TT_SYMBOL
MRB_TT_UNDEF
MRB_TT_FREE
MRB_TT_FLOAT
MRB_TT_INTEGER
MRB_TT_CPTR
MRB_TT_OBJECT
MRB_TT_CLASS
MRB_TT_MODULE
MRB_TT_ICLASS
MRB_TT_SCLASS
MRB_TT_PROC
MRB_TT_ARRAY
MRB_TT_HASH
MRB_TT_STRING
MRB_TT_RANGE
MRB_TT_EXCEPTION
MRB_TT_ENV
MRB_TT_DATA
MRB_TT_FIBER
MRB_TT_ISTRUCT
MRB_TT_BREAK
MRB_TT_COMPLEX
MRB_TT_RATIONAL
MRB_TT_MAXDEFINE
end
struct RbValue
w : LibC::ULong
end
struct RbDataType
struct_name : LibC::Char*
dmark : RbDataFunc
dfree : RbDataFunc
dize : Proc(Void*, LibC::SizeT)
dcompact : RbDataFunc
reserved : Void**
parent : RbDataType*
data : Void*
flags : RbValue
end
struct KWArgs
num : UInt32
required : UInt32
table : RbSymbol*
values : RbValue*
rest : RbValue*
end
fun rb_open = open_interpreter : State*
fun rb_close = close_interpreter(rb : State*)
fun rb_define_module = rb_define_module_helper(rb : State*, name : LibC::Char*) : RClassPtr
fun rb_define_module_under = rb_define_module_under_helper(rb : State*, under : RClassPtr, name : LibC::Char*) : RClassPtr
fun rb_define_class = rb_define_class_helper(rb : State*, name : LibC::Char*, superclass : RClassPtr) : RClassPtr
fun rb_define_class_under = rb_define_class_under_helper(rb : State*, under : RClassPtr, name : LibC::Char*, superclass : RClassPtr) : RClassPtr
fun rb_define_method = rb_define_method_helper(rb : State*, c : RClassPtr, name : LibC::Char*, func : RbFunc, aspec : UInt32) # TODO: Aspec values
fun rb_define_class_method = rb_define_class_method_helper(rb : State*, c : RClassPtr, name : LibC::Char*, func : RbFunc, aspect : UInt32)
fun rb_define_module_function = rb_define_module_function_helper(rb : State*, c : RClassPtr, name : LibC::Char*, func : RbFunc, aspect : UInt32)
fun rb_define_const = rb_define_const_helper(rb : State*, c : RClassPtr, name : LibC::Char*, val : RbValue)
# UNUSED
# fun rb_print_error = mrb_print_error(rb : State*)
fun get_last_rb_error(rb : State*) : RbValue
fun clear_last_rb_error(rb : State*)
fun rb_raise = rb_raise_helper(rb : State*, c : RClassPtr, msg : LibC::Char*)
fun rb_raise_runtime_error = rb_raise_runtime_error(rb : State*, msg : LibC::Char*)
fun rb_raise_type_error = rb_raise_type_error(rb : State*, msg : LibC::Char*)
fun rb_raise_argument_error = rb_raise_argument_error(rb : State*, msg : LibC::Char*)
fun rb_raise_index_error = rb_raise_index_error(rb : State*, msg : LibC::Char*)
fun rb_raise_range_error = rb_raise_range_error(rb : State*, msg : LibC::Char*)
fun rb_raise_name_error = rb_raise_name_error(rb : State*, msg : LibC::Char*)
fun rb_raise_script_error = rb_raise_script_error(rb : State*, msg : LibC::Char*)
fun rb_raise_not_implemented_error = rb_raise_not_implemented_error(rb : State*, msg : LibC::Char*)
fun rb_raise_key_error = rb_raise_key_error(rb : State*, msg : LibC::Char*)
fun rb_get_args = rb_scan_args(argc : RbInt, argv : RbValue*, format : LibC::Char*, ...) : RbInt
# UNUSED
# fun rb_get_argc = mrb_get_argc(rb : State*) : RbInt
# fun rb_get_argv = mrb_get_argv(rb : State*) : RbValue*
fun rb_yield = rb_yield_helper(rb : State*, value : RbValue, arg : RbValue) : RbValue
fun rb_yield_argv = rb_yield_argv_helper(rb : State*, value : RbValue, argc : RbInt, argv : RbValue*) : RbValue
fun rb_call_block = rb_yield_helper(rb : State*, value : RbValue, arg : RbValue) : RbValue
fun rb_call_block_with_args = rb_call_block_with_args_helper(rb : State*, value : RbValue, argc : RbInt, argv : RbValue*) : RbValue
fun rb_ary_ref = rb_ary_ref_helper(rb : State*, value : RbValue, pos : RbInt) : RbValue
fun rb_ary_entry(value : RbValue, offset : RbInt) : RbValue
fun array_length = rb_ary_length_helper(value : RbValue) : LibC::SizeT
fun rb_ary_new_from_values = rb_ary_new_from_values_helper(rb : State*, size : RbInt, values : RbValue*) : RbValue
fun rb_hash_new = rb_hash_new_helper(rb : State*) : RbValue
fun rb_hash_set = rb_hash_set_helper(rb : State*, hash : RbValue, key : RbValue, value : RbValue)
fun rb_hash_get = rb_hash_get_helper(rb : State*, hash : RbValue, key : RbValue) : RbValue
fun rb_hash_keys = rb_hash_keys_helper(rb : State*, hash : RbValue) : RbValue
fun rb_hash_size = rb_hash_size_helper(rb : State*, hash : RbValue) : RbInt
fun get_nil_value : RbValue
fun get_false_value : RbValue
fun get_true_value : RbValue
fun get_fixnum_value(value : RbInt) : RbValue
fun get_bool_value(value : RbBool) : RbValue
fun get_float_value(rb : State*, value : RbFloat) : RbValue
fun get_string_value(rb : State*, value : LibC::Char*) : RbValue
fun check_rb_fixnum(value : RbValue) : LibC::Int
fun check_rb_float(value : RbValue) : LibC::Int
fun check_rb_true(value : RbValue) : LibC::Int
fun check_rb_false(value : RbValue) : LibC::Int
fun check_rb_nil(value : RbValue) : LibC::Int
fun check_rb_undef(value : RbValue) : LibC::Int
fun check_rb_string(value : RbValue) : LibC::Int
fun check_rb_symbol(value : RbValue) : LibC::Int
fun check_rb_array(value : RbValue) : LibC::Int
fun check_rb_hash(value : RbValue) : LibC::Int
fun check_rb_data(value : RbValue) : LibC::Int
fun get_rb_fixnum(value : RbValue) : RbInt
fun get_rb_float(value : RbValue) : RbFloat
fun get_rb_bool(value : RbValue) : RbBool
fun get_rb_string(rb : State*, value : RbValue) : LibC::Char*
fun rb_str_to_cstr(rb : State*, value : RbValue) : LibC::Char*
fun convert_to_rb_sym = convert_to_rb_sym_helper(rb : State*, value : LibC::Char*) : RbSymbol
fun get_symbol_value_of_string(rb : State*, value : LibC::Char*) : RbValue
# Base class, not to be confused with `get_class_of_obj`
fun get_object_class(rb : State*) : RClassPtr
# UNUSED
# fun rb_obj_inspect = mrb_obj_inspect(rb : State*, value : RbValue) : RbValue
# fun rb_any_to_s = mrb_any_to_s(rb : State*, value : RbValue) : RbValue
fun rb_inspect = rb_inspect_helper(rb : State*, value : RbValue) : RbValue
fun rb_gc_register = rb_gc_register_helper(rb : State*, value : RbValue) : Void
fun rb_gc_unregister = rb_gc_unregister_helper(rb : State*, value : RbValue) : Void
fun rb_class_name = rb_class_name_helper(rb : State*, class_ptr : RClassPtr) : LibC::Char*
# UNUSED
# fun data_type(value : RbValue) : RbDataType*
# fun rb_data_get_ptr = mrb_data_get_ptr(rb : State*, obj : RbValue, type : RbDataType*) : Void*
fun set_instance_tt_as_data(ruby_class : RClassPtr) : Void
fun new_empty_object(rb : State*, ruby_class : RClassPtr, data_ptr : Void*, type : RbDataType*) : RbValue
fun set_data_ptr_and_type(ruby_object : RbValue, data : Void*, type : RbDataType*)
fun get_data_ptr(ruby_object : RbValue) : Void*
fun get_rb_obj_value(p : RClassPtr) : RbValue
fun rb_obj_is_kind_of = rb_obj_is_kind_of_helper(rb : State*, obj : RbValue, c : RClassPtr) : RbBool
fun get_class_of_obj = rb_obj_class_helper(rb : State*, obj : RbValue) : RClassPtr
fun rb_funcall_argv = rb_funcall_argv_helper(rb : State*, value : RbValue, name : RbSymbol, argc : RbInt, argv : RbValue*) : RbValue
fun rb_funcall_argv_with_block = rb_funcall_argv_with_block_helper(rb : State*, value : RbValue, name : RbSymbol, argc : RbInt, argv : RbValue*, block : RbValue) : RbValue
fun rb_respond_to = rb_respond_to_helper(rb : State*, obj : RbValue, name : RbSymbol) : RbBool
fun rb_class_get = get_constant(rb : State*, name : LibC::Char*) : RClassPtr
fun rb_class_get_under = get_constant_under(rb : State*, under : RClassPtr, name : LibC::Char*) : RClassPtr
fun rb_class_defined = does_constant_exist(rb : State*, name : LibC::Char*) : RbBool
fun rb_class_defined_under = does_constant_exist_under(rb : State*, under : RClassPtr, name : LibC::Char*) : RbBool
fun rb_module_get = get_constant(rb : State*, name : LibC::Char*) : RClassPtr
fun rb_module_get_under = get_constant_under(rb : State*, under : RClassPtr, name : LibC::Char*) : RClassPtr
fun rb_module_defined = does_constant_exist(rb : State*, name : LibC::Char*) : RbBool
fun rb_module_defined_under = does_constant_exist_under(rb : State*, under : RClassPtr, name : LibC::Char*) : RbBool
fun rb_iv_set = rb_iv_set_helper(rb : State*, obj : RbValue, sym : RbSymbol, value : RbValue) : Void
fun rb_iv_get = rb_iv_get_helper(rb : State*, obj : RbValue, sym : RbSymbol) : RbValue
fun rb_cv_set = rb_cv_set_helper(rb : State*, mod : RbValue, sym : RbSymbol, value : RbValue) : Void
fun rb_cv_get = rb_cv_get_helper(rb : State*, mod : RbValue, sym : RbSymbol) : RbValue
fun rb_gv_set = rb_gv_set_helper(rb : State*, name : LibC::Char*, value : RbValue) : Void
fun rb_gv_get = rb_gv_get_helper(rb : State*, name : LibC::Char*) : RbValue
gitextract_2zcw5nvv/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ └── problem-report.md
│ └── workflows/
│ ├── doc.yml
│ ├── mritest.yml
│ ├── test.yml
│ └── wintest.yml
├── .gitignore
├── .gitmodules
├── Changelog.md
├── LICENSE
├── README.md
├── Rakefile.rb
├── anyolite.cr
├── config_files/
│ ├── anyolite_config_external_ruby.json
│ ├── anyolite_config_mri.json
│ └── anyolite_config_mruby.json
├── examples/
│ ├── bytecode_test.rb
│ ├── hp_example.rb
│ ├── mri_test.rb
│ ├── test.rb
│ └── test_framework.rb
├── glue/
│ ├── mri/
│ │ ├── data_helper.c
│ │ ├── error_helper.c
│ │ ├── return_functions.c
│ │ └── script_helper.c
│ └── mruby/
│ ├── data_helper.c
│ ├── error_helper.c
│ ├── return_functions.c
│ └── script_helper.c
├── install.cr
├── shard.yml
├── src/
│ ├── BytecodeCompiler.cr
│ ├── BytecodeGetter.cr
│ ├── Macro.cr
│ ├── Main.cr
│ ├── Preloader.cr
│ ├── RbArgCache.cr
│ ├── RbCast.cr
│ ├── RbClass.cr
│ ├── RbClassCache.cr
│ ├── RbInternal.cr
│ ├── RbInterpreter.cr
│ ├── RbModule.cr
│ ├── RbRefTable.cr
│ ├── RbTypeCache.cr
│ ├── helper_classes/
│ │ ├── AnyolitePointer.cr
│ │ ├── HelperClasses.cr
│ │ └── Regex.cr
│ ├── implementations/
│ │ ├── mri/
│ │ │ ├── FormatString.cr
│ │ │ ├── Implementation.cr
│ │ │ └── RbCore.cr
│ │ └── mruby/
│ │ ├── FormatString.cr
│ │ ├── Implementation.cr
│ │ ├── KeywordArgStruct.cr
│ │ └── RbCore.cr
│ └── macros/
│ ├── ArgConversions.cr
│ ├── ArgTuples.cr
│ ├── FunctionCalls.cr
│ ├── FunctionGenerators.cr
│ ├── ObjectAllocations.cr
│ ├── RubyConversions.cr
│ ├── RubyTypes.cr
│ ├── UnionCasts.cr
│ ├── WrapAll.cr
│ ├── WrapMethodIndex.cr
│ └── Wrappers.cr
├── test.cr
└── utility/
└── mruby_build_config.rb
SYMBOL INDEX (160 symbols across 12 files)
FILE: Rakefile.rb
function get_value (line 4) | def get_value(env_var_name, default)
function determine_compiler (line 8) | def determine_compiler
class AnyoliteConfig (line 16) | class AnyoliteConfig
method initialize (line 31) | def initialize
method load (line 35) | def load(config_file)
method read_option (line 46) | def read_option(content, name)
method set_option (line 51) | def set_option(name, value)
FILE: examples/bytecode_test.rb
class BytecodeTestClass (line 1) | class BytecodeTestClass
method initialize (line 2) | def initialize(str)
method do_test (line 6) | def do_test(some_number)
FILE: examples/test.rb
class InheritedTest (line 371) | class InheritedTest < TestModule::Test
method initialize (line 372) | def initialize(x: 0, z: "")
class InheritedContentTest (line 379) | class InheritedContentTest < TestModule::Test::ContentTest
method initialize (line 380) | def initialize(content, another_content)
method overloaded_content (line 385) | def overloaded_content
type TestModule (line 402) | module TestModule
class Test (line 403) | class Test
method method_only_in_ruby (line 404) | def method_only_in_ruby(str, int)
method class_method_in_ruby (line 408) | def self.class_method_in_ruby(str, int)
FILE: examples/test_framework.rb
type TestFramework (line 1) | module TestFramework
function init (line 2) | def self.init
function check (line 7) | def self.check(test_no: -1, should_be: nil, &block)
function results (line 28) | def self.results(raise_if_failures: false)
FILE: glue/mri/data_helper.c
function VALUE (line 3) | extern VALUE rb_define_class_under_helper(void* rb, VALUE under, const c...
function VALUE (line 9) | extern VALUE rb_define_class_helper(void* rb, const char* name, VALUE su...
function VALUE (line 15) | extern VALUE rb_define_module_under_helper(void* rb, VALUE under, const ...
function VALUE (line 21) | extern VALUE rb_define_module_helper(void* rb, const char* name) {
function VALUE (line 27) | extern VALUE rb_define_const_helper(void* rb, VALUE under, const char* n...
function set_instance_tt_as_data (line 33) | extern void set_instance_tt_as_data(VALUE ruby_class) {
function rb_obj_is_kind_of_helper (line 40) | extern bool rb_obj_is_kind_of_helper(void* rb, VALUE object, VALUE ruby_...
function VALUE (line 46) | extern VALUE rb_obj_class_helper(void* rb, VALUE object) {
function set_data_ptr_and_type (line 70) | extern void set_data_ptr_and_type(VALUE ruby_object, void* data, struct ...
function VALUE (line 79) | extern VALUE new_empty_object(void* rb, VALUE ruby_class, void* data_ptr...
function rb_define_method_helper (line 85) | extern void rb_define_method_helper(void* rb, VALUE ruby_class, const ch...
function rb_define_class_method_helper (line 91) | extern void rb_define_class_method_helper(void* rb, VALUE ruby_class, co...
function rb_define_module_function_helper (line 97) | extern void rb_define_module_function_helper(void* rb, VALUE ruby_module...
function VALUE (line 103) | extern VALUE rb_inspect_helper(void* rb, VALUE value) {
function VALUE (line 109) | extern VALUE rb_hash_new_helper(void* rb) {
function rb_hash_set_helper (line 115) | extern void rb_hash_set_helper(void* rb, VALUE hash, VALUE key, VALUE va...
function VALUE (line 121) | extern VALUE rb_hash_get_helper(void* rb, VALUE hash, VALUE key) {
function VALUE (line 127) | extern VALUE rb_hash_keys_helper(void* rb, VALUE hash) {
function rb_hash_size_helper (line 134) | extern int rb_hash_size_helper(void* rb, VALUE hash) {
function VALUE (line 140) | extern VALUE convert_to_rb_sym_helper(void* rb, const char* value) {
function VALUE (line 146) | extern VALUE rb_ary_ref_helper(void* rb, VALUE ary, int pos) {
function rb_ary_length_helper (line 152) | extern size_t rb_ary_length_helper(VALUE ary) {
function VALUE (line 159) | extern VALUE rb_ary_new_from_values_helper(void* rb, int size, VALUE* va...
function rb_gc_register_helper (line 165) | extern void rb_gc_register_helper(void* rb, VALUE value) {
function rb_gc_unregister_helper (line 171) | extern void rb_gc_unregister_helper(void* rb, VALUE value) {
function VALUE (line 177) | extern VALUE rb_yield_helper(void* rb, VALUE value, VALUE arg) {
function VALUE (line 183) | extern VALUE rb_yield_argv_helper(void* rb, VALUE value, int argc, VALUE...
function VALUE (line 189) | extern VALUE rb_call_block_helper(void* rb, VALUE value, VALUE arg) {
function VALUE (line 195) | extern VALUE rb_call_block_with_args_helper(void* rb, VALUE value, int a...
function rb_respond_to_helper (line 201) | extern bool rb_respond_to_helper(void* rb, VALUE obj, ID name) {
function VALUE (line 207) | extern VALUE get_rb_obj_value(VALUE obj) {
function VALUE (line 213) | extern VALUE rb_funcall_argv_helper(void *rb, VALUE value, ID name, int ...
function VALUE (line 219) | extern VALUE rb_funcall_argv_with_block_helper(void *rb, VALUE value, ID...
function VALUE (line 225) | extern VALUE rb_iv_get_helper(void* rb, VALUE obj, ID sym) {
function rb_iv_set_helper (line 231) | extern void rb_iv_set_helper(void* rb, VALUE obj, ID sym, VALUE value) {
function VALUE (line 237) | extern VALUE rb_cv_get_helper(void* rb, VALUE mod, ID sym) {
function rb_cv_set_helper (line 243) | extern void rb_cv_set_helper(void* rb, VALUE mod, ID sym, VALUE value) {
function VALUE (line 249) | extern VALUE rb_gv_get_helper(void* rb, const char* name) {
function rb_gv_set_helper (line 255) | extern void rb_gv_set_helper(void* rb, const char* name, VALUE value) {
function does_constant_exist_under (line 261) | extern bool does_constant_exist_under(void* rb, VALUE under, const char*...
function does_constant_exist (line 267) | extern bool does_constant_exist(void* rb, const char* name) {
function VALUE (line 273) | extern VALUE get_constant_under(void* rb, VALUE under, const char* name) {
function VALUE (line 279) | extern VALUE get_constant(void* rb, const char* name) {
function VALUE (line 285) | extern VALUE rb_undef_method_helper(void* rb, VALUE mod, const char* nam...
FILE: glue/mri/error_helper.c
function rb_raise_runtime_error (line 3) | extern void rb_raise_runtime_error(void* rb, const char* msg) {
function rb_raise_type_error (line 9) | extern void rb_raise_type_error(void* rb, const char* msg) {
function rb_raise_argument_error (line 15) | extern void rb_raise_argument_error(void* rb, const char* msg) {
function rb_raise_index_error (line 21) | extern void rb_raise_index_error(void* rb, const char* msg) {
function rb_raise_range_error (line 27) | extern void rb_raise_range_error(void* rb, const char* msg) {
function rb_raise_name_error (line 33) | extern void rb_raise_name_error(void* rb, const char* msg) {
function rb_raise_script_error (line 39) | extern void rb_raise_script_error(void* rb, const char* msg) {
function rb_raise_not_implemented_error (line 45) | extern void rb_raise_not_implemented_error(void* rb, const char* msg) {
function rb_raise_key_error (line 51) | extern void rb_raise_key_error(void* rb, const char* msg) {
function rb_raise_helper (line 57) | extern void rb_raise_helper(void* rb, VALUE exc, const char* msg) {
function clear_last_rb_error (line 63) | extern void clear_last_rb_error(void* rb) {
function VALUE (line 69) | extern VALUE get_last_rb_error(void* rb) {
FILE: glue/mri/return_functions.c
function VALUE (line 3) | extern VALUE get_object_class(void* rb) {
function VALUE (line 9) | extern VALUE get_nil_value() {
function VALUE (line 15) | extern VALUE get_false_value() {
function VALUE (line 21) | extern VALUE get_true_value() {
function VALUE (line 27) | extern VALUE get_fixnum_value(int value) {
function VALUE (line 33) | extern VALUE get_bool_value(bool value) {
function VALUE (line 39) | extern VALUE get_float_value(void* mrb, double value) {
function VALUE (line 45) | extern VALUE get_string_value(void* mrb, char* value) {
function VALUE (line 51) | extern VALUE get_symbol_value_of_string(void* mrb, char* value) {
function check_rb_fixnum (line 58) | extern int check_rb_fixnum(VALUE value) {
function check_rb_float (line 64) | extern int check_rb_float(VALUE value) {
function check_rb_true (line 70) | extern int check_rb_true(VALUE value) {
function check_rb_false (line 76) | extern int check_rb_false(VALUE value) {
function check_rb_nil (line 82) | extern int check_rb_nil(VALUE value) {
function check_rb_undef (line 88) | extern int check_rb_undef(VALUE value) {
function check_rb_string (line 94) | extern int check_rb_string(VALUE value) {
function check_rb_symbol (line 100) | extern int check_rb_symbol(VALUE value) {
function check_rb_array (line 106) | extern int check_rb_array(VALUE value) {
function check_rb_hash (line 112) | extern int check_rb_hash(VALUE value) {
function check_rb_data (line 118) | extern int check_rb_data(VALUE value) {
function get_rb_fixnum (line 124) | extern int get_rb_fixnum(VALUE value) {
function get_rb_float (line 130) | extern double get_rb_float(VALUE value) {
function get_rb_bool (line 136) | extern bool get_rb_bool(VALUE value) {
FILE: glue/mri/script_helper.c
function close_interpreter (line 23) | extern void close_interpreter(void* rb) {
function load_script_from_file (line 39) | extern void load_script_from_file(void* rb, const char* filename) {
function VALUE (line 82) | extern VALUE execute_script_line(void* rb, const char* text) {
FILE: glue/mruby/data_helper.c
function mrb_data_type (line 9) | extern const mrb_data_type* data_type(mrb_value value) {
function set_instance_tt_as_data (line 15) | extern void set_instance_tt_as_data(struct RClass* ruby_class) {
function mrb_value (line 21) | extern mrb_value new_empty_object(mrb_state* mrb, struct RClass* ruby_cl...
function set_data_ptr_and_type (line 33) | extern void set_data_ptr_and_type(mrb_value ruby_object, void* data, mrb...
type RClass (line 40) | struct RClass
function mrb_sym (line 46) | extern mrb_sym convert_to_mrb_sym(mrb_state* mrb, const char* str) {
function array_length (line 52) | extern size_t array_length(mrb_value array) {
function mrb_value (line 58) | extern mrb_value get_mrb_obj_value(void* p) {
function mrb_value (line 64) | extern mrb_value mrb_gv_get_helper(mrb_state* mrb, const char* name) {
function mrb_gv_set_helper (line 71) | extern void mrb_gv_set_helper(mrb_state* mrb, const char* name, mrb_valu...
FILE: glue/mruby/error_helper.c
function mrb_raise_runtime_error (line 3) | extern void mrb_raise_runtime_error(mrb_state* mrb, const char* msg) {
function mrb_raise_type_error (line 9) | extern void mrb_raise_type_error(mrb_state* mrb, const char* msg) {
function mrb_raise_argument_error (line 15) | extern void mrb_raise_argument_error(mrb_state* mrb, const char* msg) {
function mrb_raise_index_error (line 21) | extern void mrb_raise_index_error(mrb_state* mrb, const char* msg) {
function mrb_raise_range_error (line 27) | extern void mrb_raise_range_error(mrb_state* mrb, const char* msg) {
function mrb_raise_name_error (line 33) | extern void mrb_raise_name_error(mrb_state* mrb, const char* msg) {
function mrb_raise_script_error (line 39) | extern void mrb_raise_script_error(mrb_state* mrb, const char* msg) {
function mrb_raise_not_implemented_error (line 45) | extern void mrb_raise_not_implemented_error(mrb_state* mrb, const char* ...
function mrb_raise_key_error (line 51) | extern void mrb_raise_key_error(mrb_state* mrb, const char* msg) {
function clear_last_mrb_error (line 57) | extern void clear_last_mrb_error(mrb_state* mrb) {
function mrb_value (line 63) | extern mrb_value get_last_mrb_error(mrb_state* mrb) {
FILE: glue/mruby/return_functions.c
type RClass (line 7) | struct RClass
function mrb_value (line 13) | extern mrb_value get_nil_value() {
function mrb_value (line 19) | extern mrb_value get_false_value() {
function mrb_value (line 25) | extern mrb_value get_true_value() {
function mrb_value (line 31) | extern mrb_value get_fixnum_value(mrb_int value) {
function mrb_value (line 37) | extern mrb_value get_bool_value(mrb_bool value) {
function mrb_value (line 43) | extern mrb_value get_float_value(mrb_state* mrb, mrb_float value) {
function mrb_value (line 49) | extern mrb_value get_string_value(mrb_state* mrb, char* value) {
function mrb_value (line 55) | extern mrb_value get_symbol_value_of_string(mrb_state* mrb, char* value) {
function check_mrb_fixnum (line 62) | extern int check_mrb_fixnum(mrb_value value) {
function check_mrb_float (line 68) | extern int check_mrb_float(mrb_value value) {
function check_mrb_true (line 74) | extern int check_mrb_true(mrb_value value) {
function check_mrb_false (line 80) | extern int check_mrb_false(mrb_value value) {
function check_mrb_nil (line 86) | extern int check_mrb_nil(mrb_value value) {
function check_mrb_undef (line 92) | extern int check_mrb_undef(mrb_value value) {
function check_mrb_string (line 98) | extern int check_mrb_string(mrb_value value) {
function check_mrb_symbol (line 104) | extern int check_mrb_symbol(mrb_value value) {
function check_mrb_array (line 110) | extern int check_mrb_array(mrb_value value) {
function check_mrb_hash (line 116) | extern int check_mrb_hash(mrb_value value) {
function check_mrb_data (line 122) | extern int check_mrb_data(mrb_value value) {
function mrb_int (line 128) | extern mrb_int get_mrb_fixnum(mrb_value value) {
function mrb_float (line 134) | extern mrb_float get_mrb_float(mrb_value value) {
function mrb_bool (line 140) | extern mrb_bool get_mrb_bool(mrb_value value) {
FILE: glue/mruby/script_helper.c
function mrb_value (line 9) | extern mrb_value load_script_from_file(mrb_state* mrb, const char* filen...
function mrb_value (line 36) | extern mrb_value execute_script_line(mrb_state* mrb, const char* str) {
function mrb_value (line 53) | extern mrb_value load_bytecode_from_file(mrb_state* mrb, const char* fil...
function mrb_value (line 74) | extern mrb_value execute_bytecode(mrb_state* mrb, const uint8_t* bytecod...
function transform_script_to_bytecode (line 84) | extern int transform_script_to_bytecode(const char* filename, const char...
type bytecode_container_t (line 130) | typedef struct bytecode_container {
function bytecode_container_t (line 139) | extern bytecode_container_t transform_script_to_bytecode_container(const...
function bytecode_container_t (line 184) | extern bytecode_container_t transform_proc_to_bytecode_container(mrb_sta...
function free_bytecode_container (line 203) | extern void free_bytecode_container(bytecode_container_t container) {
function mrb_gc_arena_save_helper (line 209) | extern int mrb_gc_arena_save_helper(mrb_state* mrb) {
function mrb_gc_arena_restore_helper (line 216) | extern void mrb_gc_arena_restore_helper(mrb_state* mrb, int idx) {
Condensed preview — 67 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (372K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/problem-report.md",
"chars": 1340,
"preview": "---\nname: Problem report\nabout: Something may not work as inteded or requires fixing\ntitle: ''\nlabels: ''\nassignees: ''\n"
},
{
"path": ".github/workflows/doc.yml",
"chars": 503,
"preview": "name: Doc\n\non:\n release:\n types: [published, edited, prereleased]\n\njobs:\n build:\n\n runs-on: ubuntu-latest\n\n s"
},
{
"path": ".github/workflows/mritest.yml",
"chars": 660,
"preview": "name: MRITest\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n schedule:\n - cron: '45 03 * * "
},
{
"path": ".github/workflows/test.yml",
"chars": 476,
"preview": "name: Test\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n schedule:\n - cron: '45 03 * * 6'\n"
},
{
"path": ".github/workflows/wintest.yml",
"chars": 694,
"preview": "name: WinTest\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n schedule:\n - cron: '45 03 * * "
},
{
"path": ".gitignore",
"chars": 112,
"preview": "build/\r\nutility/mruby_build_config.rb.lock\r\nthird_party/\r\ndocs/\r\nexamples/bytecode_test.mrb\r\n.vscode/\r\n.crystal/"
},
{
"path": ".gitmodules",
"chars": 0,
"preview": ""
},
{
"path": "Changelog.md",
"chars": 18698,
"preview": "# Changelog\r\n\r\n## Releases\r\n\r\n### Version 1.1.1\r\n\r\nNOTE: This version requires recompilation of mruby and the C files.\r\n"
},
{
"path": "LICENSE",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2020 Anyolite\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "README.md",
"chars": 6477,
"preview": "# Anyolite\r\n\r\nAnyolite is a Crystal shard which adds a fully functional mruby (or even regular Ruby) interpreter to Crys"
},
{
"path": "Rakefile.rb",
"chars": 8722,
"preview": "require 'fileutils'\r\nrequire 'json'\r\n\r\ndef get_value(env_var_name, default)\r\n ENV[env_var_name] ? ENV[env_var_name] :"
},
{
"path": "anyolite.cr",
"chars": 24,
"preview": "require \"./src/Main.cr\"\n"
},
{
"path": "config_files/anyolite_config_external_ruby.json",
"chars": 204,
"preview": "{\r\n \"implementation\": \"mri\",\r\n \"build_path\": \"build\",\r\n \"rb_fork\": \"___EXTERNAL___\",\r\n \"rb_release\": \"\",\r\n \"rb_mino"
},
{
"path": "config_files/anyolite_config_mri.json",
"chars": 229,
"preview": "{\r\n \"implementation\": \"mri\",\r\n \"build_path\": \"build\",\r\n \"rb_fork\": \"https://github.com/ruby/ruby\",\r\n \"rb_release\": \""
},
{
"path": "config_files/anyolite_config_mruby.json",
"chars": 263,
"preview": "{\r\n \"implementation\": \"mruby\",\r\n \"build_path\": \"build\",\r\n \"rb_fork\": \"https://github.com/mruby/mruby\",\r\n \"rb_release"
},
{
"path": "examples/bytecode_test.rb",
"chars": 260,
"preview": "class BytecodeTestClass\r\n def initialize(str)\r\n @str = str\r\n end\r\n\r\n def do_test(some_number)\r\n ret_array = [@s"
},
{
"path": "examples/hp_example.rb",
"chars": 270,
"preview": "a = RPGTest::Entity.new(hp: 20)\r\na.damage(diff: 13)\r\nputs a.hp\r\n\r\nb = RPGTest::Entity.new(hp: 10)\r\na.absorb_hp_from(othe"
},
{
"path": "examples/mri_test.rb",
"chars": 106,
"preview": "require_relative \"./hp_example.rb\"\r\nrequire_relative \"./test_framework.rb\"\r\nrequire_relative \"./test.rb\"\r\n"
},
{
"path": "examples/test.rb",
"chars": 20562,
"preview": "begin\r\n puts \"Initiate testing...\"\r\n\r\n TestFramework.init\r\n\r\n start_time = Time.now\r\n\r\n # Testing bytecode if enable"
},
{
"path": "examples/test_framework.rb",
"chars": 1188,
"preview": "module TestFramework\r\n def self.init\r\n @@failed_tests = []\r\n @@test_count = 0\r\n end\r\n\r\n def self.check(test_no:"
},
{
"path": "glue/mri/data_helper.c",
"chars": 6518,
"preview": "#include <ruby.h>\r\n\r\nextern VALUE rb_define_class_under_helper(void* rb, VALUE under, const char* name, VALUE superclass"
},
{
"path": "glue/mri/error_helper.c",
"chars": 1300,
"preview": "#include <ruby.h>\r\n\r\nextern void rb_raise_runtime_error(void* rb, const char* msg) {\r\n\r\n rb_raise(rb_eRuntimeError, \"%s"
},
{
"path": "glue/mri/return_functions.c",
"chars": 2094,
"preview": "#include <ruby.h>\r\n\r\nextern VALUE get_object_class(void* rb) {\r\n\r\n return rb_cObject;\r\n\r\n}\r\n\r\nextern VALUE get_nil_va"
},
{
"path": "glue/mri/script_helper.c",
"chars": 1755,
"preview": "#include <ruby.h>\r\n\r\nextern void* open_interpreter(void) {\r\n\r\n static int first_run = 1;\r\n\r\n if(!first_run) {\r\n\r\n p"
},
{
"path": "glue/mruby/data_helper.c",
"chars": 1669,
"preview": "#include <mruby.h>\r\n#include <mruby/class.h>\r\n#include <mruby/data.h>\r\n#include <mruby/array.h>\r\n#include <mruby/string."
},
{
"path": "glue/mruby/error_helper.c",
"chars": 1277,
"preview": "#include <mruby.h>\r\n\r\nextern void mrb_raise_runtime_error(mrb_state* mrb, const char* msg) {\r\n\r\n mrb_raise(mrb, E_RUNTI"
},
{
"path": "glue/mruby/return_functions.c",
"chars": 2422,
"preview": "#include <mruby.h>\r\n#include <mruby/class.h>\r\n#include <mruby/data.h>\r\n#include <mruby/string.h>\r\n#include <string.h>\r\n\r"
},
{
"path": "glue/mruby/script_helper.c",
"chars": 4865,
"preview": "#include <mruby.h>\r\n#include <mruby/compile.h>\r\n#include <mruby/dump.h>\r\n#include <mruby/proc.h>\r\n#include <mruby/intern"
},
{
"path": "install.cr",
"chars": 378,
"preview": "command = {% if flag?(:win32) %}\n # TODO: Is there a better solution?\n \"cmd /C rake build_shard\"\n{% else %}\n \"rake bu"
},
{
"path": "shard.yml",
"chars": 251,
"preview": "name: anyolite\nversion: 1.1.1\n\nauthors:\n - Hadeweka\n\ndescription: |\n A library which allows for binding crystal classe"
},
{
"path": "src/BytecodeCompiler.cr",
"chars": 88,
"preview": "require \"./Main.cr\"\n\nAnyolite::Preloader.transform_script_to_bytecode(ARGV[0], ARGV[1])\n"
},
{
"path": "src/BytecodeGetter.cr",
"chars": 90,
"preview": "require \"./Main.cr\"\n\nputs Anyolite::Preloader.transform_script_to_bytecode_array(ARGV[0])\n"
},
{
"path": "src/Macro.cr",
"chars": 513,
"preview": "module Anyolite\n # Helper methods which should not be used for trivial cases in the final version\n module Macro\n end\n"
},
{
"path": "src/Main.cr",
"chars": 58788,
"preview": "require \"./RbInternal.cr\"\r\n\r\nrequire \"./RbInterpreter.cr\"\r\nrequire \"./RbClass.cr\"\r\nrequire \"./RbCast.cr\"\r\nrequire \"./Mac"
},
{
"path": "src/Preloader.cr",
"chars": 2842,
"preview": "module Anyolite\n module Preloader\n @@content = {} of String => Array(UInt8)\n\n module AtCompiletime\n # Caches"
},
{
"path": "src/RbArgCache.cr",
"chars": 406,
"preview": "module Anyolite\n module RbArgCache\n @@block_cache : Deque(Pointer(RbCore::RbValue)) = Deque(Pointer(RbCore::RbValue)"
},
{
"path": "src/RbCast.cr",
"chars": 8515,
"preview": "module Anyolite\n # Module for specific casts of Crystal values into mruby values\n module RbCast\n # Explicit return "
},
{
"path": "src/RbClass.cr",
"chars": 1501,
"preview": "module Anyolite\n # Reference to a mruby class\n class RbClass\n @class_ptr : RbCore::RClassPtr\n\n def initialize(@r"
},
{
"path": "src/RbClassCache.cr",
"chars": 943,
"preview": "module Anyolite\n # Cache for mruby class and module references\n module RbClassCache\n @@cache = {} of String => RbCl"
},
{
"path": "src/RbInternal.cr",
"chars": 304,
"preview": "{% if flag?(:anyolite_implementation_mruby_3) %}\n require \"./implementations/mruby/Implementation.cr\"\n{% elsif flag?(:a"
},
{
"path": "src/RbInterpreter.cr",
"chars": 2474,
"preview": "module Anyolite\r\n # Wrapper for an mruby state reference\r\n # NOTE: Do not create more than one at a time!\r\n class RbI"
},
{
"path": "src/RbModule.cr",
"chars": 1261,
"preview": "module Anyolite\n # Reference to a mruby module\n class RbModule\n @module_ptr : RbCore::RClassPtr\n\n def initialize"
},
{
"path": "src/RbRefTable.cr",
"chars": 4873,
"preview": "module Anyolite\r\n # This is a very simple approach to generate artificial references to the wrapped objects.\r\n # There"
},
{
"path": "src/RbTypeCache.cr",
"chars": 1900,
"preview": "module Anyolite\n # Cache for mruby data types, holding the destructor methods\n module RbTypeCache\n @@cache = {} of "
},
{
"path": "src/helper_classes/AnyolitePointer.cr",
"chars": 567,
"preview": "module Anyolite\n module HelperClasses\n class AnyolitePointer\n property ptr : Void* = Pointer(Void).null\n\n "
},
{
"path": "src/helper_classes/HelperClasses.cr",
"chars": 474,
"preview": "require \"./AnyolitePointer.cr\"\nrequire \"./Regex.cr\"\n\nmodule Anyolite\n module HelperClasses\n macro load_helper_class("
},
{
"path": "src/helper_classes/Regex.cr",
"chars": 4721,
"preview": "{% if flag?(:anyolite_implementation_ruby_3) %}\r\n {% skip_file %}\r\n{% end %}\r\n\r\n@[Anyolite::ExcludeConstant(\"SPECIAL_CH"
},
{
"path": "src/implementations/mri/FormatString.cr",
"chars": 1003,
"preview": "{% unless flag?(:anyolite_implementation_ruby_3) %}\r\n {% skip_file %}\r\n{% end %}\r\n\r\nmodule Anyolite\r\n module Macro\r\n "
},
{
"path": "src/implementations/mri/Implementation.cr",
"chars": 6404,
"preview": "{% unless flag?(:anyolite_implementation_ruby_3) %}\n {% skip_file %}\n{% end %}\n\nrequire \"./RbCore.cr\"\nrequire \"./Format"
},
{
"path": "src/implementations/mri/RbCore.cr",
"chars": 12157,
"preview": "{% unless flag?(:anyolite_implementation_ruby_3) %}\r\n {% skip_file %}\r\n{% end %}\r\n\r\nmodule Anyolite\r\n macro link_libra"
},
{
"path": "src/implementations/mruby/FormatString.cr",
"chars": 2600,
"preview": "module Anyolite\r\n module Macro\r\n macro format_string(args, options = {} of Symbol => NoReturn)\r\n \"\" +\r\n {%"
},
{
"path": "src/implementations/mruby/Implementation.cr",
"chars": 3350,
"preview": "require \"./RbCore.cr\"\nrequire \"./FormatString.cr\"\nrequire \"./KeywordArgStruct.cr\"\n\n{% if flag?(:use_general_object_forma"
},
{
"path": "src/implementations/mruby/KeywordArgStruct.cr",
"chars": 833,
"preview": "module Anyolite\n module Macro\n macro generate_keyword_argument_struct(rb_interpreter, keyword_args)\n %kw_names "
},
{
"path": "src/implementations/mruby/RbCore.cr",
"chars": 13206,
"preview": "module Anyolite\r\n macro link_libraries\r\n {% build_path = env(\"ANYOLITE_BUILD_PATH\") ? env(\"ANYOLITE_BUILD_PATH\") : \""
},
{
"path": "src/macros/ArgConversions.cr",
"chars": 18894,
"preview": "module Anyolite\r\n module Macro\r\n macro get_raw_args(rb, regular_args, context = nil)\r\n {% options = {:context ="
},
{
"path": "src/macros/ArgTuples.cr",
"chars": 2431,
"preview": "module Anyolite\r\n module Macro\r\n macro generate_arg_tuple(rb, args, options = {} of Symbol => NoReturn)\r\n Tuple"
},
{
"path": "src/macros/FunctionCalls.cr",
"chars": 12195,
"preview": "module Anyolite\r\n module Macro\r\n macro call_and_return(rb, proc, regular_args, converted_args, operator = \"\", option"
},
{
"path": "src/macros/FunctionGenerators.cr",
"chars": 2300,
"preview": "module Anyolite\n module Macro\n macro add_default_constructor(rb_interpreter, crystal_class, verbose)\n {% puts \""
},
{
"path": "src/macros/ObjectAllocations.cr",
"chars": 1634,
"preview": "module Anyolite\n module Macro\n macro allocate_constructed_object(rb, crystal_class, obj, new_obj)\n # Call initi"
},
{
"path": "src/macros/RubyConversions.cr",
"chars": 2632,
"preview": "module Anyolite\r\n module Macro\r\n macro convert_from_ruby_object(rb, obj, crystal_type)\r\n if !Anyolite::RbCast.c"
},
{
"path": "src/macros/RubyTypes.cr",
"chars": 4546,
"preview": "module Anyolite\r\n module Macro\r\n macro type_in_ruby(type, options = {} of Symbol => NoReturn)\r\n {% if type.is_a"
},
{
"path": "src/macros/UnionCasts.cr",
"chars": 8143,
"preview": "module Anyolite\r\n module Macro\r\n macro cast_to_union_value(rb, value, types, options = {} of Symbol => NoReturn, deb"
},
{
"path": "src/macros/WrapAll.cr",
"chars": 29108,
"preview": "module Anyolite\r\n module Macro\r\n macro wrap_all_instance_methods(rb_interpreter, crystal_class, exclusions, verbose,"
},
{
"path": "src/macros/WrapMethodIndex.cr",
"chars": 10917,
"preview": "module Anyolite\r\n module Macro\r\n macro wrap_method_index(rb_interpreter, crystal_class, method_index, ruby_name,\r\n "
},
{
"path": "src/macros/Wrappers.cr",
"chars": 28040,
"preview": "module Anyolite\r\n module Macro\r\n macro wrap_module_function_with_args(rb_interpreter, under_module, name, proc, regu"
},
{
"path": "test.cr",
"chars": 14947,
"preview": "require \"./anyolite.cr\"\n\n# TODO: Pass flags to temporary executable\n{% unless flag?(:anyolite_implementation_ruby_3) %}\n"
},
{
"path": "utility/mruby_build_config.rb",
"chars": 507,
"preview": "MRuby::Build.new do |conf|\r\n\r\n if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']\r\n toolchain :visualcpp\r\n # NO"
}
]
About this extraction
This page contains the full source code of the Anyolite/anyolite GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 67 files (342.3 KB), approximately 91.5k tokens, and a symbol index with 160 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.