Add Set C-API

This should be a minimal C-API needed to deal with Set objects. It
supports creating the sets, checking whether an element is the set,
adding and removing elements, iterating over the elements, clearing
a set, and returning the size of the set.

Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>
This commit is contained in:
Jeremy Evans 2025-06-28 17:09:24 -07:00
parent 08d4f7893e
commit 2ab38691a2
8 changed files with 322 additions and 3 deletions

View file

@ -51,6 +51,7 @@
#include "ruby/internal/intern/re.h"
#include "ruby/internal/intern/ruby.h"
#include "ruby/internal/intern/select.h"
#include "ruby/internal/intern/set.h"
#include "ruby/internal/intern/signal.h"
#include "ruby/internal/intern/sprintf.h"
#include "ruby/internal/intern/string.h"

View file

@ -93,6 +93,7 @@ RUBY_EXTERN VALUE rb_cRandom; /**< `Random` class. */
RUBY_EXTERN VALUE rb_cRange; /**< `Range` class. */
RUBY_EXTERN VALUE rb_cRational; /**< `Rational` class. */
RUBY_EXTERN VALUE rb_cRegexp; /**< `Regexp` class. */
RUBY_EXTERN VALUE rb_cSet; /**< `Set` class. */
RUBY_EXTERN VALUE rb_cStat; /**< `File::Stat` class. */
RUBY_EXTERN VALUE rb_cString; /**< `String` class. */
RUBY_EXTERN VALUE rb_cStruct; /**< `Struct` class. */

View file

@ -0,0 +1,111 @@
#ifndef RBIMPL_INTERN_SET_H /*-*-C++-*-vi:se ft=cpp:*/
#define RBIMPL_INTERN_SET_H
/**
* @file
* @author Ruby developers <ruby-core@ruby-lang.org>
* @copyright This file is a part of the programming language Ruby.
* Permission is hereby granted, to either redistribute and/or
* modify this file, provided that the conditions mentioned in the
* file COPYING are met. Consult the file for details.
* @warning Symbols prefixed with either `RBIMPL` or `rbimpl` are
* implementation details. Don't take them as canon. They could
* rapidly appear then vanish. The name (path) of this header file
* is also an implementation detail. Do not expect it to persist
* at the place it is now. Developers are free to move it anywhere
* anytime at will.
* @note To ruby-core: remember that this header can be possibly
* recursively included from extension libraries written in C++.
* Do not expect for instance `__VA_ARGS__` is always available.
* We assume C99 for ruby itself but we don't assume languages of
* extension libraries. They could be written in C++98.
* @brief Public APIs related to ::rb_cSet.
*/
#include "ruby/internal/attr/nonnull.h"
#include "ruby/internal/dllexport.h"
#include "ruby/internal/value.h"
RBIMPL_SYMBOL_EXPORT_BEGIN()
/* set.c */
RBIMPL_ATTR_NONNULL(())
/**
* Iterates over a set. Calls func with each element of the set and the
* argument given. func should return ST_CONTINUE, ST_STOP, or ST_DELETE.
*
* @param[in] set An instance of ::rb_cSet to iterate over.
* @param[in] func Callback function to yield.
* @param[in] arg Passed as-is to `func`.
* @exception rb_eRuntimeError `set` was tampered during iterating.
*/
void rb_set_foreach(VALUE set, int (*func)(VALUE element, VALUE arg), VALUE arg);
/**
* Creates a new, empty set object.
*
* @return An allocated new instance of ::rb_cSet.
*/
VALUE rb_set_new(void);
/**
* Identical to rb_set_new(), except it additionally specifies how many elements
* it is expected to contain. This way you can create a set that is large enough
* for your need. For large sets, it means it won't need to be reallocated
* much, improving performance.
*
* @param[in] capa Designed capacity of the set.
* @return An empty Set, whose capacity is `capa`.
*/
VALUE rb_set_new_capa(size_t capa);
/**
* Whether the set contains the given element.
*
* @param[in] set Set to look into.
* @param[in] element Set element to look for.
* @return true if element is in the set, falst otherwise.
*/
bool rb_set_lookup(VALUE set, VALUE element);
/**
* Adds element to set.
*
* @param[in] set Target set table to modify.
* @param[in] element Arbitrary Ruby object.
* @exception rb_eFrozenError `set` is frozen.
* @return true if element was not already in set, false otherwise
* @post `element` is in `set`.
*/
bool rb_set_add(VALUE set, VALUE element);
/**
* Removes all entries from set.
*
* @param[out] set Target to clear.
* @exception rb_eFrozenError `set`is frozen.
* @return The passed `set`
* @post `set` has no elements.
*/
VALUE rb_set_clear(VALUE set);
/**
* Removes the element from from set.
*
* @param[in] set Target set to modify.
* @param[in] element Key to delete.
* @retval true if element was already in set, false otherwise
* @post `set` does not have `element` as an element.
*/
bool rb_set_delete(VALUE set, VALUE element);
/**
* Returns the number of elements in the set.
*
* @param[in] set A set object.
* @return The size of the set.
*/
size_t rb_set_size(VALUE set);
RBIMPL_SYMBOL_EXPORT_END()
#endif /* RBIMPL_INTERN_SET_H */

