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. ![Test](https://github.com/Anyolite/anyolite/workflows/Test/badge.svg) ![Release](https://img.shields.io/github/v/release/Anyolite/anyolite) ![ReleaseDate](https://img.shields.io/github/release-date/Anyolite/anyolite) ![License](https://img.shields.io/github/license/Anyolite/anyolite) # 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 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 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 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 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 #include #include #include #include #include #include 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 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 #include #include #include #include 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 #include #include #include #include #include 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 fun rb_undef_method = rb_undef_method_helper(rb : State*, class_ptr : RClassPtr, name : LibC::Char*) : Void fun rb_undef_class_method = rb_undef_method_helper(rb : State*, class_ptr : RClassPtr, name : LibC::Char*) : Void fun load_script_from_file(rb : State*, filename : LibC::Char*) : Void fun execute_script_line(rb : State*, str : LibC::Char*) : RbValue # UNUSED # fun execute_bytecode(rb : State*, bytecode : UInt8*) : RbValue # fun load_bytecode_from_file(rb : State*, filename : LibC::Char*) : RbValue # fun transform_script_to_bytecode(filename : LibC::Char*, target_filename : LibC::Char*) : LibC::Int end end ================================================ FILE: src/implementations/mruby/FormatString.cr ================================================ module Anyolite module Macro macro format_string(args, options = {} of Symbol => NoReturn) "" + {% if args %} {% for arg in args %} Anyolite::Macro.format_char({{arg}}, options: {{options}}) + {% end %} {% end %} "" end macro format_char(arg, options = {} of Symbol => NoReturn) {% if arg.is_a?(TypeDeclaration) %} {% if arg.type.is_a?(Union) %} {% if options[:optional_values] != true %} "|o" {% else %} "o" {% end %} {% elsif options[:optional_values] != true && arg.value %} {% options[:optional_values] = true %} "|" + Anyolite::Macro.format_char({{arg.type}}, options: {{options}}) {% else %} Anyolite::Macro.format_char({{arg.type}}, options: {{options}}) {% end %} {% elsif options[:context] %} Anyolite::Macro.resolve_format_char({{options[:context]}}::{{arg.stringify.starts_with?("::") ? arg.stringify[2..-1].id : arg}}, {{arg}}, options: {{options}}) {% else %} Anyolite::Macro.resolve_format_char({{arg}}, {{arg}}) {% end %} end macro resolve_format_char(arg, raw_arg, options = {} of Symbol => NoReturn) {% if arg.resolve? %} {% if ANYOLITE_INTERNAL_FLAG_USE_GENERAL_OBJECT_FORMAT_CHARS %} "o" {% else %} {% if arg.resolve <= Bool %} "b" {% elsif arg.resolve <= Pointer %} "o" {% elsif arg.resolve <= Int %} "i" {% elsif arg.resolve <= Float || arg.resolve == Number %} "f" {% elsif arg.resolve <= String %} "z" {% elsif arg.resolve <= Array %} "A" {% elsif arg.resolve <= Anyolite::RbRef %} "o" {% else %} "o" {% end %} {% end %} {% elsif options[:context] %} {% if options[:context].names[0..-2].size > 0 %} {% new_context = options[:context].names[0..-2].join("::").gsub(/(::)+/, "::").id %} {% options[:context] = new_context %} Anyolite::Macro.resolve_format_char({{new_context}}::{{raw_arg.stringify.starts_with?("::") ? raw_arg.stringify[2..-1].id : raw_arg}}, {{raw_arg}}, options: {{options}}) {% else %} # No context available anymore Anyolite::Macro.resolve_format_char({{raw_arg}}, {{raw_arg}}) {% end %} {% else %} "o" {% end %} end end end ================================================ FILE: src/implementations/mruby/Implementation.cr ================================================ require "./RbCore.cr" require "./FormatString.cr" require "./KeywordArgStruct.cr" {% if flag?(:use_general_object_format_chars) %} ANYOLITE_INTERNAL_FLAG_USE_GENERAL_OBJECT_FORMAT_CHARS = true {% else %} ANYOLITE_INTERNAL_FLAG_USE_GENERAL_OBJECT_FORMAT_CHARS = false {% end %} module Anyolite module Macro macro new_rb_func(&b) Anyolite::RbCore::RbFunc.new do |_rb, _obj| 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 |__rb, __ptr| {{b.body}} end end macro convert_regex_from_ruby_to_crystal(rb, arg, arg_type) Anyolite::Macro.convert_from_ruby_object({{rb}}, {{arg}}, {{arg_type}}).value end macro convert_regex_from_crystal_to_ruby(rb, value) RbCast.return_object({{rb}}, {{value}}) end macro load_args_into_vars(args, format_string, regular_arg_tuple, block_ptr = nil) {% if block_ptr %} Anyolite::RbCore.rb_get_args(_rb, {{format_string}}, *{{regular_arg_tuple}}, {{block_ptr}}) {% else %} Anyolite::RbCore.rb_get_args(_rb, {{format_string}}, *{{regular_arg_tuple}}) {% end %} end macro load_kw_args_into_vars(regular_args, keyword_args, format_string, regular_arg_tuple, block_ptr = nil) %kw_args = Anyolite::Macro.generate_keyword_argument_struct(_rb, {{keyword_args}}) {% if block_ptr %} Anyolite::RbCore.rb_get_args(_rb, {{format_string}}, *{{regular_arg_tuple}}, pointerof(%kw_args), {{block_ptr}}) {% else %} Anyolite::RbCore.rb_get_args(_rb, {{format_string}}, *{{regular_arg_tuple}}, pointerof(%kw_args)) {% end %} %kw_args end end end ================================================ FILE: src/implementations/mruby/KeywordArgStruct.cr ================================================ module Anyolite module Macro macro generate_keyword_argument_struct(rb_interpreter, keyword_args) %kw_names = Anyolite::Macro.generate_keyword_names({{rb_interpreter}}, {{keyword_args}}) %kw_args = Anyolite::RbCore::KWArgs.new %kw_args.num = {{keyword_args.size}} %kw_args.values = Pointer(Anyolite::RbCore::RbValue).malloc(size: {{keyword_args.size}}) %kw_args.table = %kw_names %kw_args.required = {{keyword_args.select { |i| !i.var }.size}} %kw_args.rest = Pointer(Anyolite::RbCore::RbValue).malloc(size: 1) %kw_args end macro generate_keyword_names(rb_interpreter, keyword_args) [ {% for keyword in keyword_args %} Anyolite::RbCore.convert_to_rb_sym({{rb_interpreter}}, {{keyword.var.stringify}}), {% end %} ] end end end ================================================ FILE: src/implementations/mruby/RbCore.cr ================================================ module Anyolite macro link_libraries {% build_path = env("ANYOLITE_BUILD_PATH") ? env("ANYOLITE_BUILD_PATH") : "build" %} {% if flag?(:win32) %} {% libmruby_path = "#{__DIR__}/../../../#{build_path.id}/mruby/lib/libmruby.lib" %} {% else %} {% libmruby_path = "#{__DIR__}/../../../#{build_path.id}/mruby/lib/libmruby.a" %} {% end %} {% if !file_exists?(libmruby_path) %} {% raise "File #{libmruby_path} not found. Was mruby installed correctly?" %} {% end %} {% if flag?(:win32) %} {% if flag?(:anyolite_use_msvcrt_lib) %} # Not recommended, might be removed in a later release @[Link(ldflags: {{libmruby_path.stringify + " msvcrt.lib"}})] {% elsif compare_versions(Crystal::VERSION, "1.5.1") >= 0 %} # Crystal links against libucrt since 1.5.1 @[Link(ldflags: {{libmruby_path.stringify}})] {% else %} @[Link(ldflags: {{libmruby_path.stringify + " libucrt.lib"}})] {% end %} @[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mruby/return_functions.obj\"")] @[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mruby/data_helper.obj\"")] @[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mruby/script_helper.obj\"")] @[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mruby/error_helper.obj\"")] {% else %} @[Link(ldflags: {{libmruby_path.stringify + " -lm"}})] @[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mruby/return_functions.o\"")] @[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mruby/data_helper.o\"")] @[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mruby/script_helper.o\"")] @[Link(ldflags: "\"#{__DIR__}/../../../{{build_path.id}}/glue/mruby/error_helper.o\"")] {% end %} end {% unless env("ANYOLITE_DOCUMENTATION_MODE") %} Anyolite.link_libraries {% end %} lib RbCore alias RbFunc = Proc(State*, RbValue, RbValue) alias RbDataFunc = Proc(State*, Void*, Nil) type State = Void type RClass = Void alias RbFloat = LibC::Double alias RbInt = Int64 alias RbBool = UInt8 alias RbSymbol = UInt32 alias RClassPtr = RClass* 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::ULongLong end struct RbDataType struct_name : LibC::Char* dfree : RbDataFunc end struct KWArgs num : RbInt required : RbInt table : RbSymbol* values : RbValue* rest : RbValue* end struct BytecodeContainer content : UInt8* size : LibC::SizeT error_code : LibC::Int result : LibC::Int end fun rb_open = mrb_open : State* fun rb_close = mrb_close(rb : State*) fun rb_define_module = mrb_define_module(rb : State*, name : LibC::Char*) : RClassPtr fun rb_define_module_under = mrb_define_module_under(rb : State*, under : RClassPtr, name : LibC::Char*) : RClassPtr fun rb_define_class = mrb_define_class(rb : State*, name : LibC::Char*, superclass : RClassPtr) : RClassPtr fun rb_define_class_under = mrb_define_class_under(rb : State*, under : RClassPtr, name : LibC::Char*, superclass : RClassPtr) : RClassPtr fun rb_define_method = mrb_define_method(rb : State*, c : RClassPtr, name : LibC::Char*, func : RbFunc, aspec : UInt32) # TODO: Aspec values fun rb_define_class_method = mrb_define_class_method(rb : State*, c : RClassPtr, name : LibC::Char*, func : RbFunc, aspect : UInt32) fun rb_define_module_function = mrb_define_module_function(rb : State*, c : RClassPtr, name : LibC::Char*, func : RbFunc, aspect : UInt32) fun rb_define_const = mrb_define_const(rb : State*, c : RClassPtr, name : LibC::Char*, val : RbValue) fun rb_print_error = mrb_print_error(rb : State*) fun rb_print_backtrace = mrb_print_backtrace(rb : State*) fun get_last_rb_error = get_last_mrb_error(rb : State*) : RbValue fun clear_last_rb_error = clear_last_mrb_error(rb : State*) fun rb_raise = mrb_raise(rb : State*, c : RClassPtr, msg : LibC::Char*) fun rb_raise_runtime_error = mrb_raise_runtime_error(rb : State*, msg : LibC::Char*) fun rb_raise_type_error = mrb_raise_type_error(rb : State*, msg : LibC::Char*) fun rb_raise_argument_error = mrb_raise_argument_error(rb : State*, msg : LibC::Char*) fun rb_raise_index_error = mrb_raise_index_error(rb : State*, msg : LibC::Char*) fun rb_raise_range_error = mrb_raise_range_error(rb : State*, msg : LibC::Char*) fun rb_raise_name_error = mrb_raise_name_error(rb : State*, msg : LibC::Char*) fun rb_raise_script_error = mrb_raise_script_error(rb : State*, msg : LibC::Char*) fun rb_raise_not_implemented_error = mrb_raise_not_implemented_error(rb : State*, msg : LibC::Char*) fun rb_raise_key_error = mrb_raise_key_error(rb : State*, msg : LibC::Char*) fun rb_get_args = mrb_get_args(rb : State*, format : LibC::Char*, ...) : RbInt fun rb_get_argc = mrb_get_argc(rb : State*) : RbInt fun rb_get_argv = mrb_get_argv(rb : State*) : RbValue* fun rb_yield = mrb_yield(rb : State*, value : RbValue, arg : RbValue) : RbValue fun rb_yield_argv = mrb_yield_argv(rb : State*, value : RbValue, argc : RbInt, argv : RbValue*) : RbValue fun rb_call_block = mrb_yield(rb : State*, value : RbValue, arg : RbValue) : RbValue fun rb_call_block_with_args = mrb_yield_argv(rb : State*, value : RbValue, argc : RbInt, argv : RbValue*) : RbValue fun rb_ary_ref = mrb_ary_ref(rb : State*, value : RbValue, pos : RbInt) : RbValue fun rb_ary_entry = mrb_ary_entry(value : RbValue, offset : RbInt) : RbValue fun array_length(value : RbValue) : LibC::SizeT fun rb_ary_new_from_values = mrb_ary_new_from_values(rb : State*, size : RbInt, values : RbValue*) : RbValue fun rb_hash_new = mrb_hash_new(rb : State*) : RbValue fun rb_hash_set = mrb_hash_set(rb : State*, hash : RbValue, key : RbValue, value : RbValue) fun rb_hash_get = mrb_hash_get(rb : State*, hash : RbValue, key : RbValue) : RbValue fun rb_hash_keys = mrb_hash_keys(rb : State*, hash : RbValue) : RbValue fun rb_hash_size = mrb_hash_size(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 = check_mrb_fixnum(value : RbValue) : LibC::Int fun check_rb_float = check_mrb_float(value : RbValue) : LibC::Int fun check_rb_true = check_mrb_true(value : RbValue) : LibC::Int fun check_rb_false = check_mrb_false(value : RbValue) : LibC::Int fun check_rb_nil = check_mrb_nil(value : RbValue) : LibC::Int fun check_rb_undef = check_mrb_undef(value : RbValue) : LibC::Int fun check_rb_string = check_mrb_string(value : RbValue) : LibC::Int fun check_rb_symbol = check_mrb_symbol(value : RbValue) : LibC::Int fun check_rb_array = check_mrb_array(value : RbValue) : LibC::Int fun check_rb_hash = check_mrb_hash(value : RbValue) : LibC::Int fun check_rb_data = check_mrb_data(value : RbValue) : LibC::Int fun get_rb_fixnum = get_mrb_fixnum(value : RbValue) : RbInt fun get_rb_float = get_mrb_float(value : RbValue) : RbFloat fun get_rb_bool = get_mrb_bool(value : RbValue) : RbBool fun get_rb_string = get_mrb_string(rb : State*, value : RbValue) : LibC::Char* fun rb_str_to_cstr = mrb_str_to_cstr(rb : State*, value : RbValue) : LibC::Char* fun convert_to_rb_sym = convert_to_mrb_sym(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 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 = mrb_inspect(rb : State*, value : RbValue) : RbValue fun rb_gc_register = mrb_gc_register(rb : State*, value : RbValue) : Void fun rb_gc_unregister = mrb_gc_unregister(rb : State*, value : RbValue) : Void fun rb_class_name = mrb_class_name(rb : State*, class_ptr : RClassPtr) : LibC::Char* 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 = get_mrb_obj_value(p : RClassPtr) : RbValue fun rb_obj_is_kind_of = mrb_obj_is_kind_of(rb : State*, obj : RbValue, c : RClassPtr) : RbBool fun get_class_of_obj(rb : State*, obj : RbValue) : RClassPtr fun rb_funcall_argv = mrb_funcall_argv(rb : State*, value : RbValue, name : RbSymbol, argc : RbInt, argv : RbValue*) : RbValue fun rb_funcall_argv_with_block = mrb_funcall_with_block(rb : State*, value : RbValue, name : RbSymbol, argc : RbInt, argv : RbValue*, block : RbValue) : RbValue fun rb_respond_to = mrb_respond_to(rb : State*, obj : RbValue, name : RbSymbol) : RbBool fun rb_class_get = mrb_class_get(rb : State*, name : LibC::Char*) : RClassPtr fun rb_class_get_under = mrb_class_get_under(rb : State*, under : RClassPtr, name : LibC::Char*) : RClassPtr fun rb_class_defined = mrb_class_defined(rb : State*, name : LibC::Char*) : RbBool fun rb_class_defined_under = mrb_class_defined_under(rb : State*, under : RClassPtr, name : LibC::Char*) : RbBool fun rb_module_get = mrb_module_get(rb : State*, name : LibC::Char*) : RClassPtr fun rb_module_get_under = mrb_module_get_under(rb : State*, under : RClassPtr, name : LibC::Char*) : RClassPtr fun rb_module_defined = mrb_class_defined(rb : State*, name : LibC::Char*) : RbBool fun rb_module_defined_under = mrb_class_defined_under(rb : State*, under : RClassPtr, name : LibC::Char*) : RbBool fun rb_iv_set = mrb_iv_set(rb : State*, obj : RbValue, sym : RbSymbol, value : RbValue) : Void fun rb_iv_get = mrb_iv_get(rb : State*, obj : RbValue, sym : RbSymbol) : RbValue fun rb_cv_set = mrb_cv_set(rb : State*, mod : RbValue, sym : RbSymbol, value : RbValue) : Void fun rb_cv_get = mrb_cv_get(rb : State*, mod : RbValue, sym : RbSymbol) : RbValue # NOTE: These are differing due to global variable methods by ID being private in MRI # NOTE: You should not use globals that often, anyway fun rb_gv_set = mrb_gv_set_helper(rb : State*, name : LibC::Char*, value : RbValue) : Void fun rb_gv_get = mrb_gv_get_helper(rb : State*, name : LibC::Char*) : RbValue fun rb_undef_method = mrb_undef_method(rb : State*, class_ptr : RClassPtr, name : LibC::Char*) : Void fun rb_undef_class_method = mrb_undef_class_method(rb : State*, class_ptr : RClassPtr, name : LibC::Char*) : Void fun load_script_from_file(rb : State*, filename : LibC::Char*) : RbValue fun execute_script_line(rb : State*, str : LibC::Char*) : RbValue fun execute_bytecode(rb : State*, bytecode : UInt8*) : RbValue fun load_bytecode_from_file(rb : State*, filename : LibC::Char*) : RbValue fun transform_script_to_bytecode(filename : LibC::Char*, target_filename : LibC::Char*) : LibC::Int fun transform_script_to_bytecode_container(filename : LibC::Char*) : BytecodeContainer fun transform_proc_to_bytecode_container(rb : State*, proc_object : RbValue) : BytecodeContainer fun free_bytecode_container(container : BytecodeContainer) : Void fun rb_fiber_resume = mrb_fiber_resume(rb : State*, fiber : RbValue, argc : RbInt, argv : RbValue*) : RbValue fun rb_fiber_yield = mrb_fiber_yield(rb : State*, argc : RbInt, argv : RbValue*) : RbValue fun rb_fiber_alive = mrb_fiber_alive_p(rb : State*, fiber : RbValue) : RbValue fun rb_gc_arena_save = mrb_gc_arena_save_helper(rb : State*) : LibC::Int fun rb_gc_arena_restore = mrb_gc_arena_restore_helper(rb : State*, idx : LibC::Int) : Void end end ================================================ FILE: src/macros/ArgConversions.cr ================================================ module Anyolite module Macro macro get_raw_args(rb, regular_args, context = nil) {% options = {:context => context} %} %args = Anyolite::Macro.generate_arg_tuple({{rb}}, {{regular_args}}, options: {{options}}) %format_string = Anyolite::Macro.format_string({{regular_args}}, options: {{options}}) Anyolite::Macro.load_args_into_vars({{regular_args}}, %format_string, %args) %args end # Converts Ruby values to Crystal values macro convert_regular_arg(rb, arg, arg_type, options = {} of Symbol => NoReturn, debug_information = nil) {% if arg_type.stringify.includes?("->") || arg_type.stringify.includes?(" Proc(") %} {% puts "\e[33m> INFO: Proc types are not allowed as arguments.\e[0m" %} raise "Proc types are not allowed as arguments ({{debug_information.id}})" {% elsif arg_type.stringify.includes?(" Slice(") %} {% puts "\e[33m> INFO: Slice types are not allowed as arguments (#{debug_information.id}).\e[0m" %} raise "Slice types are not allowed as arguments ({{debug_information.id}})" {% elsif arg_type.is_a?(Generic) %} Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{arg}}, dummy_arg : {{arg_type}}, options: {{options}}, debug_information: {{debug_information}}) {% elsif arg_type.is_a?(TypeDeclaration) && arg_type.type.is_a?(Union) %} Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{arg}}, {{arg_type}}, options: {{options}}, debug_information: {{debug_information}}) {% elsif arg_type.is_a?(TypeDeclaration) %} Anyolite::Macro.convert_regular_arg({{rb}}, {{arg}}, {{arg_type.type}}, options: {{options}}, debug_information: {{debug_information}}) {% elsif options[:context] %} Anyolite::Macro.resolve_regular_arg({{rb}}, {{arg}}, {{options[:context]}}::{{arg_type.stringify.starts_with?("::") ? arg_type.stringify[2..-1].id : arg_type}}, {{arg_type}}, options: {{options}}, debug_information: {{debug_information}}) {% else %} Anyolite::Macro.resolve_regular_arg({{rb}}, {{arg}}, {{arg_type}}, {{arg_type}}, debug_information: {{debug_information}}) {% end %} end macro resolve_regular_arg(rb, arg, arg_type, raw_arg_type, options = {} of Symbol => NoReturn, debug_information = nil) {% if arg_type.resolve? %} {% if arg_type.resolve <= Nil %} nil {% elsif arg_type.resolve <= Bool %} ({{arg}} != 0) {% elsif arg_type.resolve == Number %} begin Float64.new({{arg}}) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{{{arg}}} to {{arg_type}}.") Float64.new(0.0) end {% elsif arg_type.resolve == Int %} begin Int64.new({{arg}}) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{{{arg}}} to {{arg_type}}.") Int64.new(0) end {% elsif arg_type.resolve <= Int %} begin {{arg_type}}.new({{arg}}) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{{{arg}}} to {{arg_type}}.") {{arg_type}}.new(0) end {% elsif arg_type.resolve == Float %} begin Float64.new({{arg}}) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{{{arg}}} to {{arg_type}}.") Float64.new(0.0) end {% elsif arg_type.resolve <= Float %} begin {{arg_type}}.new({{arg}}) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{{{arg}}} to {{arg_type}}.") {{arg_type}}.new(0.0) end {% elsif arg_type.resolve <= Char %} Anyolite::RbCast.cast_to_char({{rb}}, {{arg}}) {% elsif arg_type.resolve <= String %} {{arg_type}}.new({{arg}}) {% elsif arg_type.resolve <= Anyolite::RbRef %} {{arg_type}}.new({{arg}}) {% elsif arg_type.resolve <= Regex %} Anyolite::Macro.convert_regex_from_ruby_to_crystal({{rb}}, {{arg}}, {{arg_type}}) {% elsif arg_type.resolve <= Array %} %array_size = Anyolite::RbCore.array_length({{arg}}) %converted_array = {{arg_type}}.new(size: %array_size) do |%index| Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, Anyolite::RbCore.rb_ary_entry({{arg}}, %index), {{arg_type.type_vars[0]}}, options: {{options}}, debug_information: {{debug_information}}) end %converted_array {% elsif arg_type.resolve <= Hash %} %hash_size = Anyolite::RbCore.rb_hash_size({{rb}}, {{arg}}) %all_rb_hash_keys = Anyolite::RbCore.rb_hash_keys({{rb}}, {{arg}}) %all_converted_hash_keys = Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %all_rb_hash_keys, Array({{arg_type.type_vars[0]}}), options: {{options}}, debug_information: {{debug_information}}) %converted_hash = {{arg_type}}.new(initial_capacity: %hash_size) %all_converted_hash_keys.each_with_index do |%key, %index| %rb_key = Anyolite::RbCore.rb_ary_entry(%all_rb_hash_keys, %index) %rb_value = Anyolite::RbCore.rb_hash_get({{rb}}, {{arg}}, %rb_key) %converted_hash[%key] = Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %rb_value, {{arg_type.type_vars[1]}}, options: {{options}}, debug_information: {{debug_information}}) end %converted_hash {% elsif arg_type.resolve <= Pointer %} {{arg_type}}.new(address: UInt64.new({{arg.address}})) {% elsif arg_type.resolve <= Struct || arg_type.resolve <= Enum %} Anyolite::Macro.convert_from_ruby_struct({{rb}}, {{arg}}, {{arg_type}}).value.content {% else %} Anyolite::Macro.convert_from_ruby_object({{rb}}, {{arg}}, {{arg_type}}).value {% end %} {% elsif options[:context] %} {% if options[:context].names[0..-2].size > 0 %} {% new_context = options[:context].names[0..-2].join("::").gsub(/(\:\:)+/, "::").id %} {% options[:context] = new_context %} Anyolite::Macro.resolve_regular_arg({{rb}}, {{arg}}, {{new_context}}::{{raw_arg_type.stringify.starts_with?("::") ? raw_arg_type.stringify[2..-1].id : raw_arg_type}}, {{raw_arg_type}}, options: {{options}}, debug_information: {{debug_information}}) {% else %} Anyolite::Macro.resolve_regular_arg({{rb}}, {{arg}}, {{raw_arg_type}}, {{raw_arg_type}}, debug_information: {{debug_information}}) {% end %} {% else %} {% raise "Could not resolve #{arg_type}, which is a #{arg_type.class_name}, in any meaningful way (#{debug_information.id})" %} {% end %} end macro convert_from_ruby_to_crystal(rb, arg, arg_type, options = {} of Symbol => NoReturn, debug_information = nil) {% if arg_type.stringify.includes?("->") || arg_type.stringify.includes?(" Proc(") %} {% puts "\e[33m> INFO: Proc types are not allowed as arguments (#{debug_information.id}).\e[0m" %} raise "Proc types are not allowed as arguments ({{debug_information.id}})" {% elsif arg_type.stringify.includes?(" Slice(") %} {% puts "\e[33m> INFO: Slice types are not allowed as arguments (#{debug_information.id}).\e[0m" %} raise "Slice types are not allowed as arguments ({{debug_information.id}})" {% elsif options[:type_var_names] && options[:type_var_names].includes?(arg_type.type) %} {% options[:type_var_names].each_with_index { |element, index| result = index if element == arg_type.type } %} Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{arg}}, {{options[:type_vars][result]}}, options: {{{:context => options[:context]}}}, debug_information: {{debug_information}}) {% elsif options[:type_var_names] %} {% magical_regex = /([\(\s\:])([A-Z]+)([\),\s])/ %} {% replacement_arg_type = arg_type.type.stringify.gsub(magical_regex, "\\1\#\\2\#\\3") %} {% for type_var_name, index in options[:type_var_names] %} {% split_types = replacement_arg_type.split("\#") %} {% odd = true %} {% final_split_types = [] of ASTNode %} {% for split_type, split_type_index in split_types %} {% if !odd %} {% result = nil %} {% options[:type_var_names].each_with_index { |element, index| result = index if element.stringify == split_type } %} {% if result %} {% final_split_types.push(options[:type_vars][result].stringify) %} {% else %} {% final_split_types.push(split_type) %} {% end %} {% else %} {% final_split_types.push(split_type) %} {% end %} {% odd = !odd %} {% end %} {% end %} {% if arg_type.value %} {% final_type_def = "#{arg_type.var} : #{final_split_types.join("").id} = #{arg_type.value}" %} {% else %} {% final_type_def = "#{arg_type.var} : #{final_split_types.join("").id}" %} {% end %} Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{arg}}, {{final_type_def.id}}, options: {{{:context => options[:context]}}}, debug_information: {{debug_information}}) {% elsif arg_type.is_a?(Call) %} Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{arg}}, dummy_arg : {{arg_type}}, options: {{options}}, debug_information: {{debug_information}}) {% elsif arg_type.is_a?(TypeDeclaration) %} if Anyolite::RbCast.is_undef?({{arg}}) {% if arg_type.value || arg_type.value == false || arg_type.value == nil %} {{arg_type.value}} {% else %} # Should only happen if no default value was given Anyolite.raise_argument_error("Undefined argument #{{{arg}}} of {{arg_type}} in context {{options[:context]}} (#{{{debug_information.stringify}}})") # Code should jump to somewhere else before this point, but we want to have a NoReturn type here raise("Should not be reached") {% end %} else {% if arg_type.type.is_a?(Union) %} Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{arg}}, Union({{arg_type.type}}), options: {{options}}, debug_information: {{debug_information}}) {% else %} Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{arg}}, {{arg_type.type}}, options: {{options}}, debug_information: {{debug_information}}) {% end %} end # TODO: Check if this might need some improvement {% elsif options[:context] && !arg_type.stringify.starts_with?("Union") %} Anyolite::Macro.resolve_from_ruby_to_crystal({{rb}}, {{arg}}, {{options[:context]}}::{{arg_type.stringify.starts_with?("::") ? arg_type.stringify[2..-1].id : arg_type}}, {{arg_type}}, options: {{options}}, debug_information: {{debug_information}}) {% else %} Anyolite::Macro.resolve_from_ruby_to_crystal({{rb}}, {{arg}}, {{arg_type}}, {{arg_type}}, options: {{options}}, debug_information: {{debug_information}}) {% end %} end macro resolve_from_ruby_to_crystal(rb, arg, arg_type, raw_arg_type, options = {} of Symbol => NoReturn, debug_information = nil) {% if arg_type.stringify.starts_with?("Union") || arg_type.stringify.starts_with?("::Union") %} # This sadly needs some uncanny magic {% char_parser = "" %} {% brace_counter = 0 %} {% for c in arg_type.stringify[(arg_type.stringify.starts_with?("::") ? 8 : 6)..-2].chars %} {% brace_counter += 1 if c == '(' %} {% brace_counter -= 1 if c == ')' %} {% char_parser += (brace_counter == 0 && c == '|' ? ',' : c) %} {% end %} Anyolite::Macro.cast_to_union_value({{rb}}, {{arg}}, {{"[#{char_parser.id}]".id}}, options: {{options}}, debug_information: {{debug_information}}) {% elsif arg_type.resolve? %} {% if arg_type.resolve <= Nil %} Anyolite::RbCast.cast_to_nil({{rb}}, {{arg}}) {% elsif arg_type.resolve <= Bool %} Anyolite::RbCast.cast_to_bool({{rb}}, {{arg}}) {% elsif arg_type.resolve == Number %} begin Float64.new(Anyolite::RbCast.cast_to_float({{rb}}, {{arg}})) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{Anyolite::RbCast.cast_to_float({{rb}}, {{arg}})} to {{arg_type}}.") Float64.new(0.0) end {% elsif arg_type.resolve == Int %} begin Int64.new(Anyolite::RbCast.cast_to_int({{rb}}, {{arg}})) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{Anyolite::RbCast.cast_to_int({{rb}}, {{arg}})} to {{arg_type}}.") Int64.new(0) end {% elsif arg_type.resolve <= Int %} begin {{arg_type}}.new(Anyolite::RbCast.cast_to_int({{rb}}, {{arg}})) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{Anyolite::RbCast.cast_to_int({{rb}}, {{arg}})} to {{arg_type}}.") {{arg_type}}.new(0) end {% elsif arg_type.resolve == Float %} begin Float64.new(Anyolite::RbCast.cast_to_float({{rb}}, {{arg}})) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{Anyolite::RbCast.cast_to_float({{rb}}, {{arg}})} to {{arg_type}}.") Float64.new(0.0) end {% elsif arg_type.resolve <= Float %} begin {{arg_type}}.new(Anyolite::RbCast.cast_to_float({{rb}}, {{arg}})) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{Anyolite::RbCast.cast_to_float({{rb}}, {{arg}})} to {{arg_type}}.") {{arg_type}}.new(0.0) end {% elsif arg_type.resolve <= Char %} Anyolite::RbCast.cast_to_char({{rb}}, {{arg}}) {% elsif arg_type.resolve <= String %} Anyolite::RbCast.cast_to_string({{rb}}, {{arg}}) {% elsif arg_type.resolve <= Regex %} Anyolite::Macro.convert_regex_from_ruby_to_crystal({{rb}}, {{arg}}, {{arg_type}}) {% elsif arg_type.resolve <= Anyolite::RbRef %} {{arg_type}}.new({{arg}}) {% elsif arg_type.resolve <= Array %} # TODO: Make a macro out of this %array_size = Anyolite::RbCore.array_length({{arg}}) %converted_array = {{arg_type}}.new(size: %array_size) do |%index| Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, Anyolite::RbCore.rb_ary_entry({{arg}}, %index), {{arg_type.type_vars[0]}}, options: {{options}}, debug_information: {{debug_information}}) end %converted_array {% elsif arg_type.resolve <= Hash %} %hash_size = Anyolite::RbCore.rb_hash_size({{rb}}, {{arg}}) %all_rb_hash_keys = Anyolite::RbCore.rb_hash_keys({{rb}}, {{arg}}) %all_converted_hash_keys = Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %all_rb_hash_keys, Array({{arg_type.type_vars[0]}}), options: {{options}}, debug_information: {{debug_information}}) %converted_hash = {{arg_type}}.new(initial_capacity: %hash_size) %all_converted_hash_keys.each_with_index do |%key, %index| %rb_key = Anyolite::RbCore.rb_ary_entry(%all_rb_hash_keys, %index) %rb_value = Anyolite::RbCore.rb_hash_get({{rb}}, {{arg}}, %rb_key) %converted_hash[%key] = Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %rb_value, {{arg_type.type_vars[1]}}, options: {{options}}, debug_information: {{debug_information}}) end %converted_hash {% elsif arg_type.resolve <= Pointer %} %helper_ptr = Anyolite::Macro.convert_from_ruby_object({{rb}}, {{arg}}, Anyolite::HelperClasses::AnyolitePointer).value Box({{arg_type}}).unbox(%helper_ptr.retrieve_ptr) {% elsif arg_type.resolve <= Struct || arg_type.resolve <= Enum %} Anyolite::Macro.convert_from_ruby_struct({{rb}}, {{arg}}, {{arg_type}}).value.content {% elsif arg_type.resolve? %} Anyolite::Macro.convert_from_ruby_object({{rb}}, {{arg}}, {{arg_type}}).value {% else %} {% raise "Could not resolve type #{arg_type}, which is a #{arg_type.class_name.id} (#{debug_information.id})" %} {% end %} {% elsif options[:context] %} {% if options[:context].names[0..-2].size > 0 %} {% new_context = options[:context].names[0..-2].join("::").gsub(/(\:\:)+/, "::").id %} {% options[:context] = new_context %} Anyolite::Macro.resolve_from_ruby_to_crystal({{rb}}, {{arg}}, {{new_context}}::{{raw_arg_type.stringify.starts_with?("::") ? raw_arg_type.stringify[2..-1].id : raw_arg_type}}, {{raw_arg_type}}, options: {{options}}, debug_information: {{debug_information}}) {% else %} Anyolite::Macro.resolve_from_ruby_to_crystal({{rb}}, {{arg}}, {{raw_arg_type}}, {{raw_arg_type}}, debug_information: {{debug_information}}) {% end %} {% else %} {% raise "Could not resolve type #{arg_type}, which is a #{arg_type.class_name.id} (#{debug_information.id})" %} {% end %} end macro convert_regular_args(rb, args, regular_args, options = {} of Symbol => NoReturn, debug_information = nil) Tuple.new( {% c = 0 %} {% if regular_args %} {% for arg in regular_args %} {% if ANYOLITE_INTERNAL_FLAG_USE_GENERAL_OBJECT_FORMAT_CHARS %} Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{args}}[{{c}}].value, {{arg}}, options: {{options}}, debug_information: {{debug_information}}), {% else %} Anyolite::Macro.convert_regular_arg({{rb}}, {{args}}[{{c}}].value, {{arg}}, options: {{options}}, debug_information: {{debug_information}}), {% end %} {% c += 1 %} {% end %} {% end %} ) end macro get_converted_args(rb, regular_args, options = {} of Symbol => NoReturn) %args = Anyolite::Macro.generate_arg_tuple({{rb}}, {{regular_args}}, options: {{options}}) %format_string = Anyolite::Macro.format_string({{regular_args}}, options: {{options}}) Anyolite::Macro.load_args_into_vars({{regular_args}}, %format_string, %args) Anyolite::Macro.convert_regular_args({{rb}}, %args, {{regular_args}}, options: {{options}}) end end end ================================================ FILE: src/macros/ArgTuples.cr ================================================ module Anyolite module Macro macro generate_arg_tuple(rb, args, options = {} of Symbol => NoReturn) Tuple.new( {% if args %} {% for arg in args %} {% if arg.is_a?(TypeDeclaration) %} {% if arg.value %} {% if ANYOLITE_INTERNAL_FLAG_USE_GENERAL_OBJECT_FORMAT_CHARS %} Anyolite::Macro.pointer_type({{arg}}, options: {{options}}).malloc(size: 1, value: Anyolite::RbCast.return_value({{rb}}, {{arg.value}})), {% else %} {% if arg.type.is_a?(Union) %} # This does work, but I'm a bit surprised Anyolite::Macro.pointer_type({{arg}}, options: {{options}}).malloc(size: 1, value: Anyolite::RbCast.return_value({{rb}}, {{arg.value}})), {% elsif arg.type.resolve <= String %} # The outer gods bless my wretched soul that this does neither segfault nor leak Anyolite::Macro.pointer_type({{arg}}, options: {{options}}).malloc(size: 1, value: {{arg.value}}.to_unsafe), {% elsif arg.type.resolve <= Anyolite::RbRef %} Anyolite::Macro.pointer_type({{arg}}, options: {{options}}).malloc(size: 1, value: {{arg.value}}), # NOTE: This might need some extensions {% elsif arg.type.resolve <= Bool %} Anyolite::Macro.pointer_type({{arg}}, options: {{options}}).malloc(size: 1, value: Anyolite::Macro.type_in_ruby({{arg}}, options: {{options}}).new({{arg.value}} ? 1 : 0)), {% elsif arg.type.resolve <= Number %} Anyolite::Macro.pointer_type({{arg}}, options: {{options}}).malloc(size: 1, value: Anyolite::Macro.type_in_ruby({{arg}}, options: {{options}}).new({{arg.value}})), {% else %} Anyolite::Macro.pointer_type({{arg}}, options: {{options}}).malloc(size: 1, value: Anyolite::RbCast.return_value({{rb}}, {{arg.value}})), {% end %} {% end %} {% else %} Anyolite::Macro.pointer_type({{arg}}, options: {{options}}).malloc(size: 1), {% end %} {% else %} Anyolite::Macro.pointer_type({{arg}}, options: {{options}}).malloc(size: 1), {% end %} {% end %} {% end %} ) end end end ================================================ FILE: src/macros/FunctionCalls.cr ================================================ module Anyolite module Macro macro call_and_return(rb, proc, regular_args, converted_args, operator = "", options = {} of Symbol => NoReturn, block_ptr = nil) {% if !options[:block_arg_number] %} {% proc_arg_string = "" %} {% elsif options[:block_arg_number] == 0 %} {% proc_arg_string = "do" %} {% else %} {% proc_arg_string = "do |" + (0..options[:block_arg_number] - 1).map { |x| "block_arg_#{x}" }.join(", ") + "|" %} {% end %} {% if proc.stringify == "Anyolite::Empty" %} %return_value = {{operator.id}}(*{{converted_args}}) {{proc_arg_string.id}} {% else %} %return_value = {{proc}}{{operator.id}}(*{{converted_args}}) {{proc_arg_string.id}} {% end %} {% if options[:block_arg_number] == 0 %} %yield_value = Anyolite::RbCore.rb_yield({{rb}}, {{block_ptr}}.value, Anyolite::RbCast.return_nil) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% elsif options[:block_arg_number] %} %block_arg_array = [ {% for i in 0..options[:block_arg_number] - 1 %} Anyolite::RbCast.return_value({{rb}}, {{"block_arg_#{i}".id}}), {% end %} ] %yield_value = Anyolite::RbCore.rb_yield_argv({{rb}}, {{block_ptr}}.value, {{options[:block_arg_number]}}, %block_arg_array) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% end %} {% if options[:return_nil] %} Anyolite::RbCast.return_nil {% else %} Anyolite::RbCast.return_value({{rb}}, %return_value) {% end %} end macro call_and_return_keyword_method(rb, proc, converted_regular_args, keyword_args, kw_args, operator = "", options = {} of Symbol => NoReturn, block_ptr = nil) {% if !options[:block_arg_number] %} {% proc_arg_string = "" %} {% elsif options[:block_arg_number] == 0 %} {% proc_arg_string = "do" %} {% else %} {% proc_arg_string = "do |" + (0..options[:block_arg_number] - 1).map { |x| "block_arg_#{x}" }.join(", ") + "|" %} {% end %} {% if proc.stringify == "Anyolite::Empty" %} %return_value = {{operator.id}}( {% else %} %return_value = {{proc}}{{operator.id}}( {% end %} {% if options[:empty_regular] %} {% c = 0 %} {% for keyword in keyword_args %} {{keyword.var.id}}: Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{kw_args}}.values[{{c}}], {{keyword}}, options: {{options}}, debug_information: {{proc.stringify + " - " + keyword_args.stringify}}), {% c += 1 %} {% end %} {% else %} *{{converted_regular_args}}, {% c = 0 %} {% for keyword in keyword_args %} {{keyword.var.id}}: Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{kw_args}}.values[{{c}}], {{keyword}}, options: {{options}}, debug_information: {{proc.stringify + " - " + keyword_args.stringify}}), {% c += 1 %} {% end %} {% end %} ) {{proc_arg_string.id}} {% if options[:block_arg_number] == 0 %} %yield_value = Anyolite::RbCore.rb_yield({{rb}}, {{block_ptr}}.value, Anyolite::RbCast.return_nil) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% elsif options[:block_arg_number] %} %block_arg_array = [ {% for i in 0..options[:block_arg_number] - 1 %} Anyolite::RbCast.return_value({{rb}}, {{"block_arg_#{i}".id}}), {% end %} ] %yield_value = Anyolite::RbCore.rb_yield_argv({{rb}}, {{block_ptr}}.value, {{options[:block_arg_number]}}, %block_arg_array) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% end %} {% if options[:return_nil] %} Anyolite::RbCast.return_nil {% else %} Anyolite::RbCast.return_value({{rb}}, %return_value) {% end %} end macro call_and_return_instance_method(rb, proc, converted_obj, converted_args, operator = "", options = {} of Symbol => NoReturn, block_ptr = nil) {% if !options[:block_arg_number] %} {% proc_arg_string = "" %} {% elsif options[:block_arg_number] == 0 %} {% proc_arg_string = "do" %} {% else %} {% proc_arg_string = "do |" + (0..options[:block_arg_number] - 1).map { |x| "block_arg_#{x}" }.join(", ") + "|" %} {% end %} if {{converted_obj}}.is_a?(Anyolite::StructWrapper) %working_content = {{converted_obj}}.content {% if proc.stringify == "Anyolite::Empty" %} %return_value = %working_content.{{operator.id}}(*{{converted_args}}) {{proc_arg_string.id}} {% else %} %return_value = %working_content.{{proc}}{{operator.id}}(*{{converted_args}}) {{proc_arg_string.id}} {% end %} {% if options[:block_arg_number] == 0 %} %yield_value = Anyolite::RbCore.rb_yield({{rb}}, {{block_ptr}}.value, Anyolite::RbCast.return_nil) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% elsif options[:block_arg_number] %} %block_arg_array = [ {% for i in 0..options[:block_arg_number] - 1 %} Anyolite::RbCast.return_value({{rb}}, {{"block_arg_#{i}".id}}), {% end %} ] %yield_value = Anyolite::RbCore.rb_yield_argv({{rb}}, {{block_ptr}}.value, {{options[:block_arg_number]}}, %block_arg_array) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% end %} {{converted_obj}}.content = %working_content else {% if proc.stringify == "Anyolite::Empty" %} %return_value = {{converted_obj}}.{{operator.id}}(*{{converted_args}}) {{proc_arg_string.id}} {% else %} %return_value = {{converted_obj}}.{{proc}}{{operator.id}}(*{{converted_args}}) {{proc_arg_string.id}} {% end %} {% if options[:block_arg_number] == 0 %} %yield_value = Anyolite::RbCore.rb_yield({{rb}}, {{block_ptr}}.value, Anyolite::RbCast.return_nil) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% elsif options[:block_arg_number] %} %block_arg_array = [ {% for i in 0..options[:block_arg_number] - 1 %} Anyolite::RbCast.return_value({{rb}}, {{"block_arg_#{i}".id}}), {% end %} ] %yield_value = Anyolite::RbCore.rb_yield_argv({{rb}}, {{block_ptr}}.value, {{options[:block_arg_number]}}, %block_arg_array) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% end %} end {% if options[:return_nil] %} Anyolite::RbCast.return_nil {% else %} Anyolite::RbCast.return_value({{rb}}, %return_value) {% end %} end macro call_and_return_keyword_instance_method(rb, proc, converted_obj, converted_regular_args, keyword_args, kw_args, operator = "", options = {} of Symbol => NoReturn, block_ptr = nil) {% if !options[:block_arg_number] %} {% proc_arg_string = "" %} {% elsif options[:block_arg_number] == 0 %} {% proc_arg_string = "do" %} {% else %} {% proc_arg_string = "do |" + (0..options[:block_arg_number] - 1).map { |x| "block_arg_#{x}" }.join(", ") + "|" %} {% end %} if {{converted_obj}}.is_a?(Anyolite::StructWrapper) %working_content = {{converted_obj}}.content {% if proc.stringify == "Anyolite::Empty" %} %return_value = %working_content.{{operator.id}}( {% else %} %return_value = %working_content.{{proc}}{{operator.id}}( {% end %} {% if options[:empty_regular] %} {% c = 0 %} {% for keyword in keyword_args %} {{keyword.var.id}}: Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{kw_args}}.values[{{c}}], {{keyword}}, options: {{options}}, debug_information: {{proc.stringify + " - " + keyword_args.stringify}}), {% c += 1 %} {% end %} {% else %} *{{converted_regular_args}}, {% c = 0 %} {% for keyword in keyword_args %} {{keyword.var.id}}: Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{kw_args}}.values[{{c}}], {{keyword}}, options: {{options}}, debug_information: {{proc.stringify + " - " + keyword_args.stringify}}), {% c += 1 %} {% end %} {% end %} ) {{proc_arg_string.id}} {% if options[:block_arg_number] == 0 %} %yield_value = Anyolite::RbCore.rb_yield({{rb}}, {{block_ptr}}.value, Anyolite::RbCast.return_nil) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% elsif options[:block_arg_number] %} %block_arg_array = [ {% for i in 0..options[:block_arg_number] - 1 %} Anyolite::RbCast.return_value({{rb}}, {{"block_arg_#{i}".id}}), {% end %} ] %yield_value = Anyolite::RbCore.rb_yield_argv({{rb}}, {{block_ptr}}.value, {{options[:block_arg_number]}}, %block_arg_array) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% end %} {{converted_obj}}.content = %working_content else {% if proc.stringify == "Anyolite::Empty" %} %return_value = {{converted_obj}}.{{operator.id}}( {% else %} %return_value = {{converted_obj}}.{{proc}}{{operator.id}}( {% end %} {% if options[:empty_regular] %} {% c = 0 %} {% for keyword in keyword_args %} {{keyword.var.id}}: Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{kw_args}}.values[{{c}}], {{keyword}}, options: {{options}}, debug_information: {{proc.stringify + " - " + keyword_args.stringify}}), {% c += 1 %} {% end %} {% else %} *{{converted_regular_args}}, {% c = 0 %} {% for keyword in keyword_args %} {{keyword.var.id}}: Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, {{kw_args}}.values[{{c}}], {{keyword}}, options: {{options}}, debug_information: {{proc.stringify + " - " + keyword_args.stringify}}), {% c += 1 %} {% end %} {% end %} ) {{proc_arg_string.id}} {% if options[:block_arg_number] == 0 %} %yield_value = Anyolite::RbCore.rb_yield({{rb}}, {{block_ptr}}.value, Anyolite::RbCast.return_nil) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% elsif options[:block_arg_number] %} %block_arg_array = [ {% for i in 0..options[:block_arg_number] - 1 %} Anyolite::RbCast.return_value({{rb}}, {{"block_arg_#{i}".id}}), {% end %} ] %yield_value = Anyolite::RbCore.rb_yield_argv({{rb}}, {{block_ptr}}.value, {{options[:block_arg_number]}}, %block_arg_array) Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %yield_value, {{options[:block_return_type]}}) end {% end %} end {% if options[:return_nil] %} Anyolite::RbCast.return_nil {% else %} Anyolite::RbCast.return_value({{rb}}, %return_value) {% end %} end end end ================================================ FILE: src/macros/FunctionGenerators.cr ================================================ module Anyolite module Macro macro add_default_constructor(rb_interpreter, crystal_class, verbose) {% puts "> Adding constructor for #{crystal_class}\n\n" if verbose %} Anyolite.wrap_constructor({{rb_interpreter}}, {{crystal_class}}) end macro add_enum_constructor(rb_interpreter, crystal_class, verbose) {% puts "> Adding enum constructor for #{crystal_class}\n\n" if verbose %} {% type_hash = {:u8 => UInt8, :u16 => UInt16, :u32 => UInt32, :u64 => UInt64, :i8 => Int8, :i16 => Int16, :i32 => Int32, :i64 => Int64} %} {% enum_type = type_hash[parse_type("#{crystal_class}::#{crystal_class.resolve.constants.first.id}").resolve.kind] %} Anyolite.wrap_constructor({{rb_interpreter}}, {{crystal_class}}, [{{enum_type}}]) end macro add_enum_inspect(rb_interpreter, crystal_class, verbose) {% puts "> Adding enum inspect method for #{crystal_class}\n\n" if verbose %} Anyolite.wrap_instance_method({{rb_interpreter}}, {{crystal_class}}, "inspect", inspect) end macro add_enum_to_s(rb_interpreter, crystal_class, verbose) {% puts "> Adding enum to_s method for #{crystal_class}\n\n" if verbose %} Anyolite.wrap_instance_method({{rb_interpreter}}, {{crystal_class}}, "to_s", to_s) end macro add_equality_method(rb_interpreter, crystal_class, context, verbose) {% puts "> Adding equality method for #{crystal_class}\n\n" if verbose %} {% options = {:context => context} %} Anyolite::Macro.wrap_equality_function({{rb_interpreter}}, {{crystal_class}}, "==", Anyolite::Empty, operator: "==", options: {{options}}) end macro add_copy_constructor(rb_interpreter, crystal_class, context, verbose) {% puts "> Adding copy constructor for #{crystal_class}\n\n" if verbose %} {% options = {:context => context} %} %copy_proc = Anyolite::Macro.new_rb_func do %converted_args = Anyolite::Macro.get_converted_args(_rb, [other : {{crystal_class}}], options: {{options}}) %new_obj = %converted_args[0].dup Anyolite::Macro.allocate_constructed_object(_rb, {{crystal_class}}, _obj, %new_obj) _obj end {{rb_interpreter}}.define_method("initialize_copy", Anyolite::RbClassCache.get({{crystal_class}}), %copy_proc) end end end ================================================ FILE: src/macros/ObjectAllocations.cr ================================================ module Anyolite module Macro macro allocate_constructed_object(rb, crystal_class, obj, new_obj) # Call initializer method if available if {{new_obj}}.responds_to?(:rb_initialize) {{new_obj}}.rb_initialize({{rb}}) end # Allocate memory so we do not lose this object if {{crystal_class}} <= Struct || {{crystal_class}} <= Enum %struct_wrapper = Anyolite::StructWrapper({{crystal_class}}).new({{new_obj}}) %new_obj_ptr = Pointer(Anyolite::StructWrapper({{crystal_class}})).malloc(size: 1, value: %struct_wrapper) Anyolite::RbRefTable.add(Anyolite::RbRefTable.get_object_id(%new_obj_ptr.value), %new_obj_ptr.as(Void*), {{obj}}) puts "> S: {{crystal_class}}: #{%new_obj_ptr.value.inspect}" if Anyolite::RbRefTable.option_active?(:logging) %destructor = Anyolite::RbTypeCache.destructor_method({{crystal_class}}) Anyolite::RbCore.set_data_ptr_and_type({{obj}}, %new_obj_ptr, Anyolite::RbTypeCache.register({{crystal_class}}, %destructor)) else %new_obj_ptr = Pointer({{crystal_class}}).malloc(size: 1, value: {{new_obj}}) Anyolite::RbRefTable.add(Anyolite::RbRefTable.get_object_id(%new_obj_ptr.value), %new_obj_ptr.as(Void*), {{obj}}) puts "> C: {{crystal_class}}: #{%new_obj_ptr.value.inspect}" if Anyolite::RbRefTable.option_active?(:logging) %destructor = Anyolite::RbTypeCache.destructor_method({{crystal_class}}) Anyolite::RbCore.set_data_ptr_and_type({{obj}}, %new_obj_ptr, Anyolite::RbTypeCache.register({{crystal_class}}, %destructor)) end end end end ================================================ FILE: src/macros/RubyConversions.cr ================================================ module Anyolite module Macro macro convert_from_ruby_object(rb, obj, crystal_type) if !Anyolite::RbCast.check_for_data({{obj}}) %obj_class = Anyolite::RbCore.get_class_of_obj({{rb}}, {{obj}}) %rb_class_name = String.new(Anyolite::RbCore.rb_class_name({{rb}}, %obj_class)) Anyolite.raise_argument_error "Could not convert data type #{%rb_class_name} into object class #{{{crystal_type.stringify}}}." end if !Anyolite::RbCore.get_data_ptr({{obj}}) %obj_class = Anyolite::RbCore.get_class_of_obj({{rb}}, {{obj}}) %rb_class_name = String.new(Anyolite::RbCore.rb_class_name({{rb}}, %obj_class)) Anyolite.raise_runtime_error "Object of class #{%rb_class_name} has content incompatible to Crystal class #{{{crystal_type.stringify}}}." end if !Anyolite::RbCast.check_custom_type({{rb}}, {{obj}}, {{crystal_type}}) %obj_class = Anyolite::RbCore.get_class_of_obj({{rb}}, {{obj}}) %rb_class_name = String.new(Anyolite::RbCore.rb_class_name({{rb}}, %obj_class)) Anyolite.raise_argument_error "Invalid data type #{%rb_class_name} for object class #{{{crystal_type.stringify}}}." end %ptr = Anyolite::RbCore.get_data_ptr({{obj}}) %ptr.as({{crystal_type}}*) end macro convert_from_ruby_struct(rb, obj, crystal_type) if !Anyolite::RbCast.check_for_data({{obj}}) %obj_class = Anyolite::RbCore.get_class_of_obj({{rb}}, {{obj}}) %rb_class_name = String.new(Anyolite::RbCore.rb_class_name({{rb}}, %obj_class)) Anyolite.raise_argument_error "Could not convert data type #{%rb_class_name} into struct class #{{{crystal_type.stringify}}}." end if !Anyolite::RbCore.get_data_ptr({{obj}}) %obj_class = Anyolite::RbCore.get_class_of_obj({{rb}}, {{obj}}) %rb_class_name = String.new(Anyolite::RbCore.rb_class_name({{rb}}, %obj_class)) Anyolite.raise_runtime_error "Object of class #{%rb_class_name} has content incompatible to Crystal struct #{{{crystal_type.stringify}}}." end if !Anyolite::RbCast.check_custom_type({{rb}}, {{obj}}, {{crystal_type}}) %obj_class = Anyolite::RbCore.get_class_of_obj({{rb}}, {{obj}}) %rb_class_name = String.new(Anyolite::RbCore.rb_class_name({{rb}}, %obj_class)) Anyolite.raise_argument_error "Invalid data type #{%rb_class_name} for struct class #{{{crystal_type.stringify}}}" end %ptr = Anyolite::RbCore.get_data_ptr({{obj}}) %ptr.as(Anyolite::StructWrapper({{crystal_type}})*) end end end ================================================ FILE: src/macros/RubyTypes.cr ================================================ module Anyolite module Macro macro type_in_ruby(type, options = {} of Symbol => NoReturn) {% if type.is_a?(TypeDeclaration) %} {% if type.type.is_a?(Union) %} Anyolite::RbCore::RbValue {% else %} Anyolite::Macro.type_in_ruby({{type.type}}) {% end %} {% elsif options[:context] %} Anyolite::Macro.resolve_type_in_ruby({{options[:context]}}::{{type.stringify.starts_with?("::") ? type.stringify[2..-1].id : type}}, {{type}}, options: {{options}}) {% else %} Anyolite::Macro.resolve_type_in_ruby({{type}}, {{type}}) {% end %} end macro resolve_type_in_ruby(type, raw_type, options = {} of Symbol => NoReturn) {% if type.resolve? %} {% if ANYOLITE_INTERNAL_FLAG_USE_GENERAL_OBJECT_FORMAT_CHARS %} Anyolite::RbCore::RbValue {% else %} {% if type.resolve <= Bool %} Anyolite::RbCore::RbBool {% elsif type.resolve <= Int || type.resolve <= Pointer %} Anyolite::RbCore::RbInt {% elsif type.resolve <= Float || type.resolve == Number %} Anyolite::RbCore::RbFloat {% elsif type.resolve <= String %} # Should actually never occur due to special handling before this function Pointer(LibC::Char) {% elsif type.resolve <= Anyolite::RbRef %} Anyolite::RbCore::RbValue {% elsif type.resolve <= Regex %} Anyolite::RbCore::RbValue {% elsif type.resolve <= Array %} Anyolite::RbCore::RbValue {% else %} Anyolite::RbCore::RbValue {% end %} {% end %} {% elsif options[:context] %} {% if options[:context].names[0..-2].size > 0 %} {% new_context = options[:context].names[0..-2].join("::").gsub(/(\:\:)+/, "::").id %} {% options[:context] = new_context %} Anyolite::Macro.resolve_type_in_ruby({{new_context}}::{{raw_type.stringify.starts_with?("::") ? raw_type.stringify[2..-1].id : raw_type}}, {{raw_type}}, options: {{options}}) {% else %} Anyolite::Macro.resolve_type_in_ruby({{raw_type}}, {{raw_type}}) {% end %} {% else %} Anyolite::RbCore::RbValue {% end %} end macro pointer_type(type, options = {} of Symbol => NoReturn) {% if type.is_a?(TypeDeclaration) %} {% if type.type.is_a?(Union) %} Pointer(Anyolite::RbCore::RbValue) {% else %} Anyolite::Macro.pointer_type({{type.type}}, options: {{options}}) {% end %} {% elsif options[:context] %} Anyolite::Macro.resolve_pointer_type({{options[:context]}}::{{type.stringify.starts_with?("::") ? type.stringify[2..-1].id : type}}, {{type}}, options: {{options}}) {% else %} Anyolite::Macro.resolve_pointer_type({{type}}, {{type}}) {% end %} end macro resolve_pointer_type(type, raw_type, options = {} of Symbol => NoReturn) {% if type.resolve? %} {% if ANYOLITE_INTERNAL_FLAG_USE_GENERAL_OBJECT_FORMAT_CHARS %} Pointer(Anyolite::RbCore::RbValue) {% else %} {% if type.resolve <= Bool %} Pointer(Anyolite::RbCore::RbBool) {% elsif type.resolve <= Int %} Pointer(Anyolite::RbCore::RbInt) {% elsif type.resolve <= Float || type.resolve == Number %} Pointer(Anyolite::RbCore::RbFloat) {% elsif type.resolve <= String %} Pointer(LibC::Char*) {% elsif type.resolve <= Anyolite::RbRef %} Pointer(Anyolite::RbCore::RbValue) {% elsif type.resolve <= Array %} Pointer(Anyolite::RbCore::RbValue) {% else %} Pointer(Anyolite::RbCore::RbValue) {% end %} {% end %} {% elsif options[:context] %} {% if options[:context].names[0..-2].size > 0 %} {% new_context = options[:context].names[0..-2].join("::").gsub(/(\:\:)+/, "::").id %} {% options[:context] = new_context %} Anyolite::Macro.resolve_pointer_type({{new_context}}::{{raw_type.stringify.starts_with?("::") ? raw_type.stringify[2..-1].id : raw_type}}, {{raw_type}}, options: {{options}}) {% else %} Anyolite::Macro.resolve_pointer_type({{raw_type}}, {{raw_type}}) {% end %} {% else %} Pointer(Anyolite::RbCore::RbValue) {% end %} end end end ================================================ FILE: src/macros/UnionCasts.cr ================================================ module Anyolite module Macro macro cast_to_union_value(rb, value, types, options = {} of Symbol => NoReturn, debug_information = nil) _final_value = :invalid {% for type in types %} {% if type.resolve? %} Anyolite::Macro.check_and_cast_union_type({{rb}}, {{value}}, {{type}}, {{type}}, options: {{options}}) {% elsif options[:context] %} Anyolite::Macro.check_and_cast_union_type({{rb}}, {{value}}, {{options[:context]}}::{{type.stringify.starts_with?("::") ? type.stringify[2..-1].id : type}}, {{type}}, options: {{options}}) {% else %} {% raise "Could not resolve type #{type}, which is a #{type.class_name}, in context #{options[:context]} (#{debug_information.id})" %} {% end %} {% end %} if _final_value.is_a?(Symbol) # TODO: Better value description Anyolite::RbCast.casting_error({{rb}}, {{value}}, "{{types}}", nil) #Anyolite.raise_argument_error("Could not determine any value for #{{{value}}} with types {{types}} in context {{options[:context]}}") raise("Should not be reached") else _final_value end end macro check_and_cast_union_type(rb, value, type, raw_type, options = {} of Symbol => NoReturn, debug_information = nil) {% if type.resolve? %} Anyolite::Macro.check_and_cast_resolved_union_type({{rb}}, {{value}}, {{type}}, {{type}}) {% elsif options[:context] %} {% if options[:context].names[0..-2].size > 0 %} {% new_context = options[:context].names[0..-2].join("::").gsub(/(\:\:)+/, "::").id %} {% options[:context] = new_context %} Anyolite::Macro.check_and_cast_union_type({{rb}}, {{value}}, {{options[:context]}}::{{raw_type.stringify.starts_with?("::") ? raw_type[2..-1] : raw_type}}, {{raw_type}}, options: {{options}}) {% else %} Anyolite::Macro.check_and_cast_union_type({{rb}}, {{value}}, {{raw_type}}, {{raw_type}}) {% end %} {% else %} {% raise "Could not resolve type #{type}, which is a #{type.class_name} (#{debug_information.id})" %} {% end %} end # TODO: Some double checks could be omitted macro check_and_cast_resolved_union_type(rb, value, type, raw_type, options = {} of Symbol => NoReturn, debug_information = nil) {% if type.resolve <= Nil %} if Anyolite::RbCast.check_for_nil({{value}}) _final_value = Anyolite::RbCast.cast_to_nil({{rb}}, {{value}}) end {% elsif type.resolve <= Bool %} if Anyolite::RbCast.check_for_bool({{value}}) _final_value = Anyolite::RbCast.cast_to_bool({{rb}}, {{value}}) end {% elsif type.resolve == Number %} if Anyolite::RbCast.check_for_float({{value}}) begin _final_value = Float64.new(Anyolite::RbCast.cast_to_float({{rb}}, {{value}})) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{Anyolite::RbCast.cast_to_float({{rb}}, {{value}})} to {{type}}.") Float64.new(0.0) end end {% elsif type.resolve == Int %} if Anyolite::RbCast.check_for_fixnum({{value}}) begin _final_value = Int64.new(Anyolite::RbCast.cast_to_int({{rb}}, {{value}})) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{Anyolite::RbCast.cast_to_int({{rb}}, {{value}})} to {{type}}.") Int64.new(0) end end {% elsif type.resolve <= Int %} if Anyolite::RbCast.check_for_fixnum({{value}}) begin _final_value = {{type}}.new(Anyolite::RbCast.cast_to_int({{rb}}, {{value}})) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{Anyolite::RbCast.cast_to_int({{rb}}, {{value}})} to {{type}}.") {{type}}.new(0) end end {% elsif type.resolve == Float %} if Anyolite::RbCast.check_for_float({{value}}) || Anyolite::RbCast.check_for_fixnum({{value}}) begin _final_value = Float64.new(Anyolite::RbCast.cast_to_float({{rb}}, {{value}})) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{Anyolite::RbCast.cast_to_int({{rb}}, {{value}})} to {{type}}.") Float64.new(0) end end {% elsif type.resolve <= Float %} if Anyolite::RbCast.check_for_float({{value}}) || Anyolite::RbCast.check_for_fixnum({{value}}) begin _final_value = {{type}}.new(Anyolite::RbCast.cast_to_float({{rb}}, {{value}})) rescue OverflowError Anyolite.raise_range_error("Overflow while casting #{Anyolite::RbCast.cast_to_int({{rb}}, {{value}})} to {{type}}.") {{type}}.new(0) end end {% elsif type.resolve <= Char %} if Anyolite::RbCast.check_for_string({{value}}) _final_value = Anyolite::RbCast.cast_to_char({{rb}}, {{value}}) end {% elsif type.resolve <= String %} if Anyolite::RbCast.check_for_string({{value}}) || Anyolite::RbCast.check_for_symbol({{value}}) _final_value = Anyolite::RbCast.cast_to_string({{rb}}, {{value}}) end {% elsif type.resolve <= Anyolite::RbRef %} _final_value = {{type}}.new({{value}}) {% elsif type.resolve <= Array %} if Anyolite::RbCast.check_for_array({{value}}) %array_size = Anyolite::RbCore.array_length({{value}}) %converted_array = {{type}}.new(size: %array_size) do |%index| Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, Anyolite::RbCore.rb_ary_entry({{value}}, %index), {{type.type_vars[0]}}) end _final_value = %converted_array end {% elsif type.resolve <= Regex %} Anyolite::Macro.convert_regex_from_ruby_to_crystal({{rb}}, {{value}}, {{type}}) {% elsif type.resolve <= Hash %} if Anyolite::RbCast.check_for_hash({{value}}) %hash_size = Anyolite::RbCore.rb_hash_size({{rb}}, {{value}}) %all_rb_hash_keys = Anyolite::RbCore.rb_hash_keys({{rb}}, {{value}}) %all_converted_hash_keys = Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %all_rb_hash_keys, Array({{type.type_vars[0]}}), options: {{options}}) %converted_hash = {{type}}.new(initial_capacity: %hash_size) %all_converted_hash_keys.each_with_index do |%key, %index| %rb_key = Anyolite::RbCore.rb_ary_entry(%all_rb_hash_keys, %index) %rb_value = Anyolite::RbCore.rb_hash_get({{rb}}, {{value}}, %rb_key) %converted_hash[%key] = Anyolite::Macro.convert_from_ruby_to_crystal({{rb}}, %rb_value, {{type.type_vars[1]}}, options: {{options}}) end _final_value = %converted_hash end {% elsif type.resolve <= Pointer %} if Anyolite::RbCast.check_for_data({{value}}) && Anyolite::RbCast.check_custom_type({{rb}}, {{value}}, Anyolite::HelperClasses::AnyolitePointer) %helper_ptr = Anyolite::Macro.convert_from_ruby_object({{rb}}, {{value}}, Anyolite::HelperClasses::AnyolitePointer).value _final_value = Box({{type}}).unbox(%helper_ptr.retrieve_ptr) end {% elsif type.resolve <= Struct || type.resolve <= Enum %} if Anyolite::RbCast.check_for_data({{value}}) && Anyolite::RbCast.check_custom_type({{rb}}, {{value}}, {{type}}) _final_value = Anyolite::Macro.convert_from_ruby_struct({{rb}}, {{value}}, {{type}}).value.content end {% elsif type.resolve? %} if Anyolite::RbCast.check_for_data({{value}}) && Anyolite::RbCast.check_custom_type({{rb}}, {{value}}, {{type}}) _final_value = Anyolite::Macro.convert_from_ruby_object({{rb}}, {{value}}, {{type}}).value end {% else %} {% raise "Could not resolve type #{type}, which is a #{type.class_name} (#{debug_information.id})" %} {% end %} end end end ================================================ FILE: src/macros/WrapAll.cr ================================================ module Anyolite module Macro macro wrap_all_instance_methods(rb_interpreter, crystal_class, exclusions, verbose, context = nil, use_enum_methods = false, wrap_equality_method = false, other_source = nil, later_ancestors = nil) {% has_specialized_method = {} of String => Bool %} {% method_source = other_source ? other_source : crystal_class %} {% default_optional_args_to_keyword_args = parse_type("ANYOLITE_DEFAULT_OPTIONAL_ARGS_TO_KEYWORD_ARGS").resolve? %} {% if !crystal_class.resolve.annotations(Anyolite::DefaultOptionalArgsToKeywordArgs).empty? %} {% default_optional_args_to_keyword_args = true %} {% else %} {% for class_ancestor in crystal_class.resolve.ancestors %} {% if !class_ancestor.resolve.annotations(Anyolite::DefaultOptionalArgsToKeywordArgs).empty? %} {% default_optional_args_to_keyword_args = true %} {% end %} {% end %} {% end %} {% if !method_source.resolve? %} {% puts "> Skipping #{method_source} due to private visibility." if verbose %} {% else %} {% for method in method_source.resolve.methods %} {% all_annotations_specialize_im = crystal_class.resolve.annotations(Anyolite::SpecializeInstanceMethod) + method_source.resolve.annotations(Anyolite::SpecializeInstanceMethod) %} {% annotation_specialize_im = all_annotations_specialize_im.find { |element| element[0].stringify == method.name.stringify || element[0] == method.name.stringify } %} {% if method.annotation(Anyolite::Specialize) %} {% has_specialized_method[method.name.stringify] = true %} {% end %} {% if annotation_specialize_im %} {% has_specialized_method[annotation_specialize_im[0].id.stringify] = true %} {% end %} {% end %} {% how_many_times_wrapped = {} of String => UInt32 %} {% for method, index in method_source.resolve.methods %} {% all_annotations_exclude_im = crystal_class.resolve.annotations(Anyolite::ExcludeInstanceMethod) + method_source.resolve.annotations(Anyolite::ExcludeInstanceMethod) + crystal_class.resolve.ancestors.map { |ancestor| ancestor.resolve.annotations(Anyolite::ExcludeInstanceMethod) } %} {% annotation_exclude_im = all_annotations_exclude_im.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_include_im = crystal_class.resolve.annotations(Anyolite::IncludeInstanceMethod) + method_source.resolve.annotations(Anyolite::IncludeInstanceMethod) %} {% annotation_include_im = all_annotations_include_im.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_specialize_im = crystal_class.resolve.annotations(Anyolite::SpecializeInstanceMethod) + method_source.resolve.annotations(Anyolite::SpecializeInstanceMethod) %} {% annotation_specialize_im = all_annotations_specialize_im.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_rename_im = crystal_class.resolve.annotations(Anyolite::RenameInstanceMethod) + method_source.resolve.annotations(Anyolite::RenameInstanceMethod) %} {% annotation_rename_im = all_annotations_rename_im.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_without_keywords_im = crystal_class.resolve.annotations(Anyolite::WrapWithoutKeywordsInstanceMethod) + method_source.resolve.annotations(Anyolite::WrapWithoutKeywordsInstanceMethod) %} {% annotation_without_keyword_im = all_annotations_without_keywords_im.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_return_nil_im = crystal_class.resolve.annotations(Anyolite::ReturnNilInstanceMethod) + method_source.resolve.annotations(Anyolite::ReturnNilInstanceMethod) %} {% annotation_return_nil_im = all_annotations_return_nil_im.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_add_block_arg_im = crystal_class.resolve.annotations(Anyolite::AddBlockArgInstanceMethod) + method_source.resolve.annotations(Anyolite::AddBlockArgInstanceMethod) %} {% annotation_add_block_arg_im = all_annotations_add_block_arg_im.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_store_block_arg_im = crystal_class.resolve.annotations(Anyolite::StoreBlockArgInstanceMethod) + method_source.resolve.annotations(Anyolite::StoreBlockArgInstanceMethod) %} {% annotation_store_block_arg_im = all_annotations_store_block_arg_im.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_force_keyword_arg_im = crystal_class.resolve.annotations(Anyolite::ForceKeywordArgInstanceMethod) + method_source.resolve.annotations(Anyolite::ForceKeywordArgInstanceMethod) %} {% annotation_force_keyword_arg_im = all_annotations_force_keyword_arg_im.find { |element| element[0].id.stringify == method.name.stringify } %} {% if crystal_class.resolve.annotation(Anyolite::NoKeywordArgs) || method_source.resolve.annotation(Anyolite::NoKeywordArgs) %} {% no_keyword_args = true %} {% else %} {% no_keyword_args = false %} {% end %} {% if method.annotation(Anyolite::Rename) %} {% ruby_name = method.annotation(Anyolite::Rename)[0].id %} {% elsif annotation_rename_im && method.name.stringify == annotation_rename_im[0].id.stringify %} {% ruby_name = annotation_rename_im[1].id %} {% else %} {% ruby_name = method.name %} {% end %} {% if method.annotation(Anyolite::AddBlockArg) %} {% block_arg_number = method.annotation(Anyolite::AddBlockArg)[0] %} {% block_return_type = method.annotation(Anyolite::AddBlockArg)[1] %} {% elsif annotation_add_block_arg_im && method.name.stringify == annotation_add_block_arg_im[0].id.stringify %} {% block_arg_number = annotation_add_block_arg_im[1] %} {% block_return_type = annotation_add_block_arg_im[2] %} {% else %} {% block_arg_number = nil %} {% block_return_type = nil %} {% end %} {% added_keyword_args = nil %} {% if method.annotation(Anyolite::Specialize) && method.annotation(Anyolite::Specialize)[0] %} {% added_keyword_args = method.annotation(Anyolite::Specialize)[0] %} {% end %} {% if annotation_specialize_im && (method.args.stringify == annotation_specialize_im[1].stringify || (method.args.stringify == "[]" && annotation_specialize_im[1] == nil)) %} {% added_keyword_args = annotation_specialize_im[2] %} {% end %} {% without_keywords = false %} {% if method.annotation(Anyolite::WrapWithoutKeywords) %} {% without_keywords = method.annotation(Anyolite::WrapWithoutKeywords)[0] ? method.annotation(Anyolite::WrapWithoutKeywords)[0] : -1 %} {% elsif annotation_without_keyword_im %} {% without_keywords = annotation_without_keyword_im[1] ? annotation_without_keyword_im[1] : -1 %} {% elsif default_optional_args_to_keyword_args %} {% without_keywords = -2 %} {% end %} {% return_nil = false %} {% if method.annotation(Anyolite::ReturnNil) || (annotation_return_nil_im) %} {% return_nil = true %} {% end %} {% store_block_arg = false %} {% if method.annotation(Anyolite::StoreBlockArg) || (annotation_store_block_arg_im) %} {% store_block_arg = true %} {% end %} {% force_keyword_arg = false %} {% if method.annotation(Anyolite::ForceKeywordArg) || (annotation_force_keyword_arg_im) %} {% force_keyword_arg = true %} {% end %} {% puts "> Processing instance method #{crystal_class}::#{method.name} to #{ruby_name}\n--> Args: #{method.args}" if verbose %} {% if method.accepts_block? %} {% puts "--> Block arg possible for #{crystal_class}::#{method.name}" if verbose %} {% end %} # Ignore private and protected methods (can't be called from outside, they'd need to be wrapped for this to work) {% if method.name == "dup" || method.name == "clone" %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Exclusion due to copy property)" if verbose %} {% elsif crystal_class.resolve.abstract? && method.name == "initialize" %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Exclusion due to abstract class)" if verbose %} {% elsif method.visibility != :public && method.name != "initialize" %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Exclusion due to visibility)" if verbose %} {% elsif crystal_class != method_source && (later_ancestors ? later_ancestors + [crystal_class] : [crystal_class]).find { |later_ancestor| later_ancestor.resolve? ? later_ancestor.resolve.methods.find { |orig_methods| orig_methods.name == method.name } : [] of Def } %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Exclusion due to finalization)" if verbose %} # Ignore rb hooks, to_unsafe and finalize (unless specialized, but this is not recommended) {% elsif (method.name.starts_with?("rb_") || method.name == "finalize" || method.name == "to_unsafe") && !has_specialized_method[method.name.stringify] %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Exclusion by default)" if verbose %} # Exclude methods if given as arguments {% elsif exclusions.includes?(method.name.symbolize) || exclusions.includes?(method.name.stringify) %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Exclusion argument)" if verbose %} # Exclude methods which were annotated to be excluded {% elsif method.annotation(Anyolite::Exclude) || (annotation_exclude_im && !annotation_include_im && !method.annotation(Anyolite::Include)) %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Exclusion annotation)" if verbose %} # Exclude methods which are not the specialized methods {% elsif has_specialized_method[method.name.stringify] && !(method.annotation(Anyolite::Specialize) || (annotation_specialize_im && (method.args.stringify == annotation_specialize_im[1].stringify || (method.args.stringify == "[]" && annotation_specialize_im[1] == nil)))) %} {% puts "--> Excluding #{crystal_class}::#{method.name} #{method.args} (Specialization)" if verbose %} # These methods are a bit tricky and technically require an argument, but usually they are called without one, so we just do it here, too {% elsif method.name == "inspect" || method.name == "to_s" || method.name == "hash" %} Anyolite::Macro.wrap_method_index({{rb_interpreter}}, {{crystal_class}}, {{index}}, "{{ruby_name}}", without_keywords: {{without_keywords || (no_keyword_args && !force_keyword_arg)}}, added_keyword_args: [] of NoReturn, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}, other_source: {{other_source}}) {% how_many_times_wrapped[ruby_name.stringify] = how_many_times_wrapped[ruby_name.stringify] ? how_many_times_wrapped[ruby_name.stringify] + 1 : 1 %} # Handle operator methods (including setters) by just transferring the original name into the operator # TODO: This might still be a source for potential bugs, so this code might need some reworking in the future {% elsif method.name[-1..-1] =~ /\W/ %} {% operator = ruby_name %} Anyolite::Macro.wrap_method_index({{rb_interpreter}}, {{crystal_class}}, {{index}}, "{{ruby_name}}", operator: "{{operator}}", without_keywords: {{force_keyword_arg ? false : -1}}, added_keyword_args: {{added_keyword_args}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}, other_source: {{other_source}}) {% how_many_times_wrapped[ruby_name.stringify] = how_many_times_wrapped[ruby_name.stringify] ? how_many_times_wrapped[ruby_name.stringify] + 1 : 1 %} # Handle constructors {% elsif method.name == "initialize" && use_enum_methods == false %} Anyolite::Macro.wrap_method_index({{rb_interpreter}}, {{crystal_class}}, {{index}}, "{{ruby_name}}", is_constructor: true, without_keywords: {{without_keywords || (no_keyword_args && !force_keyword_arg)}}, added_keyword_args: {{added_keyword_args}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}, other_source: {{other_source}}) {% how_many_times_wrapped[ruby_name.stringify] = how_many_times_wrapped[ruby_name.stringify] ? how_many_times_wrapped[ruby_name.stringify] + 1 : 1 %} # Handle other instance methods {% else %} Anyolite::Macro.wrap_method_index({{rb_interpreter}}, {{crystal_class}}, {{index}}, "{{ruby_name}}", without_keywords: {{without_keywords || (no_keyword_args && !force_keyword_arg)}}, added_keyword_args: {{added_keyword_args}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}, other_source: {{other_source}}) {% how_many_times_wrapped[ruby_name.stringify] = how_many_times_wrapped[ruby_name.stringify] ? how_many_times_wrapped[ruby_name.stringify] + 1 : 1 %} {% end %} {% if how_many_times_wrapped[ruby_name.stringify] && how_many_times_wrapped[ruby_name.stringify] > 1 %} {% puts "\e[31m> WARNING: Method #{crystal_class}::#{ruby_name}\n--> New arguments: #{method.args}\n--> Wrapped more than once (#{how_many_times_wrapped[ruby_name.stringify]}).\e[0m" %} {% end %} {% puts "" if verbose %} {% end %} {% if method_source == crystal_class && !crystal_class.resolve.abstract? %} # Make sure to add a default constructor if none was specified with Crystal {% if !how_many_times_wrapped["initialize"] && !use_enum_methods %} Anyolite::Macro.add_default_constructor({{rb_interpreter}}, {{crystal_class}}, {{verbose}}) {% elsif !how_many_times_wrapped["initialize"] && use_enum_methods %} Anyolite::Macro.add_enum_constructor({{rb_interpreter}}, {{crystal_class}}, {{verbose}}) {% end %} {% if !how_many_times_wrapped["inspect"] && use_enum_methods %} Anyolite::Macro.add_enum_inspect({{rb_interpreter}}, {{crystal_class}}, {{verbose}}) {% end %} {% all_annotations_exclude_im = crystal_class.resolve.annotations(Anyolite::ExcludeInstanceMethod) + method_source.resolve.annotations(Anyolite::ExcludeInstanceMethod) %} {% if all_annotations_exclude_im.find { |element| element[0].id.stringify == "dup" || element[0].id.stringify == "clone" } %} Anyolite::RbCore.rb_undef_method({{rb_interpreter}}, Anyolite::RbClassCache.get({{crystal_class}}), "dup") Anyolite::RbCore.rb_undef_method({{rb_interpreter}}, Anyolite::RbClassCache.get({{crystal_class}}), "clone") {% else %} Anyolite::Macro.add_copy_constructor({{rb_interpreter}}, {{crystal_class}}, {{context}}, {{verbose}}) {% end %} {% if wrap_equality_method && !how_many_times_wrapped["=="] %} Anyolite::Macro.add_equality_method({{rb_interpreter}}, {{crystal_class}}, {{context}}, {{verbose}}) {% end %} {% end %} {% end %} end macro wrap_all_class_methods(rb_interpreter, crystal_class, exclusions, verbose, context = nil) {% has_specialized_method = {} of String => Bool %} {% for method in crystal_class.resolve.class.methods %} {% all_annotations_specialize_cm = crystal_class.resolve.annotations(Anyolite::SpecializeClassMethod) %} {% annotation_specialize_cm = all_annotations_specialize_cm.find { |element| element[0].stringify == method.name.stringify || element[0] == method.name.stringify } %} {% if method.annotation(Anyolite::Specialize) %} {% has_specialized_method[method.name.stringify] = true %} {% end %} {% if annotation_specialize_cm %} {% has_specialized_method[annotation_specialize_cm[0].id.stringify] = true %} {% end %} {% end %} {% how_many_times_wrapped = {} of String => UInt32 %} {% default_optional_args_to_keyword_args = false %} {% if !crystal_class.resolve.annotations(Anyolite::DefaultOptionalArgsToKeywordArgs).empty? %} {% default_optional_args_to_keyword_args = true %} {% else %} {% for class_ancestor in crystal_class.resolve.ancestors %} {% if !class_ancestor.resolve.annotations(Anyolite::DefaultOptionalArgsToKeywordArgs).empty? %} {% default_optional_args_to_keyword_args = true %} {% end %} {% end %} {% end %} {% for method, index in crystal_class.resolve.class.methods %} {% all_annotations_exclude_cm = crystal_class.resolve.annotations(Anyolite::ExcludeClassMethod) %} {% annotation_exclude_cm = all_annotations_exclude_cm.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_specialize_cm = crystal_class.resolve.annotations(Anyolite::SpecializeClassMethod) %} {% annotation_specialize_cm = all_annotations_specialize_cm.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_rename_cm = crystal_class.resolve.annotations(Anyolite::RenameClassMethod) %} {% annotation_rename_cm = all_annotations_rename_cm.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_without_keywords_cm = crystal_class.resolve.annotations(Anyolite::WrapWithoutKeywordsClassMethod) %} {% annotation_without_keyword_cm = all_annotations_without_keywords_cm.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_return_nil_cm = crystal_class.resolve.annotations(Anyolite::ReturnNilClassMethod) %} {% annotation_return_nil_cm = all_annotations_return_nil_cm.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_add_block_arg_cm = crystal_class.resolve.annotations(Anyolite::AddBlockArgClassMethod) %} {% annotation_add_block_arg_cm = all_annotations_add_block_arg_cm.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_store_block_arg_cm = crystal_class.resolve.annotations(Anyolite::StoreBlockArgClassMethod) %} {% annotation_store_block_arg_cm = all_annotations_store_block_arg_cm.find { |element| element[0].id.stringify == method.name.stringify } %} {% all_annotations_force_keyword_arg_cm = crystal_class.resolve.annotations(Anyolite::ForceKeywordArgClassMethod) %} {% annotation_force_keyword_arg_cm = all_annotations_force_keyword_arg_cm.find { |element| element[0].id.stringify == method.name.stringify } %} {% if crystal_class.resolve.annotation(Anyolite::NoKeywordArgs) %} {% no_keyword_args = true %} {% else %} {% no_keyword_args = false %} {% end %} {% if method.annotation(Anyolite::Rename) %} {% ruby_name = method.annotation(Anyolite::Rename)[0].id %} {% elsif annotation_rename_cm && method.name.stringify == annotation_rename_cm[0].stringify %} {% ruby_name = annotation_rename_cm[1].id %} {% else %} {% ruby_name = method.name %} {% end %} {% if method.annotation(Anyolite::AddBlockArg) %} {% block_arg_number = method.annotation(Anyolite::AddBlockArg)[0] %} {% block_return_type = method.annotation(Anyolite::AddBlockArg)[1] %} {% elsif annotation_add_block_arg_cm && method.name.stringify == annotation_add_block_arg_cm[0].stringify %} {% block_arg_number = annotation_add_block_arg_cm[1] %} {% block_return_type = annotation_add_block_arg_cm[2] %} {% else %} {% block_arg_number = nil %} {% block_return_type = nil %} {% end %} {% added_keyword_args = nil %} {% if method.annotation(Anyolite::Specialize) && method.annotation(Anyolite::Specialize)[1] %} {% added_keyword_args = method.annotation(Anyolite::Specialize)[1] %} {% end %} {% if annotation_specialize_cm && (method.args.stringify == annotation_specialize_cm[1].stringify || (method.args.stringify == "[]" && annotation_specialize_cm[1] == nil)) %} {% added_keyword_args = annotation_specialize_cm[2] %} {% end %} {% without_keywords = false %} {% if method.annotation(Anyolite::WrapWithoutKeywords) %} {% without_keywords = method.annotation(Anyolite::WrapWithoutKeywords)[0] ? method.annotation(Anyolite::WrapWithoutKeywords)[0] : -1 %} {% elsif annotation_without_keyword_cm %} {% without_keywords = annotation_without_keyword_cm[1] ? annotation_without_keyword_cm[1] : -1 %} {% elsif default_optional_args_to_keyword_args %} {% without_keywords = -2 %} {% end %} {% return_nil = false %} {% if method.annotation(Anyolite::ReturnNil) || (annotation_return_nil_cm) %} {% return_nil = true %} {% end %} {% store_block_arg = false %} {% if method.annotation(Anyolite::StoreBlockArg) || (annotation_store_block_arg_cm) %} {% store_block_arg = true %} {% end %} {% force_keyword_arg = false %} {% if method.annotation(Anyolite::ForceKeywordArg) || (annotation_force_keyword_arg_cm) %} {% force_keyword_arg = true %} {% end %} {% puts "> Processing class method #{crystal_class}::#{method.name} to #{ruby_name}\n--> Args: #{method.args}" if verbose %} {% if method.accepts_block? %} {% puts "--> Block arg possible for #{crystal_class}::#{method.name}" if verbose %} {% end %} # Ignore private and protected methods (can't be called from outside, they'd need to be wrapped for this to work) {% if method.visibility != :public %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Exclusion due to visibility)" if verbose %} # We already wrapped 'initialize', so we don't need to wrap these {% elsif method.name == "allocate" || method.name == "new" %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Allocation method)" if verbose %} {% elsif method.name == "parse?" && method.args.stringify == "[string]" %} {% operator = ruby_name %} Anyolite::Macro.wrap_method_index({{rb_interpreter}}, {{crystal_class}}, {{index}}, "{{ruby_name}}", operator: "{{operator}}", is_class_method: true, added_keyword_args: [string : String], without_keywords: {{force_keyword_arg ? false : -1}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% how_many_times_wrapped[ruby_name.stringify] = how_many_times_wrapped[ruby_name.stringify] ? how_many_times_wrapped[ruby_name.stringify] + 1 : 1 %} {% elsif method.name == "<=" %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Class argument method)" if verbose %} # Exclude methods if given as arguments {% elsif exclusions.includes?(method.name.symbolize) || exclusions.includes?(method.name.stringify) %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Exclusion argument)" if verbose %} # Exclude methods which were annotated to be excluded {% elsif method.annotation(Anyolite::Exclude) || (annotation_exclude_cm) %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Exclusion annotation)" if verbose %} # Exclude methods which are not the specialized methods {% elsif has_specialized_method[method.name.stringify] && !(method.annotation(Anyolite::Specialize) || (annotation_specialize_cm && (method.args.stringify == annotation_specialize_cm[1].stringify || (method.args.stringify == "[]" && annotation_specialize_cm[1] == nil)))) %} {% puts "--> Excluding #{crystal_class}::#{method.name} (Specialization)" if verbose %} {% elsif method.name[-1..-1] =~ /\W/ %} {% operator = ruby_name %} Anyolite::Macro.wrap_method_index({{rb_interpreter}}, {{crystal_class}}, {{index}}, "{{ruby_name}}", operator: "{{operator}}", is_class_method: true, added_keyword_args: {{added_keyword_args}}, without_keywords: {{force_keyword_arg ? false : -1}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% how_many_times_wrapped[ruby_name.stringify] = how_many_times_wrapped[ruby_name.stringify] ? how_many_times_wrapped[ruby_name.stringify] + 1 : 1 %} # Handle other class methods {% else %} Anyolite::Macro.wrap_method_index({{rb_interpreter}}, {{crystal_class}}, {{index}}, "{{ruby_name}}", is_class_method: true, without_keywords: {{without_keywords || (no_keyword_args && !force_keyword_arg)}}, added_keyword_args: {{added_keyword_args}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% how_many_times_wrapped[ruby_name.stringify] = how_many_times_wrapped[ruby_name.stringify] ? how_many_times_wrapped[ruby_name.stringify] + 1 : 1 %} {% end %} {% if how_many_times_wrapped[ruby_name.stringify] && how_many_times_wrapped[ruby_name.stringify] > 1 %} {% puts "\e[31m> WARNING: Method #{crystal_class}::#{ruby_name}\n--> New arguments: #{method.args}\n--> Wrapped more than once (#{how_many_times_wrapped[ruby_name.stringify]}).\e[0m" %} {% end %} {% puts "" if verbose %} {% end %} end macro wrap_all_constants(rb_interpreter, crystal_class, exclusions, verbose = false, overwrite = false, context = nil) # TODO: Is the context needed here? # NOTE: This check is necessary due to https://github.com/crystal-lang/crystal/issues/5757 {% if crystal_class.resolve.type_vars.empty? %} {% for constant, index in crystal_class.resolve.constants %} {% all_annotations_exclude_cm = crystal_class.resolve.annotations(Anyolite::ExcludeConstant) %} {% annotation_exclude_cm = all_annotations_exclude_cm.find { |element| element[0].id.stringify == constant.stringify } %} {% all_annotations_rename_cm = crystal_class.resolve.annotations(Anyolite::RenameConstant) %} {% annotation_rename_cm = all_annotations_rename_cm.find { |element| element[0].id.stringify == constant.stringify } %} {% if annotation_rename_cm && constant.stringify == annotation_rename_cm[0].stringify %} {% ruby_name = annotation_rename_cm[1].id %} {% else %} {% ruby_name = constant %} {% end %} {% puts "> Processing constant #{crystal_class}::#{constant} to #{ruby_name}" if verbose %} # Exclude methods which were annotated to be excluded {% if exclusions.includes?(constant.symbolize) || exclusions.includes?(constant) %} {% puts "--> Excluding #{crystal_class}::#{constant} (Exclusion argument)" if verbose %} {% elsif annotation_exclude_cm %} {% puts "--> Excluding #{crystal_class}::#{constant} (Exclusion annotation)" if verbose %} {% else %} Anyolite::Macro.wrap_constant_or_class({{rb_interpreter}}, {{crystal_class}}, "{{ruby_name}}", {{constant}}, overwrite: {{overwrite}}, verbose: {{verbose}}) {% end %} {% puts "" if verbose %} {% end %} {% end %} end end end ================================================ FILE: src/macros/WrapMethodIndex.cr ================================================ module Anyolite module Macro macro wrap_method_index(rb_interpreter, crystal_class, method_index, ruby_name, is_constructor = false, is_class_method = false, operator = "", cut_name = nil, without_keywords = false, added_keyword_args = nil, context = nil, return_nil = false, block_arg_number = nil, block_return_type = nil, store_block_arg = false, other_source = nil) {% method_source = other_source ? other_source : crystal_class %} {% if is_class_method %} {% method = method_source.resolve.class.methods[method_index] %} {% else %} {% method = method_source.resolve.methods[method_index] %} {% end %} {% if !operator.empty? %} {% if cut_name %} {% if is_class_method %} {% final_method_name = "#{crystal_class}.#{cut_name}".id %} {% final_operator = "#{crystal_class}.#{operator.id}" %} {% else %} {% final_method_name = cut_name %} {% final_operator = operator %} {% end %} {% else %} {% final_method_name = Anyolite::Empty %} {% if is_class_method %} {% final_operator = "#{crystal_class}.#{operator.id}" %} {% else %} {% final_operator = operator %} {% end %} {% end %} {% else %} {% if is_class_method %} {% final_method_name = "#{crystal_class}.#{method.name}".id %} {% else %} {% final_method_name = method.name %} {% end %} {% final_operator = operator %} {% end %} {% final_arg_array = added_keyword_args ? added_keyword_args : method.args %} # Routine to check all arguments for validity and potential self values {% try_again = false %} {% for arg, index in final_arg_array %} {% if arg.is_a?(Arg) %} {% if arg.restriction.is_a?(Self) %} {% try_again = true %} {% if arg.default_value %} {% final_arg_array[index] = "#{arg.name} : #{crystal_class} = #{arg.default_value}".id %} {% else %} {% final_arg_array[index] = "#{arg.name} : #{crystal_class}".id %} {% end %} {% end %} {% elsif arg.is_a?(TypeDeclaration) %} {% if arg.type.is_a?(Self) %} {% try_again = true %} {% if arg.value %} {% final_arg_array[index] = "#{arg.var} : #{crystal_class} = #{arg.value}".id %} {% else %} {% final_arg_array[index] = "#{arg.var} : #{crystal_class}".id %} {% end %} {% end %} {% else %} {% raise "Argument #{arg} in function #{final_method_name} of class #{crystal_class} is neither Arg nor TypeDeclaration." %} {% end %} {% end %} {% if try_again %} Anyolite::Macro.wrap_method_index({{rb_interpreter}}, {{crystal_class}}, {{method_index}}, {{ruby_name}}, is_constructor: {{is_constructor}}, is_class_method: {{is_class_method}}, operator: {{operator}}, cut_name: {{cut_name}}, without_keywords: {{without_keywords}}, added_keyword_args: {{final_arg_array}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}, other_source: {{other_source}}) {% else %} {% if final_arg_array.empty? %} {% if is_class_method %} Anyolite.wrap_class_method({{rb_interpreter}}, {{crystal_class}}, {{ruby_name}}, {{final_method_name}}, operator: {{final_operator}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% elsif is_constructor %} # Do not ever let a constructor return nil (for now) Anyolite.wrap_constructor({{rb_interpreter}}, {{crystal_class}}, context: {{context}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% else %} Anyolite.wrap_instance_method({{rb_interpreter}}, {{crystal_class}}, {{ruby_name}}, {{final_method_name}}, operator: {{final_operator}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% end %} # A complicated check, but it is more stable than simply checking for colons {% elsif !final_arg_array.find { |m| !((m.is_a?(TypeDeclaration) && m.type) || (m.is_a?(Arg) && m.restriction)) } %} {% if without_keywords %} {% if without_keywords >= final_arg_array.size %} {% regular_arg_partition = nil %} {% keyword_arg_partition = final_arg_array %} {% elsif without_keywords == -2 %} {% first_optional_arg = 0 %} {% final_arg_array.each { |arg| first_optional_arg += 1 unless (arg.is_a?(TypeDeclaration) && !arg.value.is_a?(Nop)) || (arg.is_a?(Arg) && !arg.default_value.is_a?(Nop)) } %} {% regular_arg_partition = final_arg_array[0..first_optional_arg - 1] %} {% keyword_arg_partition = final_arg_array[first_optional_arg..-1] %} {% regular_arg_partition = nil if first_optional_arg == 0 || regular_arg_partition.empty? %} {% keyword_arg_partition = nil if keyword_arg_partition.empty? %} {% elsif without_keywords < 0 %} {% regular_arg_partition = final_arg_array %} {% keyword_arg_partition = nil %} {% else %} {% regular_arg_partition = final_arg_array[0..without_keywords - 1] %} {% keyword_arg_partition = final_arg_array[without_keywords..-1] %} {% regular_arg_partition = nil if without_keywords == 0 || regular_arg_partition.empty? %} {% keyword_arg_partition = nil if keyword_arg_partition.empty? %} {% end %} {% if keyword_arg_partition %} {% if is_class_method %} Anyolite.wrap_class_method_with_keywords({{rb_interpreter}}, {{crystal_class}}, {{ruby_name}}, {{final_method_name}}, {{keyword_arg_partition}}, regular_args: {{regular_arg_partition}}, operator: {{final_operator}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% elsif is_constructor %} Anyolite.wrap_constructor_with_keywords({{rb_interpreter}}, {{crystal_class}}, {{keyword_arg_partition}}, regular_args: {{regular_arg_partition}}, operator: {{final_operator}}, context: {{context}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% else %} Anyolite.wrap_instance_method_with_keywords({{rb_interpreter}}, {{crystal_class}}, {{ruby_name}}, {{final_method_name}}, {{keyword_arg_partition}}, regular_args: {{regular_arg_partition}}, operator: {{final_operator}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% end %} {% else %} {% if is_class_method %} Anyolite.wrap_class_method({{rb_interpreter}}, {{crystal_class}}, {{ruby_name}}, {{final_method_name}}, {{regular_arg_partition}}, operator: {{final_operator}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% elsif is_constructor %} Anyolite.wrap_constructor({{rb_interpreter}}, {{crystal_class}}, {{regular_arg_partition}}, operator: {{final_operator}}, context: {{context}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% else %} Anyolite.wrap_instance_method({{rb_interpreter}}, {{crystal_class}}, {{ruby_name}}, {{final_method_name}}, {{regular_arg_partition}}, operator: {{final_operator}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% end %} {% end %} {% else %} {% if is_class_method %} Anyolite.wrap_class_method_with_keywords({{rb_interpreter}}, {{crystal_class}}, {{ruby_name}}, {{final_method_name}}, {{final_arg_array}}, operator: {{final_operator}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% elsif is_constructor %} Anyolite.wrap_constructor_with_keywords({{rb_interpreter}}, {{crystal_class}}, {{final_arg_array}}, operator: {{final_operator}}, context: {{context}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% else %} Anyolite.wrap_instance_method_with_keywords({{rb_interpreter}}, {{crystal_class}}, {{ruby_name}}, {{final_method_name}}, {{final_arg_array}}, operator: {{final_operator}}, context: {{context}}, return_nil: {{return_nil}}, block_arg_number: {{block_arg_number}}, block_return_type: {{block_return_type}}, store_block_arg: {{store_block_arg}}) {% end %} {% end %} {% elsif !is_class_method && operator == "==" && final_arg_array[0] && !final_arg_array[1] %} {% options = {:context => context} %} Anyolite::Macro.wrap_equality_function({{rb_interpreter}}, {{crystal_class}}, {{ruby_name}}, {{final_method_name}}, operator: {{final_operator}}, options: {{options}}) {% else %} {% if is_class_method %} {% puts "\e[33m> INFO: Could not wrap function '#{crystal_class}.#{method.name}' with args #{method.args}.\e[0m" %} {% else %} {% puts "\e[33m> INFO: Could not wrap function '#{method.name}' with args #{method.args}.\e[0m" %} {% end %} {% end %} {% end %} end end end ================================================ FILE: src/macros/Wrappers.cr ================================================ module Anyolite module Macro macro wrap_module_function_with_args(rb_interpreter, under_module, name, proc, regular_args = nil, operator = "", options = {} of Symbol => NoReturn) {% if regular_args.is_a?(ArrayLiteral) %} {% regular_arg_array = regular_args %} {% elsif regular_args == nil %} {% regular_arg_array = nil %} {% else %} {% regular_arg_array = [regular_args] %} {% end %} {% options[:type_vars] = under_module.resolve.type_vars %} {% options[:type_var_names_annotation] = under_module.resolve.annotation(Anyolite::SpecifyGenericTypes) %} {% options[:type_var_names] = options[:type_var_names_annotation] ? options[:type_var_names_annotation][0] : nil %} %wrapped_method = Anyolite::Macro.new_rb_func do # TODO: Put these kinds of commands into a new macro {% if options[:block_arg_number] || options[:store_block_arg] %} %block_ptr = Pointer(Anyolite::RbCore::RbValue).malloc(size: 1) {% if options[:store_block_arg] %} Anyolite::RbArgCache.push_block_cache(%block_ptr) {% end %} %args = Anyolite::Macro.generate_arg_tuple(_rb, {{regular_args}}, options: {{options}}) %format_string = Anyolite::Macro.format_string({{regular_args}}, options: {{options}}) + "&" Anyolite::Macro.load_args_into_vars({{regular_args}}, %format_string, %args, %block_ptr) {% if options[:block_arg_number] %} if Anyolite::RbCast.check_for_nil(%block_ptr.value) Anyolite.raise_argument_error("No block given.") Anyolite::RbCast.return_nil end {% end %} %converted_args = Anyolite::Macro.convert_regular_args(_rb, %args, {{regular_args}}, options: {{options}}) {% else %} %block_ptr = nil %converted_args = Anyolite::Macro.get_converted_args(_rb, {{regular_arg_array}}, options: {{options}}) {% end %} %return_value = Anyolite::Macro.call_and_return(_rb, {{proc}}, {{regular_arg_array}}, %converted_args, operator: {{operator}}, options: {{options}}, block_ptr: %block_ptr) {% if options[:store_block_arg] %} Anyolite::RbArgCache.pop_block_cache {% end %} %return_value end {{rb_interpreter}}.define_module_function({{name}}, Anyolite::RbClassCache.get({{under_module}}), %wrapped_method) end macro wrap_module_function_with_keyword_args(rb_interpreter, under_module, name, proc, keyword_args, regular_args = nil, operator = "", options = {} of Symbol => NoReturn) {% if regular_args.is_a?(ArrayLiteral) %} {% regular_arg_array = regular_args %} {% elsif regular_args == nil %} {% regular_arg_array = nil %} {% else %} {% regular_arg_array = [regular_args] %} {% end %} {% options[:type_vars] = under_module.resolve.type_vars %} {% options[:type_var_names_annotation] = under_module.resolve.annotation(Anyolite::SpecifyGenericTypes) %} {% options[:type_var_names] = options[:type_var_names_annotation] ? options[:type_var_names_annotation][0] : nil %} %wrapped_method = Anyolite::Macro.new_rb_func do %regular_arg_tuple = Anyolite::Macro.generate_arg_tuple(_rb, {{regular_arg_array}}, options: {{options}}) {% if options[:block_arg_number] || options[:store_block_arg] %} %block_ptr = Pointer(Anyolite::RbCore::RbValue).malloc(size: 1) {% if options[:store_block_arg] %} Anyolite::RbArgCache.push_block_cache(%block_ptr) {% end %} %format_string = Anyolite::Macro.format_string({{regular_arg_array}}, options: {{options}}) + ":&" %kw_args = Anyolite::Macro.load_kw_args_into_vars({{regular_arg_array}}, {{keyword_args}}, %format_string, %regular_arg_tuple, %block_ptr) {% if options[:block_arg_number] %} if Anyolite::RbCast.check_for_nil(%block_ptr.value) Anyolite.raise_argument_error("No block given.") Anyolite::RbCast.return_nil end {% end %} {% else %} %block_ptr = nil %format_string = Anyolite::Macro.format_string({{regular_arg_array}}, options: {{options}}) + ":" %kw_args = Anyolite::Macro.load_kw_args_into_vars({{regular_arg_array}}, {{keyword_args}}, %format_string, %regular_arg_tuple) {% end %} %converted_regular_args = Anyolite::Macro.convert_regular_args(_rb, %regular_arg_tuple, {{regular_arg_array}}, options: {{options}}) {% if !regular_arg_array || regular_arg_array.size == 0 %} {% options[:empty_regular] = true %} {% end %} %return_value = Anyolite::Macro.call_and_return_keyword_method(_rb, {{proc}}, %converted_regular_args, {{keyword_args}}, %kw_args, operator: {{operator}}, options: {{options}}, block_ptr: %block_ptr) {% if options[:store_block_arg] %} Anyolite::RbArgCache.pop_block_cache {% end %} %return_value end {{rb_interpreter}}.define_module_function({{name}}, Anyolite::RbClassCache.get({{under_module}}), %wrapped_method) end macro wrap_class_method_with_args(rb_interpreter, crystal_class, name, proc, regular_args = nil, operator = "", options = {} of Symbol => NoReturn) {% if regular_args.is_a?(ArrayLiteral) %} {% regular_arg_array = regular_args %} {% elsif regular_args == nil %} {% regular_arg_array = nil %} {% else %} {% regular_arg_array = [regular_args] %} {% end %} {% options[:type_vars] = crystal_class.resolve.type_vars %} {% options[:type_var_names_annotation] = crystal_class.resolve.annotation(Anyolite::SpecifyGenericTypes) %} {% options[:type_var_names] = options[:type_var_names_annotation] ? options[:type_var_names_annotation][0] : nil %} %wrapped_method = Anyolite::Macro.new_rb_func do {% if options[:block_arg_number] || options[:store_block_arg] %} %block_ptr = Pointer(Anyolite::RbCore::RbValue).malloc(size: 1) {% if options[:store_block_arg] %} Anyolite::RbArgCache.push_block_cache(%block_ptr) {% end %} %args = Anyolite::Macro.generate_arg_tuple(_rb, {{regular_args}}, options: {{options}}) %format_string = Anyolite::Macro.format_string({{regular_args}}, options: {{options}}) + "&" Anyolite::Macro.load_args_into_vars({{regular_args}}, %format_string, %args, %block_ptr) {% if options[:block_arg_number] %} if Anyolite::RbCast.check_for_nil(%block_ptr.value) Anyolite.raise_argument_error("No block given.") Anyolite::RbCast.return_nil end {% end %} %converted_args = Anyolite::Macro.convert_regular_args(_rb, %args, {{regular_args}}, options: {{options}}) {% else %} %block_ptr = nil %converted_args = Anyolite::Macro.get_converted_args(_rb, {{regular_arg_array}}, options: {{options}}) {% end %} %return_value = Anyolite::Macro.call_and_return(_rb, {{proc}}, {{regular_arg_array}}, %converted_args, operator: {{operator}}, options: {{options}}, block_ptr: %block_ptr) {% if options[:store_block_arg] %} Anyolite::RbArgCache.pop_block_cache {% end %} %return_value end {{rb_interpreter}}.define_class_method({{name}}, Anyolite::RbClassCache.get({{crystal_class}}), %wrapped_method) end macro wrap_class_method_with_keyword_args(rb_interpreter, crystal_class, name, proc, keyword_args, regular_args = nil, operator = "", options = {} of Symbol => NoReturn) {% if regular_args.is_a?(ArrayLiteral) %} {% regular_arg_array = regular_args %} {% elsif regular_args == nil %} {% regular_arg_array = nil %} {% else %} {% regular_arg_array = [regular_args] %} {% end %} {% options[:type_vars] = crystal_class.resolve.type_vars %} {% options[:type_var_names_annotation] = crystal_class.resolve.annotation(Anyolite::SpecifyGenericTypes) %} {% options[:type_var_names] = options[:type_var_names_annotation] ? options[:type_var_names_annotation][0] : nil %} %wrapped_method = Anyolite::Macro.new_rb_func do %regular_arg_tuple = Anyolite::Macro.generate_arg_tuple(_rb, {{regular_arg_array}}, options: {{options}}) {% if options[:block_arg_number] || options[:store_block_arg] %} %block_ptr = Pointer(Anyolite::RbCore::RbValue).malloc(size: 1) {% if options[:store_block_arg] %} Anyolite::RbArgCache.push_block_cache(%block_ptr) {% end %} %format_string = Anyolite::Macro.format_string({{regular_arg_array}}, options: {{options}}) + ":&" %kw_args = Anyolite::Macro.load_kw_args_into_vars({{regular_arg_array}}, {{keyword_args}}, %format_string, %regular_arg_tuple, %block_ptr) {% if options[:block_arg_number] %} if Anyolite::RbCast.check_for_nil(%block_ptr.value) Anyolite.raise_argument_error("No block given.") Anyolite::RbCast.return_nil end {% end %} {% else %} %block_ptr = nil %format_string = Anyolite::Macro.format_string({{regular_arg_array}}, options: {{options}}) + ":" %kw_args = Anyolite::Macro.load_kw_args_into_vars({{regular_arg_array}}, {{keyword_args}}, %format_string, %regular_arg_tuple) {% end %} %converted_regular_args = Anyolite::Macro.convert_regular_args(_rb, %regular_arg_tuple, {{regular_arg_array}}, options: {{options}}) {% if !regular_arg_array || regular_arg_array.size == 0 %} {% options[:empty_regular] = true %} {% end %} %return_value = Anyolite::Macro.call_and_return_keyword_method(_rb, {{proc}}, %converted_regular_args, {{keyword_args}}, %kw_args, operator: {{operator}}, options: {{options}}, block_ptr: %block_ptr) {% if options[:store_block_arg] %} Anyolite::RbArgCache.pop_block_cache {% end %} %return_value end {{rb_interpreter}}.define_class_method({{name}}, Anyolite::RbClassCache.get({{crystal_class}}), %wrapped_method) end macro wrap_instance_function_with_args(rb_interpreter, crystal_class, name, proc, regular_args = nil, operator = "", options = {} of Symbol => NoReturn) {% if regular_args.is_a?(ArrayLiteral) %} {% regular_arg_array = regular_args %} {% elsif regular_args == nil %} {% regular_arg_array = nil %} {% else %} {% regular_arg_array = [regular_args] %} {% end %} {% options[:type_vars] = crystal_class.resolve.type_vars %} {% options[:type_var_names_annotation] = crystal_class.resolve.annotation(Anyolite::SpecifyGenericTypes) %} {% options[:type_var_names] = options[:type_var_names_annotation] ? options[:type_var_names_annotation][0] : nil %} %wrapped_method = Anyolite::Macro.new_rb_func do {% if options[:block_arg_number] || options[:store_block_arg] %} %block_ptr = Pointer(Anyolite::RbCore::RbValue).malloc(size: 1) {% if options[:store_block_arg] %} Anyolite::RbArgCache.push_block_cache(%block_ptr) {% end %} %args = Anyolite::Macro.generate_arg_tuple(_rb, {{regular_args}}, options: {{options}}) %format_string = Anyolite::Macro.format_string({{regular_args}}, options: {{options}}) + "&" Anyolite::Macro.load_args_into_vars({{regular_args}}, %format_string, %args, %block_ptr) {% if options[:block_arg_number] %} if Anyolite::RbCast.check_for_nil(%block_ptr.value) Anyolite.raise_argument_error("No block given.") Anyolite::RbCast.return_nil end {% end %} %converted_args = Anyolite::Macro.convert_regular_args(_rb, %args, {{regular_args}}, options: {{options}}) {% else %} %block_ptr = nil %converted_args = Anyolite::Macro.get_converted_args(_rb, {{regular_arg_array}}, options: {{options}}) {% end %} if {{crystal_class}} <= Struct || {{crystal_class}} <= Enum %converted_obj = Anyolite::Macro.convert_from_ruby_struct(_rb, _obj, {{crystal_class}}).value else %converted_obj = Anyolite::Macro.convert_from_ruby_object(_rb, _obj, {{crystal_class}}).value end %return_value = Anyolite::Macro.call_and_return_instance_method(_rb, {{proc}}, %converted_obj, %converted_args, operator: {{operator}}, options: {{options}}, block_ptr: %block_ptr) {% if options[:store_block_arg] %} Anyolite::RbArgCache.pop_block_cache {% end %} %return_value end {{rb_interpreter}}.define_method({{name}}, Anyolite::RbClassCache.get({{crystal_class}}), %wrapped_method) end macro wrap_instance_function_with_keyword_args(rb_interpreter, crystal_class, name, proc, keyword_args, regular_args = nil, operator = "", options = {} of Symbol => NoReturn) {% if regular_args.is_a?(ArrayLiteral) %} {% regular_arg_array = regular_args %} {% elsif regular_args == nil %} {% regular_arg_array = nil %} {% else %} {% regular_arg_array = [regular_args] %} {% end %} {% options[:type_vars] = crystal_class.resolve.type_vars %} {% options[:type_var_names_annotation] = crystal_class.resolve.annotation(Anyolite::SpecifyGenericTypes) %} {% options[:type_var_names] = options[:type_var_names_annotation] ? options[:type_var_names_annotation][0] : nil %} %wrapped_method = Anyolite::Macro.new_rb_func do %regular_arg_tuple = Anyolite::Macro.generate_arg_tuple(_rb, {{regular_arg_array}}, options: {{options}}) # TODO: Add annotation argument for required blocks ('&!' then) {% if options[:block_arg_number] || options[:store_block_arg] %} %block_ptr = Pointer(Anyolite::RbCore::RbValue).malloc(size: 1) {% if options[:store_block_arg] %} Anyolite::RbArgCache.push_block_cache(%block_ptr) {% end %} %format_string = Anyolite::Macro.format_string({{regular_arg_array}}, options: {{options}}) + ":&" %kw_args = Anyolite::Macro.load_kw_args_into_vars({{regular_arg_array}}, {{keyword_args}}, %format_string, %regular_arg_tuple, %block_ptr) {% if options[:block_arg_number] %} if Anyolite::RbCast.check_for_nil(%block_ptr.value) Anyolite.raise_argument_error("No block given.") Anyolite::RbCast.return_nil end {% end %} {% else %} %block_ptr = nil %format_string = Anyolite::Macro.format_string({{regular_arg_array}}, options: {{options}}) + ":" %kw_args = Anyolite::Macro.load_kw_args_into_vars({{regular_arg_array}}, {{keyword_args}}, %format_string, %regular_arg_tuple) {% end %} %converted_regular_args = Anyolite::Macro.convert_regular_args(_rb, %regular_arg_tuple, {{regular_arg_array}}, options: {{options}}) if {{crystal_class}} <= Struct || {{crystal_class}} <= Enum %converted_obj = Anyolite::Macro.convert_from_ruby_struct(_rb, _obj, {{crystal_class}}).value else %converted_obj = Anyolite::Macro.convert_from_ruby_object(_rb, _obj, {{crystal_class}}).value end {% if !regular_arg_array || regular_arg_array.size == 0 %} {% options[:empty_regular] = true %} {% end %} %return_value = Anyolite::Macro.call_and_return_keyword_instance_method(_rb, {{proc}}, %converted_obj, %converted_regular_args, {{keyword_args}}, %kw_args, operator: {{operator}}, options: {{options}}, block_ptr: %block_ptr) {% if options[:store_block_arg] %} Anyolite::RbArgCache.pop_block_cache {% end %} %return_value end {{rb_interpreter}}.define_method({{name}}, Anyolite::RbClassCache.get({{crystal_class}}), %wrapped_method) end macro wrap_constructor_function_with_args(rb_interpreter, crystal_class, proc, regular_args = nil, operator = "", options = {} of Symbol => NoReturn) {% if regular_args.is_a?(ArrayLiteral) %} {% regular_arg_array = regular_args %} {% elsif regular_args == nil %} {% regular_arg_array = nil %} {% else %} {% regular_arg_array = [regular_args] %} {% end %} {% if !options[:block_arg_number] %} {% proc_arg_string = "" %} {% elsif options[:block_arg_number] == 0 %} {% proc_arg_string = "do" %} {% else %} {% proc_arg_string = "do |" + (0..options[:block_arg_number] - 1).map { |x| "block_arg_#{x}" }.join(", ") + "|" %} {% end %} {% options[:type_vars] = crystal_class.resolve.type_vars %} {% options[:type_var_names_annotation] = crystal_class.resolve.annotation(Anyolite::SpecifyGenericTypes) %} {% options[:type_var_names] = options[:type_var_names_annotation] ? options[:type_var_names_annotation][0] : nil %} %wrapped_method = Anyolite::Macro.new_rb_func do {% if options[:block_arg_number] || options[:store_block_arg] %} %block_ptr = Pointer(Anyolite::RbCore::RbValue).malloc(size: 1) {% if options[:store_block_arg] %} Anyolite::RbArgCache.push_block_cache(%block_ptr) {% end %} %args = Anyolite::Macro.generate_arg_tuple(_rb, {{regular_args}}, options: {{options}}) %format_string = Anyolite::Macro.format_string({{regular_args}}, options: {{options}}) + "&" Anyolite::Macro.load_args_into_vars({{regular_args}}, %format_string, %args, %block_ptr) {% if options[:block_arg_number] %} if Anyolite::RbCast.check_for_nil(%block_ptr.value) Anyolite.raise_argument_error("No block given.") Anyolite::RbCast.return_nil end {% end %} %converted_args = Anyolite::Macro.convert_regular_args(_rb, %args, {{regular_args}}, options: {{options}}) {% else %} %block_ptr = nil %converted_args = Anyolite::Macro.get_converted_args(_rb, {{regular_arg_array}}, options: {{options}}) {% end %} %new_obj = {{proc}}{{operator.id}}(*%converted_args) {{proc_arg_string.id}} {% if options[:block_arg_number] == 0 %} %yield_value = Anyolite::RbCore.rb_yield(_rb, %block_ptr.value, Anyolite::RbCast.return_nil) Anyolite::Macro.convert_from_ruby_to_crystal(_rb, %yield_value, options: {{options}}) end {% elsif options[:block_arg_number] %} %block_arg_array = [ {% for i in 0..options[:block_arg_number] - 1 %} Anyolite::RbCast.return_value(_rb, {{"block_arg_#{i}".id}}), {% end %} ] %yield_value = Anyolite::RbCore.rb_yield_argv(_rb, %block_ptr.value, {{options[:block_arg_number]}}, %block_arg_array) Anyolite::Macro.convert_from_ruby_to_crystal(_rb, %yield_value, options: {{options}}) end {% end %} Anyolite::Macro.allocate_constructed_object(_rb, {{crystal_class}}, _obj, %new_obj) {% if options[:store_block_arg] %} Anyolite::RbArgCache.pop_block_cache {% end %} _obj end {{rb_interpreter}}.define_method("initialize", Anyolite::RbClassCache.get({{crystal_class}}), %wrapped_method) end macro wrap_constructor_function_with_keyword_args(rb_interpreter, crystal_class, proc, keyword_args, regular_args = nil, operator = "", options = {} of Symbol => NoReturn) {% if regular_args.is_a?(ArrayLiteral) %} {% regular_arg_array = regular_args %} {% elsif regular_args == nil %} {% regular_arg_array = nil %} {% else %} {% regular_arg_array = [regular_args] %} {% end %} {% if !options[:block_arg_number] %} {% proc_arg_string = "" %} {% elsif options[:block_arg_number] == 0 %} {% proc_arg_string = "do" %} {% else %} {% proc_arg_string = "do |" + (0..options[:block_arg_number] - 1).map { |x| "block_arg_#{x}" }.join(", ") + "|" %} {% end %} {% options[:type_vars] = crystal_class.resolve.type_vars %} {% options[:type_var_names_annotation] = crystal_class.resolve.annotation(Anyolite::SpecifyGenericTypes) %} {% options[:type_var_names] = options[:type_var_names_annotation] ? options[:type_var_names_annotation][0] : nil %} %wrapped_method = Anyolite::Macro.new_rb_func do %regular_arg_tuple = Anyolite::Macro.generate_arg_tuple(_rb, {{regular_arg_array}}, options: {{options}}) {% if options[:block_arg_number] || options[:store_block_arg] %} %block_ptr = Pointer(Anyolite::RbCore::RbValue).malloc(size: 1) {% if options[:store_block_arg] %} Anyolite::RbArgCache.push_block_cache(%block_ptr) {% end %} %format_string = Anyolite::Macro.format_string({{regular_arg_array}}, options: {{options}}) + ":&" %kw_args = Anyolite::Macro.load_kw_args_into_vars({{regular_arg_array}}, {{keyword_args}}, %format_string, %regular_arg_tuple, %block_ptr) {% if options[:block_arg_number] %} if Anyolite::RbCast.check_for_nil(%block_ptr.value) Anyolite.raise_argument_error("No block given.") Anyolite::RbCast.return_nil end {% end %} {% else %} %block_ptr = nil %format_string = Anyolite::Macro.format_string({{regular_arg_array}}, options: {{options}}) + ":" %kw_args = Anyolite::Macro.load_kw_args_into_vars({{regular_arg_array}}, {{keyword_args}}, %format_string, %regular_arg_tuple) {% end %} %converted_regular_args = Anyolite::Macro.convert_regular_args(_rb, %regular_arg_tuple, {{regular_arg_array}}, options: {{options}}) {% if !regular_arg_array || regular_arg_array.size == 0 %} %new_obj = {{proc}}{{operator.id}}( {% c = 0 %} {% for keyword in keyword_args %} {{keyword.var.id}}: Anyolite::Macro.convert_from_ruby_to_crystal(_rb, %kw_args.values[{{c}}], {{keyword}}, options: {{options}}, debug_information: {{proc.stringify + " - " + keyword_args.stringify}}), {% c += 1 %} {% end %} ) {{proc_arg_string.id}} {% else %} %new_obj = {{proc}}{{operator.id}}(*%converted_regular_args, {% c = 0 %} {% for keyword in keyword_args %} {{keyword.var.id}}: Anyolite::Macro.convert_from_ruby_to_crystal(_rb, %kw_args.values[{{c}}], {{keyword}}, options: {{options}}, debug_information: {{proc.stringify + " - " + keyword_args.stringify}}), {% c += 1 %} {% end %} ) {{proc_arg_string.id}} {% end %} {% if options[:block_arg_number] == 0 %} %yield_value = Anyolite::RbCore.rb_yield(_rb, %block_ptr.value, Anyolite::RbCast.return_nil) Anyolite::Macro.convert_from_ruby_to_crystal(_rb, %yield_value, options: {{options}}) end {% elsif options[:block_arg_number] %} %block_arg_array = [ {% for i in 0..options[:block_arg_number] - 1 %} Anyolite::RbCast.return_value(_rb, {{"block_arg_#{i}".id}}), {% end %} ] %yield_value = Anyolite::RbCore.rb_yield_argv(_rb, %block_ptr.value, {{options[:block_arg_number]}}, %block_arg_array) Anyolite::Macro.convert_from_ruby_to_crystal(_rb, %yield_value, options: {{options}}) end {% end %} Anyolite::Macro.allocate_constructed_object(_rb, {{crystal_class}}, _obj, %new_obj) {% if options[:store_block_arg] %} Anyolite::RbArgCache.pop_block_cache {% end %} _obj end {{rb_interpreter}}.define_method("initialize", Anyolite::RbClassCache.get({{crystal_class}}), %wrapped_method) end macro wrap_equality_function(rb_interpreter, crystal_class, name, proc, operator = "", options = {} of Symbol => NoReturn) {% options[:type_vars] = crystal_class.resolve.type_vars %} {% options[:type_var_names_annotation] = crystal_class.resolve.annotation(Anyolite::SpecifyGenericTypes) %} {% options[:type_var_names] = options[:type_var_names_annotation] ? options[:type_var_names_annotation][0] : nil %} %wrapped_method = Anyolite::Macro.new_rb_func do %args = Anyolite::Macro.generate_arg_tuple(_rb, [other : {{crystal_class}}], options: {{options}}) %format_string = Anyolite::Macro.format_string([other : {{crystal_class}}], options: {{options}}) Anyolite::Macro.load_args_into_vars([other : {{crystal_class}}], %format_string, %args) if !Anyolite::RbCast.check_custom_type(_rb, %args[0].value, {{crystal_class}}) Anyolite::RbCast.return_false else %converted_args = Anyolite::Macro.convert_regular_args(_rb, %args, [other : {{crystal_class}}], options: {{options}}) if {{crystal_class}} <= Struct || {{crystal_class}} <= Enum %converted_obj = Anyolite::Macro.convert_from_ruby_struct(_rb, _obj, {{crystal_class}}).value else %converted_obj = Anyolite::Macro.convert_from_ruby_object(_rb, _obj, {{crystal_class}}).value end %return_value = Anyolite::Macro.call_and_return_instance_method(_rb, {{proc}}, %converted_obj, %converted_args, operator: {{operator}}, options: {{options}}) %return_value end end {{rb_interpreter}}.define_method({{name}}, Anyolite::RbClassCache.get({{crystal_class}}), %wrapped_method) end macro wrap_constant_or_class(rb_interpreter, under_class_or_module, ruby_name, value, overwrite = false, verbose = false) {% actual_constant = under_class_or_module.resolve.constant(value.id) %} {% if actual_constant.is_a?(TypeNode) %} {% if actual_constant.module? %} Anyolite.wrap_module_with_methods({{rb_interpreter}}, {{actual_constant}}, under: {{under_class_or_module}}, overwrite: {{overwrite}}, verbose: {{verbose}}) {% elsif actual_constant.class? || actual_constant.struct? %} Anyolite.wrap_class_with_methods({{rb_interpreter}}, {{actual_constant}}, under: {{under_class_or_module}}, wrap_equality_method: {{actual_constant.struct?}}, overwrite: {{overwrite}}, verbose: {{verbose}}) {% elsif actual_constant.union? %} {% puts "\e[31m> WARNING: Wrapping of unions not supported, thus skipping #{actual_constant}\e[0m" %} {% elsif actual_constant < Enum %} Anyolite.wrap_class_with_methods({{rb_interpreter}}, {{actual_constant}}, under: {{under_class_or_module}}, use_enum_methods: true, wrap_equality_method: true, overwrite: {{overwrite}}, verbose: {{verbose}}) {% else %} # Could be an alias, just try the default case Anyolite.wrap_class_with_methods({{rb_interpreter}}, {{actual_constant}}, under: {{under_class_or_module}}, overwrite: {{overwrite}}, verbose: {{verbose}}) {% end %} {% else %} Anyolite.wrap_constant_under_class({{rb_interpreter}}, {{under_class_or_module}}, {{ruby_name}}, {{under_class_or_module}}::{{value}}) {% end %} end end end ================================================ FILE: test.cr ================================================ require "./anyolite.cr" # TODO: Pass flags to temporary executable {% unless flag?(:anyolite_implementation_ruby_3) %} Anyolite::Preloader::AtCompiletime.transform_script_to_bytecode("examples/bytecode_test.rb", "examples/bytecode_test.mrb") Anyolite::Preloader::AtCompiletime.load_bytecode_file("examples/bytecode_test.mrb") {% end %} module SomeModule class_property some_class_property : Int32 = 258 def self.test_method(int : Int32, str : String) [str, int] end @[Anyolite::RenameClass("TestStructRenamed")] struct TestStruct property value : Int32 = -123 property test : Test = Test.new(-234) def rb_initialize(rb) puts "Struct initialized!" end end @[Anyolite::SpecializeInstanceMethod(output_this_and_struct, [str : TestStruct])] @[Anyolite::RenameInstanceMethod(output_this_and_struct, "output_together_with")] @[Anyolite::ExcludeInstanceMethod(do_not_wrap_this_either)] @[Anyolite::ExcludeConstant(CONSTANT_NOT_TO_WRAP)] @[Anyolite::RenameConstant(CONSTANT, RUBY_CONSTANT)] @[Anyolite::SpecializeInstanceMethod(method_without_keywords, [arg], [arg : String])] @[Anyolite::SpecializeInstanceMethod(method_with_various_args, nil)] @[Anyolite::SpecializeInstanceMethod(inspect, nil)] class Test struct ValueStruct property i : Int32 = 1234 property f : Float32 = 0.1234 property s : String = "Empty" @[Anyolite::WrapWithoutKeywords] def initialize(new_i : Int = 5678, new_f : Float = 0.5678, new_s : String = "Default") @i = new_i.to_i32 @f = new_f.to_f32 @s = new_s end end @[Anyolite::RenameClass("UnderTestRenamed")] class UnderTest module DeepUnderTest def self.-(value : Int) "Well, you can't just subtract #{value} from a module..." end class VeryDeepUnderTest def nested_test "This is a nested test" end end end end enum TestEnum : UInt64 Three = 3 Four Five Seven = 7 end struct DeepTestStruct def to_s "DeepTestStruct" end end @[Anyolite::SpecifyGenericTypes([U, V])] struct GenericTest(U, V) property u : U property v : V def initialize(u : U, v : V) @u = u @v = v end def test(u1 : U, v1 : V) "u1 = #{u1} of #{U}, v1 = #{v1} of #{V}." end def self.self_test(other : self) "Value is #{other.u} and #{other.v}" end @[Anyolite::WrapWithoutKeywords] def +(other : GenericTest(U, V)) GenericTest(U, V).new(u: @u + other.u, v: @v + other.v) end def compare(other : GenericTest(U, V)) "This has #{@u} and #{@v}, the other has #{other.u} and #{other.v}." end end alias GTIntFloat = GenericTest(Int32, Float32) alias GTIntInt = GenericTest(Int32, Int32) property x : Int32 = 0 @@counter : Int32 = 0 @@magic_block_store : Anyolite::RbRef | Nil = nil CONSTANT = "Hello" CONSTANT_NOT_TO_WRAP = 123 def self.increase_counter @@counter += 1 end def self.reset_counter @@counter = 0 end def self.-(other_value : Int) @@counter - other_value end def self.counter return @@counter end def self.give_me_a_struct s = TestStruct.new s.value = 777 s.test = Test.new(999) s end @[Anyolite::WrapWithoutKeywords] def self.without_keywords(int : Int32) int * 10 end @[Anyolite::Rename("test")] def test_instance_method(int : Int32, bool : Bool, str : String, float : Float32 = 0.4f32) @x += int return [int, bool, str, float, @x] end def inspect(io : IO) io.puts "x is #{@x}" end def inspect "x is #{@x}" end def give_some_regex /([\S]+) [\S]+/ end def check_some_regex(r : Regex, str : String) match_data = r.match(str) if match_data match_data[1]? else nil end end @[Anyolite::StoreBlockArg] def pass_a_ruby_block_to_another_method block_arg = Anyolite.obtain_given_rb_block Anyolite.call_rb_method("block_test", block: block_arg, cast_to: String).to_i end # Would all trigger an error! # def proc_test(pr : Int32 | (Int32 -> Int32)) # pr.call(12) # end # def proc_test_2(pr : Proc(Int32)) # pr.call(12) # end # def slice_test(s : Slice(Int32)) # end # Gets called in Crystal and Ruby def initialize(@x : Int32 = 0) Test.increase_counter puts "Test object initialized with value #{@x}" end # Gets called in Ruby def rb_initialize(rb) puts "Object registered in Ruby" end def ==(other) (self.x == other.x) end # Gets called in Crystal unless program terminates early def finalize puts "Finalized with value #{@x}" end def +(other : Test) Test.new(@x + other.x) end @[Anyolite::Exclude] def do_not_wrap_this end def do_not_wrap_this_either end @[Anyolite::Exclude] def self.do_not_wrap_this_class_method end def add(other : Test) ret = self + other end def uint_test(arg : UInt8) arg.to_s end @[Anyolite::ReturnNil] def noreturn_test puts "This will still be executed." [1] end def overload_test(arg : Int32 | String | Bool | Nil | Float32 | Test | TestEnum | GenericTest(Int32, Int32) = "Default String") if arg.is_a?(Test) "A test object with x = #{arg.x}" elsif arg.is_a?(GenericTest(Int32, Int32)) "A generic test" else "#{arg.inspect}" end end # TODO: This method does not work in MRI for some encoding reason - fix this if possible # Also emojis in method names seem not to work in recent Crystal releases :( {% unless flag?(:anyolite_implementation_ruby_3) %} @[Anyolite::Rename("happy😀emoji😀test😀😀😀")] def happy_emoji_test(arg : Int32) "😀 for number #{arg}" end {% end %} def inside_mri? Anyolite.implementation == :mri end def nilable_test(arg : Int32?) "Received argument #{arg.inspect}" end @[Anyolite::Specialize([arg1 : Int32, arg2 : Float32, arg_req : Float32, arg_opt_1 : String | Test | Bool | TestEnum | GenericTest(Int32, Int32) = "Cookies", arg_opt_2 : Int32 = 32])] @[Anyolite::WrapWithoutKeywords(4)] def complicated_method(arg1, arg2, arg_req : Float32, arg_opt_1 : String | Test | Bool | TestEnum | GenericTest(Int32, Int32) = "Cookies", arg_opt_2 : Int32 = 32) "#{arg1} - #{arg2} - #{arg_req} - #{arg_opt_1.is_a?(Test) ? arg_opt_1.x : arg_opt_1} - #{arg_opt_2}" end def returns_an_enum TestEnum::Five end def returns_something_random if rand < 0.5 3 else "Hello" end end def method_without_keywords(arg) puts "Argument is #{arg}" end def output_this_and_struct(str : TestStruct) "#{@x} #{str.value} #{str.test.x}" end def output_this_and_struct(i : Int32) raise "This should not be wrapped" end @[Anyolite::Specialize([strvar : String, intvar : Int32, floatvar : Float64 = 0.123, strvarkw : String = "nothing", boolvar : Bool = true, othervar : Test = SomeModule::Test.new(17)])] def keyword_test(strvar : String, intvar : Int32, floatvar : Float64 = 0.123, strvarkw : String = "nothing", boolvar : Bool = true, othervar : Test = Test.new(17)) "str = #{strvar}, int = #{intvar}, float = #{floatvar.round(3)}, stringkw = #{strvarkw}, bool = #{boolvar}, other.x = #{othervar.x}" end def keyword_test(whatever) raise "This should not be wrapped" end def array_test(arg : Array(String | Int32) | String) if arg.is_a?(String) [arg] else arg.map { |element| element * 2 } end end def hash_test(arg : Hash(String | Int32, String | SomeModule::Test | SomeModule::Test::UnderTest | SomeModule::Test::TestEnum)) arg.each do |key, value| puts "Crystal: #{key} -> #{value.is_a?(Test) ? "Test with x = #{value.x}" : value}" end arg end @[Anyolite::StoreBlockArg] def block_test block_cache = Anyolite.obtain_given_rb_block ret_value = Anyolite.call_rb_block(block_cache, [self], cast_to: Int32) ret_value.to_s end @[Anyolite::AddBlockArg(2, String | Int32)] def block_test_2 return_value = yield 1, 2 return_value.to_s end @[Anyolite::AddBlockArg(2, String)] def self.block_test_3(arg : String) return_value = yield "Hello", "There" arg.to_s + ": " + return_value.to_s end @[Anyolite::StoreBlockArg] def block_store_test if block_cache = Anyolite.obtain_given_rb_block @@magic_block_store = block_cache true else false end end def block_store_call if mbs = @@magic_block_store ret_value = Anyolite.call_rb_block(mbs, [self], cast_to: Int32) ret_value.to_s else false end end def hash_return_test {:hello => "Nice", :world => "to see you!", 3 => 15, "test😊" => :very_long_test_symbol} end private def private_method end def method_with_various_args(int_arg : Int) "Some args" end def method_with_various_args "No args" end @[Anyolite::Specialize([arg : Int | String])] @[Anyolite::WrapWithoutKeywords] def overload_cheat_test(arg : Int) "This was an int" end @[Anyolite::Exclude] def overload_cheat_test(arg : String) "This was a string" end def float_test(arg : Float) arg end def char_test(arg : Char) arg end def bool_setter_test?(arg : Bool = true) "#{arg}" end @[Anyolite::ForceKeywordArg] def keyword_operator_arg?(arg : Float) "#{arg + 1.0}" end def am_i_in_ruby? Anyolite.referenced_in_ruby?(self) end @[Anyolite::WrapWithoutKeywords] def response_test(name : String) Anyolite.does_obj_respond_to(self, name) end @[Anyolite::WrapWithoutKeywords] def class_response_test(name : String) Anyolite.does_class_respond_to(self.class, name) end # This annotation will prevent the method from its global exclusion @[Anyolite::Include] def call_test result = Anyolite.call_rb_method(:method_only_in_ruby, ["Hello", 3], cast_to: String) result end def class_call_test Anyolite.call_rb_method_of_class(self.class, :class_method_in_ruby, ["World", 4], cast_to: String) end @[Anyolite::WrapWithoutKeywords] def why_would_you_do_this?(name : String) result = Anyolite.call_rb_method(name, nil, cast_to: String | Int32 | Float32 | Bool | Nil) result end def set_instance_variable_to_int(name : String, value : Int) Anyolite.set_iv(self, name, value) end def get_instance_variable(name : String) Anyolite.get_iv(self, name, cast_to: Int?) end def ref_test(str : String, ref : Anyolite::RbRef) converted_arg = Anyolite.cast_to_crystal(ref, Int32?) puts "Reference: #{ref.value}" "#{str} and a reference with #{converted_arg} were given." end def hash 213345 end @[Anyolite::WrapWithoutKeywords] def num_test(n : Number) n end def ptr_return_test pointerof(@x) end def ptr_arg_test(arg : Pointer(Int32)) arg.value += 1 arg.value end def ptr_star_arg_test(arg : Int32*) arg.value += 3 arg.value end def test_int_or_ptr(arg : Int32 | Int32*) if arg.is_a?(Int32) arg else arg.value end end @[Anyolite::ExcludeInstanceMethod("dup")] class TestChild < Test property y : String def initialize(x : Int32 = 0) super(x: x) @y = x.to_s end end class ContentTest def initialize(content : Array(SomeModule::Test)) @content = content end def content @content end end class NewContentTest < ContentTest def initialize(content : Array(SomeModule::Test), more_content : Array(SomeModule::Test)) super(content: content) @more_content = more_content end def more_content @more_content end end # Gets called in Ruby unless program crashes def rb_finalize(rb) puts "Ruby destructor called for value #{@x}" end end class SubTest < SomeModule::Test end class Bla def initialize end end end # For testing purposes, let's exclude this method globally (and include it again locally) @[Anyolite::ExcludeInstanceMethod("call_test")] class Object end 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 macro load_test_module Anyolite.wrap_module(rb, SomeModule, "TestModule") Anyolite.wrap_module_function_with_keywords(rb, SomeModule, "test_method", SomeModule.test_method, [int : Int32 = 19, str : String]) Anyolite.wrap_class_property(rb, SomeModule, "some_class_property", SomeModule.some_class_property, Int32) Anyolite.wrap_constant(rb, SomeModule, "SOME_CONSTANT", "Smile! 😊") Anyolite.wrap(rb, SomeModule::Bla, under: SomeModule, verbose: true) Anyolite.wrap(rb, SomeModule::TestStruct, under: SomeModule, verbose: true) Anyolite.wrap(rb, SomeModule::Test, under: SomeModule, instance_method_exclusions: [:add], verbose: true) Anyolite.wrap_instance_method(rb, SomeModule::Test, "add", add, [SomeModule::Test]) end {% unless flag?(:anyolite_implementation_ruby_3) %} Anyolite::RbInterpreter.create do |rb| Anyolite.wrap(rb, RPGTest) rb.load_script_from_file("examples/hp_example.rb") end puts "------------------------------" Anyolite::RbInterpreter.create do |rb| Anyolite::Preloader.execute_bytecode_from_cache_or_file(rb, "examples/bytecode_test.mrb") load_test_module() rb.load_script_from_file("examples/test_framework.rb") rb.load_script_from_file("examples/test.rb") end {% else %} Anyolite::RbInterpreter.create do |rb| load_test_module() Anyolite.wrap(rb, RPGTest) Anyolite.eval("puts TestModule::Test.new(x: 12345).inspect") Anyolite.eval("require_relative './examples/bytecode_test.rb'") rb.load_script_from_file("examples/mri_test.rb") Anyolite.eval("puts TestModule::Test.new(x: 67890).inspect") end {% end %} ================================================ FILE: utility/mruby_build_config.rb ================================================ MRuby::Build.new do |conf| if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR'] toolchain :visualcpp # NOTE: If you want to use dynamic linking, change /MT to /MD instead conf.cc.flags = ["/nologo", "/W3", "/MT", "/O2", "/D_CRT_SECURE_NO_WARNINGS"] else toolchain :gcc end conf.gembox 'default' conf.gem :mgem => 'json' conf.cc.flags << '-DMRB_UTF8_STRING -DMRB_INT64' conf.build_dir = ENV["MRUBY_BUILD_DIR"] || raise("MRUBY_BUILD_DIR undefined!") end