From f66ed6492b1fa4a0be584acfb95b7da7347e0e1d Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 25 Jun 2025 13:55:30 -0500 Subject: [PATCH 1/3] Initial Ruby stubs for FFI version --- lib/rbs.rb | 7 +++++- lib/rbs/ffi/location.rb | 49 +++++++++++++++++++++++++++++++++++++++++ lib/rbs/ffi/parser.rb | 29 ++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 lib/rbs/ffi/location.rb create mode 100644 lib/rbs/ffi/parser.rb diff --git a/lib/rbs.rb b/lib/rbs.rb index 197d5f703..d2b04d366 100644 --- a/lib/rbs.rb +++ b/lib/rbs.rb @@ -69,7 +69,12 @@ require "rbs/type_alias_regularity" require "rbs/collection" -require "rbs_extension" +begin + require "rbs_extension" +rescue LoadError + require "rbs/ffi/parser" + require "rbs/ffi/location" +end require "rbs/parser_aux" require "rbs/location_aux" diff --git a/lib/rbs/ffi/location.rb b/lib/rbs/ffi/location.rb new file mode 100644 index 000000000..ca4686553 --- /dev/null +++ b/lib/rbs/ffi/location.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module RBS + class Location + def initialize(buffer, start_pos, end_pos) + + end + + def initialize_copy(other) + + end + + def buffer + + end + + def _start_pos + + end + + def _end_pos + + end + + def _add_required_child(name, start, end_pos) + + end + + def _add_optional_child(name, start, end_pos) + + end + + def _add_optional_no_child(name) + + end + + def _optional_keys + + end + + def _required_keys + + end + + def [](name) + + end + end +end \ No newline at end of file diff --git a/lib/rbs/ffi/parser.rb b/lib/rbs/ffi/parser.rb new file mode 100644 index 000000000..c143ba855 --- /dev/null +++ b/lib/rbs/ffi/parser.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module RBS + class Parser + def _parse_type(buffer, start_pos, end_pos, variables, require_eof) + + end + + def _parse_method_type(buffer, start_pos, end_pos, variables, require_eof) + + end + + def _parse_signature(buffer, start_pos, end_pos) + + end + + def _parse_type_params(buffer, start_pos, end_pos, module_type_params) + + end + + def _parse_inline_leading_annotation(buffer, start_pos, end_pos, variables) + + end + + def _parse_inline_trailing_annotation(buffer, start_pos, end_pos, variables) + + end + end +end From 75e18a1003446f2da11af67ed2c9cc6440cc445e Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Mon, 26 Jan 2026 16:29:43 -0600 Subject: [PATCH 2/3] Guard a few more gems that are C-ext specific --- Gemfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 79b7ff9a6..4ae4ee59f 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,7 @@ gem "test-unit" gem "rspec" gem "rubocop" gem "rubocop-rubycw" -gem "rubocop-on-rbs" +gem "rubocop-on-rbs", platform: :ruby # zlib transitive dep gem "json" gem "json-schema" gem "goodcheck" @@ -28,15 +28,15 @@ group :libs do gem "abbrev" gem "base64" gem "bigdecimal" - gem "dbm" + gem "dbm", platform: :ruby gem "mutex_m" gem "nkf" end group :profilers do # Performance profiling and benchmarking - gem 'stackprof' - gem 'memory_profiler' + gem 'stackprof', platform: :ruby + gem 'memory_profiler', platform: :ruby gem 'benchmark-ips' gem "ruby_memcheck", platform: :ruby end From 5e0334c15ab80e4e39ccece2c37735581b51de43 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Tue, 27 Jan 2026 00:14:27 -0600 Subject: [PATCH 3/3] Build dynamic lib and start binding FFI This adds the following: * A very basic Makefile to build a dynamic library from the RBS sources. * Exports for a few of the RBS API functions * Beginning FFI bindings for some of those functions It appears to call into the library correctly, but crashes deep in the process when trying to add to the constant pool. --- include/rbs/defines.h | 15 +++ include/rbs/parser.h | 4 + include/rbs/util/rbs_encoding.h | 1 + lib/rbs/ffi/parser.rb | 208 +++++++++++++++++++++++++++++++- 4 files changed, 227 insertions(+), 1 deletion(-) diff --git a/include/rbs/defines.h b/include/rbs/defines.h index 1f24e3c67..0ca4ab386 100644 --- a/include/rbs/defines.h +++ b/include/rbs/defines.h @@ -83,4 +83,19 @@ #define RBS_ATTRIBUTE_UNUSED #endif +/*********************************************************************************************************************** + * Exported functions from dynamic lib * + **********************************************************************************************************************/ +#ifndef RBS_EXPORTED_FUNCTION +# ifdef RBS_EXPORT_SYMBOLS +# ifdef _WIN32 +# define RBS_EXPORTED_FUNCTION __declspec(dllexport) extern +# else +# define RBS_EXPORTED_FUNCTION __attribute__((__visibility__("default"))) extern +# endif +# else +# define RBS_EXPORTED_FUNCTION +# endif +#endif + #endif diff --git a/include/rbs/parser.h b/include/rbs/parser.h index efb06ebe5..55acf560a 100644 --- a/include/rbs/parser.h +++ b/include/rbs/parser.h @@ -99,7 +99,9 @@ rbs_lexer_t *rbs_lexer_new(rbs_allocator_t *, rbs_string_t string, const rbs_enc * rbs_parser_new(buffer, string, encoding, 0, 1); * ``` * */ +RBS_EXPORTED_FUNCTION rbs_parser_t *rbs_parser_new(rbs_string_t string, const rbs_encoding_t *encoding, int start_pos, int end_pos); +RBS_EXPORTED_FUNCTION void rbs_parser_free(rbs_parser_t *parser); /** @@ -126,8 +128,10 @@ rbs_ast_comment_t *rbs_parser_get_comment(rbs_parser_t *parser, int subject_line void rbs_parser_set_error(rbs_parser_t *parser, rbs_token_t tok, bool syntax_error, const char *fmt, ...) RBS_ATTRIBUTE_FORMAT(4, 5); +RBS_EXPORTED_FUNCTION bool rbs_parse_type(rbs_parser_t *parser, rbs_node_t **type, bool void_allowed, bool self_allowed, bool classish_allowed); bool rbs_parse_method_type(rbs_parser_t *parser, rbs_method_type_t **method_type, bool require_eof, bool classish_allowed); +RBS_EXPORTED_FUNCTION bool rbs_parse_signature(rbs_parser_t *parser, rbs_signature_t **signature); bool rbs_parse_type_params(rbs_parser_t *parser, bool module_type_params, rbs_node_list_t **params); diff --git a/include/rbs/util/rbs_encoding.h b/include/rbs/util/rbs_encoding.h index 59f99c7af..1cbdfb4ad 100644 --- a/include/rbs/util/rbs_encoding.h +++ b/include/rbs/util/rbs_encoding.h @@ -277,6 +277,7 @@ extern const rbs_encoding_t rbs_encodings[RBS_ENCODING_MAXIMUM]; * @param end A pointer to the last byte of the name. * @returns A pointer to the encoding struct if one is found, otherwise NULL. */ +RBS_EXPORTED_FUNCTION const rbs_encoding_t *rbs_encoding_find(const uint8_t *start, const uint8_t *end); #endif diff --git a/lib/rbs/ffi/parser.rb b/lib/rbs/ffi/parser.rb index c143ba855..c80954723 100644 --- a/lib/rbs/ffi/parser.rb +++ b/lib/rbs/ffi/parser.rb @@ -1,7 +1,201 @@ # frozen_string_literal: true +require 'ffi' module RBS + + module Native + extend FFI::Library + + ffi_lib "librbs.dylib" + + NodeType = enum( + :ast_annotation, 1, + :ast_bool, 2, + :ast_comment, 3, + :ast_declarations_class, 4, + :ast_declarations_class_super, 5, + :ast_declarations_class_alias, 6, + :ast_declarations_constant, 7, + :ast_declarations_global, 8, + :ast_declarations_interface, 9, + :ast_declarations_module, 10, + :ast_declarations_module_self, 11, + :ast_declarations_module_alias, 12, + :ast_declarations_type_alias, 13, + :ast_directives_use, 14, + :ast_directives_use_single_clause, 15, + :ast_directives_use_wildcard_clause, 16, + :ast_integer, 17, + :ast_members_alias, 18, + :ast_members_attr_accessor, 19, + :ast_members_attr_reader, 20, + :ast_members_attr_writer, 21, + :ast_members_class_instance_variable, 22, + :ast_members_class_variable, 23, + :ast_members_extend, 24, + :ast_members_include, 25, + :ast_members_instance_variable, 26, + :ast_members_method_definition, 27, + :ast_members_method_definition_overload, 28, + :ast_members_prepend, 29, + :ast_members_private, 30, + :ast_members_public, 31, + :ast_ruby_annotations_class_alias_annotation, 32, + :ast_ruby_annotations_colon_method_type_annotation, 33, + :ast_ruby_annotations_instance_variable_annotation, 34, + :ast_ruby_annotations_method_types_annotation, 35, + :ast_ruby_annotations_module_alias_annotation, 36, + :ast_ruby_annotations_node_type_assertion, 37, + :ast_ruby_annotations_return_type_annotation, 38, + :ast_ruby_annotations_skip_annotation, 39, + :ast_ruby_annotations_type_application_annotation, 40, + :ast_string, 41, + :ast_type_param, 42, + :method_type, 43, + :namespace, 44, + :signature, 45, + :type_name, 46, + :types_alias, 47, + :types_bases_any, 48, + :types_bases_bool, 49, + :types_bases_bottom, 50, + :types_bases_class, 51, + :types_bases_instance, 52, + :types_bases_nil, 53, + :types_bases_self, 54, + :types_bases_top, 55, + :types_bases_void, 56, + :types_block, 57, + :types_class_instance, 58, + :types_class_singleton, 59, + :types_function, 60, + :types_function_param, 61, + :types_interface, 62, + :types_intersection, 63, + :types_literal, 64, + :types_optional, 65, + :types_proc, 66, + :types_record, 67, + :types_record_field_type, 68, + :types_tuple, 69, + :types_union, 70, + :types_untyped_function, 71, + :types_variable, 72, + :keyword, + :symbol, + ) + + class FFI::Struct + def safe_get(name) + value = self[name] + if value.respond_to?(:null?) && value.null? + value = nil + end + value + end + end + + class StringPointer < FFI::Struct + layout :start, :pointer, :end, :pointer + + def self.new(str) + str_ptr = super() + @ptr = FFI::MemoryPointer.from_string(str) + str_ptr[:start] = @ptr + str_ptr[:end] = @ptr + str.length + p str_ptr[:start] + p str_ptr[:end] + str_ptr + end + end + + class Node < FFI::Struct + layout :type, :uint8, :location, :pointer + + def inspect + "#" + str + end + end + + class Signature < FFI::Struct + layout :base, Node, :directives, NodeList.ptr, :declarations, NodeList.ptr + + def inspect + [base, directives, declarations].inspect + end + + def base + self.safe_get(:base) + end + + def directives + self.safe_get(:directives) + end + + def declarations + self.safe_get(:declarations) + end + end + + attach_function :rbs_encoding_find, [:pointer, :pointer], :pointer + + attach_function :rbs_parser_new, [StringPointer.by_value, :pointer, :int32, :int32], :pointer + attach_function :rbs_parser_free, [:pointer], :void + attach_function :rbs_parse_type, [:pointer, :pointer, :bool, :bool, :bool], :bool + attach_function :rbs_parse_signature, [:pointer, :pointer], :bool + end + class Parser + + def self.new_parser(str, start_pos, end_pos) + buffer = Native::StringPointer.new(str) + enc = str.encoding.name + encoding_ptr = FFI::MemoryPointer.from_string(enc) + rbs_encoding = Native.rbs_encoding_find(encoding_ptr, encoding_ptr + enc.length) + Native.rbs_parser_new buffer, rbs_encoding, start_pos, end_pos + end + + def self.free_parser(parser) + Native.rbs_parser_free parser + end + def _parse_type(buffer, start_pos, end_pos, variables, require_eof) end @@ -10,8 +204,20 @@ def _parse_method_type(buffer, start_pos, end_pos, variables, require_eof) end - def _parse_signature(buffer, start_pos, end_pos) + def self._parse_signature(buffer, start_pos, end_pos) + str = buffer.content + + parser = new_parser(str, start_pos, end_pos) + + signature_ptr = FFI::MemoryPointer.new(:pointer, 1) + + result = Native.rbs_parse_signature parser, signature_ptr + + signature = Native::Signature.new(signature_ptr.get_pointer(0)) + + raise RuntimeError.new("failed to parse signature") unless result + [signature.directives.to_a, signature.declarations.to_a] end def _parse_type_params(buffer, start_pos, end_pos, module_type_params)