50
set.c
View file

@ -1913,6 +1913,56 @@ compat_loader(VALUE self, VALUE a)
return set_i_from_hash(self, rb_ivar_get(a, id_i_hash));
}
/* C-API functions */
void
rb_set_foreach(VALUE set, int (*func)(VALUE element, VALUE arg), VALUE arg)
{
set_iter(set, func, arg);
}
VALUE
rb_set_new(void)
{
return set_alloc_with_size(rb_cSet, 0);
}
VALUE
rb_set_new_capa(size_t capa)
{
return set_alloc_with_size(rb_cSet, (st_index_t)capa);
}
bool
rb_set_lookup(VALUE set, VALUE element)
{
return RSET_IS_MEMBER(set, element);
}
bool
rb_set_add(VALUE set, VALUE element)
{
return set_i_add_p(set, element) != Qnil;
}
VALUE
rb_set_clear(VALUE set)
{
return set_i_clear(set);
}
bool
rb_set_delete(VALUE set, VALUE element)
{
return set_i_delete_p(set, element) != Qnil;
}
size_t
rb_set_size(VALUE set)
{
return RSET_SIZE(set);
}
/*
* Document-class: Set
*

View file

@ -0,0 +1,62 @@
#include "ruby.h"
#include "rubyspec.h"
#ifdef __cplusplus
extern "C" {
#endif
#define RBOOL(x) ((x) ? Qtrue : Qfalse)
int yield_element_and_arg(VALUE element, VALUE arg) {
return RTEST(rb_yield_values(2, element, arg)) ? ST_CONTINUE : ST_STOP;
}
VALUE set_spec_rb_set_foreach(VALUE self, VALUE set, VALUE arg) {
rb_set_foreach(set, yield_element_and_arg, arg);
return Qnil;
}
VALUE set_spec_rb_set_new(VALUE self) {
return rb_set_new();
}
VALUE set_spec_rb_set_new_capa(VALUE self, VALUE capa) {
return rb_set_new_capa(NUM2INT(capa));
}
VALUE set_spec_rb_set_lookup(VALUE self, VALUE set, VALUE element) {
return RBOOL(rb_set_lookup(set, element));
}
VALUE set_spec_rb_set_add(VALUE self, VALUE set, VALUE element) {
return RBOOL(rb_set_add(set, element));
}
VALUE set_spec_rb_set_clear(VALUE self, VALUE set) {
return rb_set_clear(set);
}
VALUE set_spec_rb_set_delete(VALUE self, VALUE set, VALUE element) {
return RBOOL(rb_set_delete(set, element));
}
VALUE set_spec_rb_set_size(VALUE self, VALUE set) {
return SIZET2NUM(rb_set_size(set));
}
void Init_set_spec(void) {
VALUE cls = rb_define_class("CApiSetSpecs", rb_cObject);
rb_define_method(cls, "rb_set_foreach", set_spec_rb_set_foreach, 2);
rb_define_method(cls, "rb_set_new", set_spec_rb_set_new, 0);
rb_define_method(cls, "rb_set_new_capa", set_spec_rb_set_new_capa, 1);
rb_define_method(cls, "rb_set_lookup", set_spec_rb_set_lookup, 2);
rb_define_method(cls, "rb_set_add", set_spec_rb_set_add, 2);
rb_define_method(cls, "rb_set_clear", set_spec_rb_set_clear, 1);
rb_define_method(cls, "rb_set_delete", set_spec_rb_set_delete, 2);
rb_define_method(cls, "rb_set_size", set_spec_rb_set_size, 1);
}
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,96 @@
require_relative 'spec_helper'
load_extension("set")
describe "C-API Set function" do
before :each do
@s = CApiSetSpecs.new
end
ruby_version_is "3.5" do
describe "rb_set_foreach" do
it "calls function with each element and arg" do
a = []
@s.rb_set_foreach(Set[1, 2], 3) {|*args| a.concat(args) }
a.should == [1, 3, 2, 3]
end
it "respects function return value" do
a = []
@s.rb_set_foreach(Set[1, 2], 3) do |*args|
a.concat(args)
false
end
a.should == [1, 3]
end
end
describe "rb_set_new" do
it "returns a new set" do
@s.rb_set_new.should == Set[]
end
end
describe "rb_set_new_capa" do
it "returns a new set" do
@s.rb_set_new_capa(3).should == Set[]
end
end
describe "rb_set_lookup" do
it "returns whether the element is in the set" do
set = Set[1]
@s.rb_set_lookup(set, 1).should == true
@s.rb_set_lookup(set, 2).should == false
end
end
describe "rb_set_add" do
it "adds element to set" do
set = Set[]
@s.rb_set_add(set, 1).should == true
set.should == Set[1]
@s.rb_set_add(set, 2).should == true
set.should == Set[1, 2]
end
it "returns false if element is already in set" do
set = Set[1]
@s.rb_set_add(set, 1).should == false
set.should == Set[1]
end
end
describe "rb_set_clear" do
it "empties and returns self" do
set = Set[1]
@s.rb_set_clear(set).should equal(set)
set.should == Set[]
end
end
describe "rb_set_delete" do
it "removes element from set" do
set = Set[1, 2]
@s.rb_set_delete(set, 1).should == true
set.should == Set[2]
@s.rb_set_delete(set, 2).should == true
set.should == Set[]
end
it "returns false if element is not already in set" do
set = Set[2]
@s.rb_set_delete(set, 1).should == false
set.should == Set[2]
end
end
describe "rb_set_size" do
it "returns number of elements in set" do
@s.rb_set_size(Set[]).should == 0
@s.rb_set_size(Set[1]).should == 1
@s.rb_set_size(Set[1,2]).should == 2
end
end
end
end

2
zjit.c
View file

@ -32,8 +32,6 @@
#include <errno.h>
RUBY_EXTERN VALUE rb_cSet; // defined in set.c and it's not exposed yet
uint32_t
rb_zjit_get_page_size(void)
{

View file

@ -756,6 +756,7 @@ unsafe extern "C" {
pub static mut rb_cNumeric: VALUE;
pub static mut rb_cRange: VALUE;
pub static mut rb_cRegexp: VALUE;
pub static mut rb_cSet: VALUE;
pub static mut rb_cString: VALUE;
pub static mut rb_cSymbol: VALUE;
pub static mut rb_cThread: VALUE;
@ -905,7 +906,6 @@ unsafe extern "C" {
lines: *mut ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void);
pub static mut rb_cSet: VALUE;
pub fn rb_zjit_get_page_size() -> u32;
pub fn rb_zjit_reserve_addr_space(mem_size: u32) -> *mut u8;
pub fn rb_zjit_profile_disable(iseq: *const rb_iseq_t);