mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
c679c2762d
Co-authored-by: Claude <noreply@anthropic.com>
1061 lines
26 KiB
C
1061 lines
26 KiB
C
/*
|
|
** symbol.c - Symbol class
|
|
**
|
|
** See Copyright Notice in mruby.h
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <mruby.h>
|
|
#include <mruby/string.h>
|
|
#include <mruby/dump.h>
|
|
#include <mruby/class.h>
|
|
#include <mruby/internal.h>
|
|
#include <mruby/presym.h>
|
|
|
|
#ifndef MRB_NO_PRESYM
|
|
|
|
#ifndef MRB_PRESYM_SCANNING
|
|
/* const uint16_t presym_length_table[] */
|
|
/* const char * const presym_name_table[] */
|
|
# include <mruby/presym/table.h>
|
|
#endif
|
|
|
|
static mrb_sym
|
|
presym_find(const char *name, size_t len)
|
|
{
|
|
if (presym_length_table[MRB_PRESYM_MAX-1] < len) return 0;
|
|
|
|
mrb_sym presym_size = MRB_PRESYM_MAX;
|
|
for (mrb_sym start = 0; presym_size != 0; presym_size/=2) {
|
|
mrb_sym idx = start+presym_size/2;
|
|
int cmp = (int)len-(int)presym_length_table[idx];
|
|
if (cmp == 0) {
|
|
cmp = memcmp(name, presym_name_table[idx], len);
|
|
if (cmp == 0) return idx+1;
|
|
}
|
|
if (0 < cmp) {
|
|
start = ++idx;
|
|
--presym_size;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const char*
|
|
presym_sym2name(mrb_sym sym, mrb_int *lenp)
|
|
{
|
|
if (sym > MRB_PRESYM_MAX) return NULL;
|
|
if (lenp) *lenp = presym_length_table[sym-1];
|
|
return presym_name_table[sym-1];
|
|
}
|
|
|
|
#endif /* MRB_NO_PRESYM */
|
|
|
|
/* ------------------------------------------------------ */
|
|
|
|
/* LSB pointer tagging for literal flags */
|
|
#define SYMTBL_LITERAL_FLAG ((uintptr_t)1)
|
|
|
|
/* Extract clean pointer for memory operations */
|
|
static inline const char*
|
|
symtbl_get_ptr(const char *tagged_ptr)
|
|
{
|
|
return (const char*)((uintptr_t)tagged_ptr & ~SYMTBL_LITERAL_FLAG);
|
|
}
|
|
|
|
/* Check if symbol is literal by testing LSB */
|
|
static inline mrb_bool
|
|
symtbl_is_literal(const char *tagged_ptr)
|
|
{
|
|
return ((uintptr_t)tagged_ptr & SYMTBL_LITERAL_FLAG) != 0;
|
|
}
|
|
|
|
/* Create tagged pointer for literal string - with alignment verification */
|
|
static inline const char*
|
|
symtbl_tag_literal(const char *ptr)
|
|
{
|
|
mrb_assert(((uintptr_t)ptr & 1) == 0); /* Assert alignment */
|
|
return (const char*)((uintptr_t)ptr | SYMTBL_LITERAL_FLAG);
|
|
}
|
|
|
|
static void
|
|
sym_validate_len(mrb_state *mrb, size_t len)
|
|
{
|
|
if (len >= UINT16_MAX) {
|
|
mrb_raise(mrb, E_ARGUMENT_ERROR, "symbol length too long");
|
|
}
|
|
}
|
|
|
|
/* Chunk-based string pool for heap-allocated symbol names */
|
|
#define MRB_SYM_POOL_CHUNK_SIZE 4096
|
|
|
|
struct sym_pool_chunk {
|
|
struct sym_pool_chunk *next;
|
|
size_t used;
|
|
char buf[]; /* flexible array */
|
|
};
|
|
|
|
static char*
|
|
sym_pool_alloc(mrb_state *mrb, size_t size)
|
|
{
|
|
/* round up to even size to keep pointers even-aligned (LSB tagging) */
|
|
size_t asize = (size + 1) & ~(size_t)1;
|
|
struct sym_pool_chunk *chunk = (struct sym_pool_chunk*)mrb->sym_pool;
|
|
if (chunk && chunk->used + asize <= MRB_SYM_POOL_CHUNK_SIZE) {
|
|
char *p = chunk->buf + chunk->used;
|
|
chunk->used += asize;
|
|
return p;
|
|
}
|
|
size_t csize = asize > MRB_SYM_POOL_CHUNK_SIZE ? asize : MRB_SYM_POOL_CHUNK_SIZE;
|
|
chunk = (struct sym_pool_chunk*)mrb_malloc(mrb,
|
|
offsetof(struct sym_pool_chunk, buf) + csize);
|
|
chunk->next = (struct sym_pool_chunk*)mrb->sym_pool;
|
|
chunk->used = asize;
|
|
mrb->sym_pool = (void*)chunk;
|
|
return chunk->buf;
|
|
}
|
|
|
|
/* Hash table for symbols (allocated on demand when symbols exceed threshold) */
|
|
struct mrb_sym_hash_table {
|
|
uint8_t *symlink; /* collision resolution chains */
|
|
mrb_sym buckets[256]; /* hash buckets */
|
|
};
|
|
|
|
#ifdef MRB_USE_ALL_SYMBOLS
|
|
# define SYMBOL_INLINE_P(sym) FALSE
|
|
# define sym_inline_pack(name, len) 0
|
|
# define sym_inline_unpack(sym, buf, lenp) NULL
|
|
#else
|
|
# define SYMBOL_INLINE_P(sym) ((sym) >= (1<<20))
|
|
|
|
static const char pack_table[] = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
|
|
static mrb_sym
|
|
sym_inline_pack(const char *name, size_t len)
|
|
{
|
|
const size_t pack_length_max = 4;
|
|
mrb_sym sym = 0;
|
|
|
|
if (len > pack_length_max) return 0; /* too long */
|
|
if (len == 0) return 0; /* empty string */
|
|
for (size_t i=0; i<len; i++) {
|
|
char c = name[i];
|
|
if (c == 0) return 0; /* NUL in name */
|
|
const char *p = strchr(pack_table, (int)c);
|
|
if (p == 0) return 0; /* non alnum char */
|
|
|
|
uint32_t bits = (uint32_t)(p - pack_table)+1;
|
|
sym |= bits<<(20-i*6);
|
|
}
|
|
mrb_assert(SYMBOL_INLINE_P(sym));
|
|
return sym;
|
|
}
|
|
|
|
static const char*
|
|
sym_inline_unpack(mrb_sym sym, char *buf, mrb_int *lenp)
|
|
{
|
|
const size_t pack_length_max = 4;
|
|
size_t i;
|
|
|
|
mrb_assert(SYMBOL_INLINE_P(sym));
|
|
|
|
for (i=0; i<pack_length_max; i++) {
|
|
uint32_t bits = sym>>(20-i*6) & 0x3f;
|
|
if (bits == 0) break;
|
|
buf[i] = pack_table[bits-1];
|
|
}
|
|
buf[i] = '\0';
|
|
if (lenp) *lenp = i;
|
|
return buf;
|
|
}
|
|
#endif
|
|
|
|
/* Check if using hash table mode */
|
|
static inline mrb_bool
|
|
using_hash_table(mrb_state *mrb)
|
|
{
|
|
return mrb->symhash != NULL;
|
|
}
|
|
|
|
static mrb_bool
|
|
sym_check(mrb_state *mrb, const char *name, size_t len, mrb_sym i)
|
|
{
|
|
const char *tagged_ptr = mrb->symtbl[i];
|
|
const char *symname = symtbl_get_ptr(tagged_ptr); /* Untag for access */
|
|
size_t symlen;
|
|
|
|
if (symtbl_is_literal(tagged_ptr)) {
|
|
symlen = strlen(symname);
|
|
}
|
|
else {
|
|
/* length in BER */
|
|
symlen = mrb_packed_int_decode((const uint8_t*)symname, (const uint8_t**)&symname);
|
|
}
|
|
if (len == symlen && memcmp(symname, name, len) == 0) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static mrb_sym
|
|
find_symbol_linear(mrb_state *mrb, const char *name, size_t len)
|
|
{
|
|
mrb_sym i;
|
|
|
|
for (i = 1; i <= mrb->symidx; i++) {
|
|
if (sym_check(mrb, name, len, i)) {
|
|
return (i + MRB_PRESYM_MAX);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static mrb_sym
|
|
find_symbol_hash(mrb_state *mrb, const char *name, size_t len, uint8_t *hashp)
|
|
{
|
|
mrb_sym i;
|
|
uint8_t hash;
|
|
struct mrb_sym_hash_table *ht = mrb->symhash;
|
|
|
|
hash = mrb_byte_hash((const uint8_t*)name, len);
|
|
if (hashp) *hashp = hash;
|
|
|
|
i = ht->buckets[hash];
|
|
if (i == 0) return 0;
|
|
for (;;) {
|
|
if (sym_check(mrb, name, len, i)) {
|
|
return (i+MRB_PRESYM_MAX);
|
|
}
|
|
uint8_t diff = ht->symlink[i];
|
|
if (diff == 0xff) {
|
|
i -= 0xff;
|
|
while (i > 0) {
|
|
if (sym_check(mrb, name, len, i)) {
|
|
return (i+MRB_PRESYM_MAX);
|
|
}
|
|
i--;
|
|
}
|
|
return 0;
|
|
}
|
|
if (diff == 0) return 0;
|
|
i -= diff;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static mrb_sym
|
|
find_symbol(mrb_state *mrb, const char *name, size_t len, uint8_t *hashp)
|
|
{
|
|
mrb_sym i;
|
|
|
|
#ifndef MRB_NO_PRESYM
|
|
/* presym */
|
|
i = presym_find(name, len);
|
|
if (i > 0) return i;
|
|
#endif
|
|
|
|
/* inline symbol */
|
|
i = sym_inline_pack(name, len);
|
|
if (i > 0) return i;
|
|
|
|
if (using_hash_table(mrb)) {
|
|
/* Hash table mode - O(1) average case */
|
|
return find_symbol_hash(mrb, name, len, hashp);
|
|
}
|
|
else {
|
|
/* Linear mode - O(n) but fast for small n */
|
|
if (hashp) *hashp = mrb_byte_hash((const uint8_t*)name, len);
|
|
return find_symbol_linear(mrb, name, len);
|
|
}
|
|
}
|
|
|
|
static void
|
|
migrate_to_hash_table(mrb_state *mrb)
|
|
{
|
|
struct mrb_sym_hash_table *ht;
|
|
mrb_sym i;
|
|
|
|
mrb_assert(mrb->symhash == NULL);
|
|
mrb_assert(mrb->symidx >= MRB_SYMBOL_LINEAR_THRESHOLD);
|
|
|
|
/* Allocate hash table structure */
|
|
ht = (struct mrb_sym_hash_table*)mrb_calloc(mrb, 1, sizeof(struct mrb_sym_hash_table));
|
|
ht->symlink = (uint8_t*)mrb_calloc(mrb, mrb->symcapa, sizeof(uint8_t));
|
|
|
|
/* Rebuild hash table from existing linear data */
|
|
for (i = 1; i <= mrb->symidx; i++) {
|
|
const char *tagged_ptr = mrb->symtbl[i];
|
|
const char *name = symtbl_get_ptr(tagged_ptr);
|
|
size_t len;
|
|
uint8_t hash;
|
|
|
|
/* Get name and length from tagged pointer */
|
|
if (symtbl_is_literal(tagged_ptr)) {
|
|
len = strlen(name);
|
|
}
|
|
else {
|
|
/* This is a packed length string */
|
|
len = mrb_packed_int_decode((const uint8_t*)name, (const uint8_t**)&name);
|
|
}
|
|
|
|
hash = mrb_byte_hash((const uint8_t*)name, len);
|
|
|
|
/* Build collision chain */
|
|
if (ht->buckets[hash] != 0) {
|
|
mrb_sym diff = i - ht->buckets[hash];
|
|
ht->symlink[i] = (diff > 0xff) ? 0xff : (uint8_t)diff;
|
|
}
|
|
else {
|
|
ht->symlink[i] = 0;
|
|
}
|
|
ht->buckets[hash] = i;
|
|
}
|
|
|
|
mrb->symhash = ht;
|
|
}
|
|
|
|
static mrb_sym
|
|
sym_intern_common(mrb_state *mrb, const char *name, size_t len, mrb_bool lit)
|
|
{
|
|
mrb_sym sym;
|
|
|
|
sym = mrb->symidx + 1;
|
|
if (mrb->symcapa <= sym) {
|
|
size_t symcapa = mrb->symcapa;
|
|
if (symcapa == 0) symcapa = 100;
|
|
else symcapa = (size_t)(symcapa * 6 / 5);
|
|
mrb->symtbl = (const char**)mrb_realloc(mrb, (void*)mrb->symtbl, sizeof(char*)*symcapa);
|
|
if (using_hash_table(mrb)) {
|
|
struct mrb_sym_hash_table *ht = mrb->symhash;
|
|
ht->symlink = (uint8_t*)mrb_realloc(mrb, ht->symlink, symcapa);
|
|
}
|
|
mrb->symcapa = symcapa;
|
|
}
|
|
|
|
lit = lit || mrb_ro_data_p(name);
|
|
if (lit && name[len] == 0 && strlen(name) == len) {
|
|
if (((uintptr_t)name & 1) != 0) {
|
|
/* Fallback: unaligned literal, allocate heap copy */
|
|
goto heap_allocation;
|
|
}
|
|
mrb->symtbl[sym] = symtbl_tag_literal(name);
|
|
}
|
|
else {
|
|
heap_allocation:;
|
|
/* Always heap-allocate when not explicitly literal */
|
|
uint32_t ulen = (uint32_t)len;
|
|
size_t ilen = mrb_packed_int_len(ulen);
|
|
char *p = sym_pool_alloc(mrb, len+ilen+1);
|
|
mrb_packed_int_encode(ulen, (uint8_t*)p);
|
|
memcpy(p+ilen, name, len);
|
|
p[ilen+len] = 0;
|
|
mrb->symtbl[sym] = p; /* Untagged = heap */
|
|
}
|
|
|
|
mrb->symidx = sym;
|
|
return sym;
|
|
}
|
|
|
|
static mrb_sym
|
|
sym_intern_linear_mode(mrb_state *mrb, const char *name, size_t len, mrb_bool lit)
|
|
{
|
|
mrb_sym sym = sym_intern_common(mrb, name, len, lit);
|
|
return (sym+MRB_PRESYM_MAX);
|
|
}
|
|
|
|
static mrb_sym
|
|
sym_intern_hash_mode(mrb_state *mrb, const char *name, size_t len, mrb_bool lit)
|
|
{
|
|
mrb_sym sym = sym_intern_common(mrb, name, len, lit);
|
|
struct mrb_sym_hash_table *ht = mrb->symhash;
|
|
uint8_t hash = mrb_byte_hash((const uint8_t*)name, len);
|
|
|
|
if (ht->buckets[hash]) {
|
|
mrb_sym i = sym - ht->buckets[hash];
|
|
if (i > 0xff)
|
|
ht->symlink[sym] = 0xff;
|
|
else
|
|
ht->symlink[sym] = i;
|
|
}
|
|
else {
|
|
ht->symlink[sym] = 0;
|
|
}
|
|
ht->buckets[hash] = sym;
|
|
|
|
return (sym+MRB_PRESYM_MAX);
|
|
}
|
|
|
|
static mrb_sym
|
|
sym_intern(mrb_state *mrb, const char *name, size_t len, mrb_bool lit)
|
|
{
|
|
mrb_sym sym;
|
|
|
|
sym_validate_len(mrb, len);
|
|
sym = find_symbol(mrb, name, len, NULL);
|
|
if (sym > 0) return sym;
|
|
|
|
/* Check if we need to migrate to hash table */
|
|
if (!using_hash_table(mrb) && mrb->symidx >= MRB_SYMBOL_LINEAR_THRESHOLD) {
|
|
migrate_to_hash_table(mrb);
|
|
}
|
|
|
|
/* Add new symbol using current mode */
|
|
if (using_hash_table(mrb)) {
|
|
return sym_intern_hash_mode(mrb, name, len, lit);
|
|
}
|
|
else {
|
|
return sym_intern_linear_mode(mrb, name, len, lit);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Interns a string, creating a symbol from it if it doesn't already exist,
|
|
* or returning the existing symbol if it does.
|
|
*
|
|
* mrb: The mruby state.
|
|
* name: The string to intern.
|
|
* len: The length of the string.
|
|
*
|
|
* Returns the interned symbol.
|
|
*/
|
|
MRB_API mrb_sym
|
|
mrb_intern(mrb_state *mrb, const char *name, size_t len)
|
|
{
|
|
return sym_intern(mrb, name, len, FALSE);
|
|
}
|
|
|
|
/*
|
|
* Interns a static string, creating a symbol from it.
|
|
* This function is similar to mrb_intern, but it assumes that the given
|
|
* string is static and will not be freed.
|
|
*
|
|
* mrb: The mruby state.
|
|
* name: The static string to intern.
|
|
* len: The length of the string.
|
|
*
|
|
* Returns the interned symbol.
|
|
*/
|
|
MRB_API mrb_sym
|
|
mrb_intern_static(mrb_state *mrb, const char *name, size_t len)
|
|
{
|
|
return sym_intern(mrb, name, len, TRUE);
|
|
}
|
|
|
|
/*
|
|
* Interns a C string (null-terminated), creating a symbol from it.
|
|
* This function is a convenience wrapper around mrb_intern that
|
|
* automatically calculates the length of the string.
|
|
*
|
|
* mrb: The mruby state.
|
|
* name: The C string to intern.
|
|
*
|
|
* Returns the interned symbol.
|
|
*/
|
|
MRB_API mrb_sym
|
|
mrb_intern_cstr(mrb_state *mrb, const char *name)
|
|
{
|
|
return mrb_intern(mrb, name, strlen(name));
|
|
}
|
|
|
|
/*
|
|
* Interns an mruby string value, creating a symbol from it.
|
|
*
|
|
* mrb: The mruby state.
|
|
* str: The mruby string value to intern.
|
|
*
|
|
* Returns the interned symbol.
|
|
*/
|
|
MRB_API mrb_sym
|
|
mrb_intern_str(mrb_state *mrb, mrb_value str)
|
|
{
|
|
return mrb_intern(mrb, RSTRING_PTR(str), RSTRING_LEN(str));
|
|
}
|
|
|
|
/*
|
|
* Checks if a symbol already exists for the given string.
|
|
*
|
|
* mrb: The mruby state.
|
|
* name: The string to check.
|
|
* len: The length of the string.
|
|
*
|
|
* Returns the symbol if it exists, otherwise 0.
|
|
*/
|
|
MRB_API mrb_sym
|
|
mrb_intern_check(mrb_state *mrb, const char *name, size_t len)
|
|
{
|
|
mrb_sym sym;
|
|
|
|
sym_validate_len(mrb, len);
|
|
sym = find_symbol(mrb, name, len, NULL);
|
|
if (sym > 0) return sym;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Checks if a symbol already exists for the given string.
|
|
*
|
|
* mrb: The mruby state.
|
|
* name: The string to check.
|
|
* len: The length of the string.
|
|
*
|
|
* Returns the symbol as an mrb_value if it exists, otherwise a nil value.
|
|
*/
|
|
MRB_API mrb_value
|
|
mrb_check_intern(mrb_state *mrb, const char *name, size_t len)
|
|
{
|
|
mrb_sym sym = mrb_intern_check(mrb, name, len);
|
|
if (sym == 0) return mrb_nil_value();
|
|
return mrb_symbol_value(sym);
|
|
}
|
|
|
|
/*
|
|
* Checks if a symbol already exists for the given C string (null-terminated).
|
|
* This function is a convenience wrapper around mrb_intern_check that
|
|
* automatically calculates the length of the string.
|
|
*
|
|
* mrb: The mruby state.
|
|
* name: The C string to check.
|
|
*
|
|
* Returns the symbol if it exists, otherwise 0.
|
|
*/
|
|
MRB_API mrb_sym
|
|
mrb_intern_check_cstr(mrb_state *mrb, const char *name)
|
|
{
|
|
return mrb_intern_check(mrb, name, strlen(name));
|
|
}
|
|
|
|
/*
|
|
* Checks if a symbol already exists for the given C string (null-terminated).
|
|
* This function is similar to mrb_intern_check_cstr, but returns the result
|
|
* as an mrb_value (either the symbol or nil).
|
|
*
|
|
* mrb: The mruby state.
|
|
* name: The C string to check.
|
|
*
|
|
* Returns the symbol as an mrb_value if it exists, otherwise a nil value.
|
|
*/
|
|
MRB_API mrb_value
|
|
mrb_check_intern_cstr(mrb_state *mrb, const char *name)
|
|
{
|
|
mrb_sym sym = mrb_intern_check_cstr(mrb, name);
|
|
if (sym == 0) return mrb_nil_value();
|
|
return mrb_symbol_value(sym);
|
|
}
|
|
|
|
/*
|
|
* Checks if a symbol already exists for the given mruby string value.
|
|
*
|
|
* mrb: The mruby state.
|
|
* str: The mruby string value to check.
|
|
*
|
|
* Returns the symbol if it exists, otherwise 0.
|
|
*/
|
|
MRB_API mrb_sym
|
|
mrb_intern_check_str(mrb_state *mrb, mrb_value str)
|
|
{
|
|
return mrb_intern_check(mrb, RSTRING_PTR(str), RSTRING_LEN(str));
|
|
}
|
|
|
|
/*
|
|
* Checks if a symbol already exists for the given mruby string value.
|
|
* This function is similar to mrb_intern_check_str, but returns the result
|
|
* as an mrb_value (either the symbol or nil).
|
|
*
|
|
* mrb: The mruby state.
|
|
* str: The mruby string value to check.
|
|
*
|
|
* Returns the symbol as an mrb_value if it exists, otherwise a nil value.
|
|
*/
|
|
MRB_API mrb_value
|
|
mrb_check_intern_str(mrb_state *mrb, mrb_value str)
|
|
{
|
|
mrb_sym sym = mrb_intern_check_str(mrb, str);
|
|
if (sym == 0) return mrb_nil_value();
|
|
return mrb_symbol_value(sym);
|
|
}
|
|
|
|
static const char*
|
|
sym2name_len(mrb_state *mrb, mrb_sym sym, char *buf, mrb_int *lenp)
|
|
{
|
|
if (sym == 0) goto outofsym;
|
|
if (SYMBOL_INLINE_P(sym)) return sym_inline_unpack(sym, buf, lenp);
|
|
|
|
#ifndef MRB_NO_PRESYM
|
|
{
|
|
const char *name = presym_sym2name(sym, lenp);
|
|
if (name) return name;
|
|
}
|
|
#endif
|
|
sym -= MRB_PRESYM_MAX;
|
|
|
|
if (mrb->symidx < sym) {
|
|
outofsym:
|
|
if (lenp) *lenp = 0;
|
|
return NULL;
|
|
}
|
|
|
|
const char *tagged_ptr = mrb->symtbl[sym];
|
|
const char *symname = symtbl_get_ptr(tagged_ptr); /* Untag for access */
|
|
|
|
if (!symtbl_is_literal(tagged_ptr)) {
|
|
uint32_t len = mrb_packed_int_decode((const uint8_t*)symname, (const uint8_t**)&symname);
|
|
if (lenp) *lenp = (mrb_int)len;
|
|
}
|
|
else if (lenp) {
|
|
*lenp = (mrb_int)strlen(symname);
|
|
}
|
|
return symname;
|
|
}
|
|
|
|
/*
|
|
* Retrieves the name and length of a symbol.
|
|
*
|
|
* mrb: The mruby state.
|
|
* sym: The symbol to retrieve the name and length for.
|
|
* lenp: A pointer to an mrb_int where the length of the symbol name will be stored.
|
|
* This can be NULL if the length is not needed.
|
|
*
|
|
* Returns a pointer to the C string representing the symbol's name,
|
|
* or NULL if the symbol is invalid.
|
|
* For inline symbols, the name is copied to an internal buffer (mrb->symbuf)
|
|
* unless MRB_USE_ALL_SYMBOLS is defined.
|
|
*/
|
|
MRB_API const char*
|
|
mrb_sym_name_len(mrb_state *mrb, mrb_sym sym, mrb_int *lenp)
|
|
{
|
|
#ifdef MRB_USE_ALL_SYMBOLS
|
|
return sym2name_len(mrb, sym, NULL, lenp);
|
|
#else
|
|
return sym2name_len(mrb, sym, mrb->symbuf, lenp);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
mrb_free_symtbl(mrb_state *mrb)
|
|
{
|
|
/* Free symbol string pool chunks */
|
|
struct sym_pool_chunk *chunk = (struct sym_pool_chunk*)mrb->sym_pool;
|
|
while (chunk) {
|
|
struct sym_pool_chunk *next = chunk->next;
|
|
mrb_free(mrb, chunk);
|
|
chunk = next;
|
|
}
|
|
mrb->sym_pool = NULL;
|
|
|
|
mrb_free(mrb, (void*)mrb->symtbl);
|
|
|
|
/* Free hash table if allocated */
|
|
if (mrb->symhash) {
|
|
mrb_free(mrb, mrb->symhash->symlink);
|
|
mrb_free(mrb, mrb->symhash);
|
|
mrb->symhash = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
mrb_init_symtbl(mrb_state *mrb)
|
|
{
|
|
/* Initialize in linear mode - hash table allocated on demand */
|
|
mrb->symhash = NULL;
|
|
mrb->sym_pool = NULL;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* Document-class: Symbol
|
|
*
|
|
* `Symbol` objects represent names and some strings
|
|
* inside the Ruby
|
|
* interpreter. They are generated using the `:name` and
|
|
* `:"string"` literals
|
|
* syntax, and by the various `to_sym` methods. The same
|
|
* `Symbol` object will be created for a given name or string
|
|
* for the duration of a program's execution, regardless of the context
|
|
* or meaning of that name. Thus if `Fred` is a constant in
|
|
* one context, a method in another, and a class in a third, the
|
|
* `Symbol` `:Fred` will be the same object in
|
|
* all three contexts.
|
|
*
|
|
* module One
|
|
* class Fred
|
|
* end
|
|
* $f1 = :Fred
|
|
* end
|
|
* module Two
|
|
* Fred = 1
|
|
* $f2 = :Fred
|
|
* end
|
|
* def Fred()
|
|
* end
|
|
* $f3 = :Fred
|
|
* $f1.object_id #=> 2514190
|
|
* $f2.object_id #=> 2514190
|
|
* $f3.object_id #=> 2514190
|
|
*
|
|
*/
|
|
|
|
/* 15.2.11.3.2 */
|
|
/* 15.2.11.3.3 */
|
|
/*
|
|
* call-seq:
|
|
* sym.to_s -> string
|
|
*
|
|
* Returns the name or string corresponding to *sym*.
|
|
*
|
|
* :fred.to_s #=> "fred"
|
|
*/
|
|
static mrb_value
|
|
sym_to_s(mrb_state *mrb, mrb_value sym)
|
|
{
|
|
return mrb_sym_str(mrb, mrb_symbol(sym));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.name -> string
|
|
*
|
|
* Returns the name or string corresponding to *sym*. Unlike #to_s, the
|
|
* returned string is frozen.
|
|
*
|
|
* :fred.name #=> "fred"
|
|
* :fred.name.frozen? #=> true
|
|
*/
|
|
static mrb_value
|
|
sym_name(mrb_state *mrb, mrb_value vsym)
|
|
{
|
|
mrb_sym sym = mrb_symbol(vsym);
|
|
mrb_int len;
|
|
const char *name = mrb_sym_name_len(mrb, sym, &len);
|
|
|
|
mrb_assert(name != NULL);
|
|
if (SYMBOL_INLINE_P(sym)) {
|
|
return mrb_str_new_frozen(mrb, name, len);
|
|
}
|
|
return mrb_str_new_static_frozen(mrb, name, len);
|
|
}
|
|
|
|
/* 15.2.11.3.4 */
|
|
/*
|
|
* Document-method: Symbol#to_sym
|
|
*
|
|
* call-seq:
|
|
* sym.to_sym -> sym
|
|
* sym.intern -> sym
|
|
*
|
|
* In general, `to_sym` returns the `Symbol` corresponding
|
|
* to an object. As *sym* is already a symbol, `self` is returned
|
|
* in this case.
|
|
*/
|
|
|
|
/* 15.2.11.3.5(x) */
|
|
/*
|
|
* call-seq:
|
|
* sym.inspect -> string
|
|
*
|
|
* Returns the representation of *sym* as a symbol literal.
|
|
*
|
|
* :fred.inspect #=> ":fred"
|
|
*/
|
|
|
|
#if __STDC__
|
|
# define SIGN_EXTEND_CHAR(c) ((signed char)(c))
|
|
#else /* not __STDC__ */
|
|
/* As in Harbison and Steele. */
|
|
# define SIGN_EXTEND_CHAR(c) ((((unsigned char)(c)) ^ 128) - 128)
|
|
#endif
|
|
#define is_identchar(c) (SIGN_EXTEND_CHAR(c)!=-1&&(ISALNUM(c) || (c) == '_'))
|
|
|
|
static mrb_bool
|
|
is_special_global_name(const char* m)
|
|
{
|
|
switch (*m) {
|
|
case '~': case '*': case '$': case '?': case '!': case '@':
|
|
case '/': case '\\': case ';': case ',': case '.': case '=':
|
|
case ':': case '<': case '>': case '\"':
|
|
case '&': case '`': case '\'': case '+':
|
|
case '0':
|
|
m++;
|
|
break;
|
|
case '-':
|
|
m++;
|
|
if (is_identchar(*m)) m += 1;
|
|
break;
|
|
default:
|
|
if (!ISDIGIT(*m)) return FALSE;
|
|
do m++; while (ISDIGIT(*m));
|
|
break;
|
|
}
|
|
return !*m;
|
|
}
|
|
|
|
static mrb_bool
|
|
symname_p(const char *name)
|
|
{
|
|
const char *m = name;
|
|
mrb_bool localid = FALSE;
|
|
|
|
if (!m) return FALSE;
|
|
switch (*m) {
|
|
case '\0':
|
|
return FALSE;
|
|
|
|
case '$':
|
|
if (is_special_global_name(++m)) return TRUE;
|
|
goto id;
|
|
|
|
case '@':
|
|
if (*++m == '@') ++m;
|
|
goto id;
|
|
|
|
case '<':
|
|
switch (*++m) {
|
|
case '<': ++m; break;
|
|
case '=': if (*++m == '>') m++; break;
|
|
default: break;
|
|
}
|
|
break;
|
|
|
|
case '>':
|
|
switch (*++m) {
|
|
case '>': case '=': ++m; break;
|
|
default: break;
|
|
}
|
|
break;
|
|
|
|
case '=':
|
|
switch (*++m) {
|
|
case '~': m++; break;
|
|
case '=': if (*++m == '=') m++; break;
|
|
default: return FALSE;
|
|
}
|
|
break;
|
|
|
|
case '*':
|
|
if (*++m == '*') m++;
|
|
break;
|
|
case '!':
|
|
switch (*++m) {
|
|
case '=': case '~': m++;
|
|
}
|
|
break;
|
|
case '+': case '-':
|
|
if (*++m == '@') m++;
|
|
break;
|
|
case '|':
|
|
if (*++m == '|') m++;
|
|
break;
|
|
case '&':
|
|
if (*++m == '&') m++;
|
|
break;
|
|
|
|
case '^': case '/': case '%': case '~': case '`':
|
|
m++;
|
|
break;
|
|
|
|
case '[':
|
|
if (*++m != ']') return FALSE;
|
|
if (*++m == '=') m++;
|
|
break;
|
|
|
|
default:
|
|
localid = !ISUPPER(*m);
|
|
id:
|
|
if (*m != '_' && !ISALPHA(*m)) return FALSE;
|
|
while (is_identchar(*m)) m += 1;
|
|
if (localid) {
|
|
switch (*m) {
|
|
case '!': case '?': case '=': m++;
|
|
default: break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return *m ? FALSE : TRUE;
|
|
}
|
|
|
|
static mrb_value
|
|
sym_inspect(mrb_state *mrb, mrb_value sym)
|
|
{
|
|
mrb_sym id = mrb_symbol(sym);
|
|
mrb_int len;
|
|
const char *name = mrb_sym_name_len(mrb, id, &len);
|
|
mrb_value str = mrb_str_new(mrb, NULL, len+1);
|
|
char *sp = RSTRING_PTR(str);
|
|
|
|
sp[0] = ':';
|
|
memcpy(sp+1, name, len);
|
|
mrb_assert_int_fit(mrb_int, len, size_t, SIZE_MAX);
|
|
if (!symname_p(name) || strlen(name) != (size_t)len) {
|
|
str = mrb_str_inspect(mrb, str);
|
|
sp = RSTRING_PTR(str);
|
|
sp[0] = ':';
|
|
sp[1] = '"';
|
|
}
|
|
#ifdef MRB_UTF8_STRING
|
|
if (SYMBOL_INLINE_P(id)) RSTR_SET_ASCII_FLAG(mrb_str_ptr(str));
|
|
#endif
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* Converts a symbol to an mruby string value.
|
|
*
|
|
* mrb: The mruby state.
|
|
* sym: The symbol to convert.
|
|
*
|
|
* Returns the mruby string value corresponding to the symbol.
|
|
* If the symbol is an inline symbol, a new string is created.
|
|
* Otherwise, a static string (sharing the symbol's name buffer) is returned.
|
|
* Returns an undefined value if the symbol is invalid (though this should not happen).
|
|
*/
|
|
MRB_API mrb_value
|
|
mrb_sym_str(mrb_state *mrb, mrb_sym sym)
|
|
{
|
|
mrb_int len;
|
|
const char *name = mrb_sym_name_len(mrb, sym, &len);
|
|
|
|
if (!name) return mrb_undef_value(); /* can't happen */
|
|
if (SYMBOL_INLINE_P(sym)) {
|
|
mrb_value str = mrb_str_new(mrb, name, len);
|
|
RSTR_SET_ASCII_FLAG(mrb_str_ptr(str));
|
|
return str;
|
|
}
|
|
return mrb_str_new_static(mrb, name, len);
|
|
}
|
|
|
|
static const char*
|
|
sym_cstr(mrb_state *mrb, mrb_sym sym, mrb_bool dump)
|
|
{
|
|
mrb_int len;
|
|
const char *name = mrb_sym_name_len(mrb, sym, &len);
|
|
|
|
if (!name) return NULL;
|
|
if (strlen(name) == (size_t)len && (!dump || symname_p(name))) {
|
|
return name;
|
|
}
|
|
else {
|
|
mrb_value str = mrb_str_new_static(mrb, name, len);
|
|
str = mrb_str_dump(mrb, str);
|
|
return RSTRING_PTR(str);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Retrieves the C string representation of a symbol's name.
|
|
*
|
|
* mrb: The mruby state.
|
|
* sym: The symbol to retrieve the name for.
|
|
*
|
|
* Returns a pointer to the C string representing the symbol's name.
|
|
* Returns NULL if the symbol is invalid.
|
|
* If the symbol's name contains null bytes or is not a valid identifier
|
|
* for direct use (based on internal checks in sym_cstr), this function
|
|
* might return a "dumped" (quoted and escaped) version of the name.
|
|
*/
|
|
MRB_API const char*
|
|
mrb_sym_name(mrb_state *mrb, mrb_sym sym)
|
|
{
|
|
return sym_cstr(mrb, sym, FALSE);
|
|
}
|
|
|
|
/*
|
|
* Retrieves the C string representation of a symbol's name, suitable for dumping.
|
|
* This version is intended for producing a string that can be safely outputted,
|
|
* for example, in debugging or serialization contexts. It may quote or escape
|
|
* the symbol name if it's not a simple identifier.
|
|
*
|
|
* mrb: The mruby state.
|
|
* sym: The symbol to retrieve the dump name for.
|
|
*
|
|
* Returns a pointer to the C string representing the symbol's name for dumping.
|
|
* Returns NULL if the symbol is invalid.
|
|
*/
|
|
MRB_API const char*
|
|
mrb_sym_dump(mrb_state *mrb, mrb_sym sym)
|
|
{
|
|
return sym_cstr(mrb, sym, TRUE);
|
|
}
|
|
|
|
#define lesser(a,b) (((a)>(b))?(b):(a))
|
|
|
|
static mrb_value
|
|
sym_cmp(mrb_state *mrb, mrb_value s1)
|
|
{
|
|
mrb_value s2 = mrb_get_arg1(mrb);
|
|
mrb_sym sym1, sym2;
|
|
|
|
if (!mrb_symbol_p(s2)) return mrb_nil_value();
|
|
sym1 = mrb_symbol(s1);
|
|
sym2 = mrb_symbol(s2);
|
|
if (sym1 == sym2) return mrb_fixnum_value(0);
|
|
else {
|
|
const char *p1, *p2;
|
|
int retval;
|
|
mrb_int len, len1, len2;
|
|
char buf1[8], buf2[8];
|
|
|
|
p1 = sym2name_len(mrb, sym1, buf1, &len1);
|
|
p2 = sym2name_len(mrb, sym2, buf2, &len2);
|
|
len = lesser(len1, len2);
|
|
retval = memcmp(p1, p2, len);
|
|
if (retval == 0) {
|
|
if (len1 == len2) return mrb_fixnum_value(0);
|
|
if (len1 > len2) return mrb_fixnum_value(1);
|
|
return mrb_fixnum_value(-1);
|
|
}
|
|
if (retval > 0) return mrb_fixnum_value(1);
|
|
return mrb_fixnum_value(-1);
|
|
}
|
|
}
|
|
#undef lesser
|
|
|
|
/* ---------------------------*/
|
|
#ifndef MRB_NO_PRESYM
|
|
#define SYMBOL_ROM_MT_SIZE 6
|
|
static struct {
|
|
union mt_ptr vals[SYMBOL_ROM_MT_SIZE];
|
|
mrb_sym keys[SYMBOL_ROM_MT_SIZE];
|
|
} symbol_rom_data = {
|
|
.vals = {
|
|
{ .func = sym_to_s },
|
|
{ .func = sym_name },
|
|
{ .func = mrb_obj_itself },
|
|
{ .func = sym_inspect },
|
|
{ .func = sym_cmp },
|
|
{ .func = mrb_obj_equal_m },
|
|
},
|
|
.keys = {
|
|
MT_KEY(MRB_SYM(to_s), MT_FUNC|MT_NOARG|MT_PUBLIC),
|
|
MT_KEY(MRB_SYM(name), MT_FUNC|MT_NOARG|MT_PUBLIC),
|
|
MT_KEY(MRB_SYM(to_sym), MT_FUNC|MT_NOARG|MT_PUBLIC),
|
|
MT_KEY(MRB_SYM(inspect), MT_FUNC|MT_NOARG|MT_PUBLIC),
|
|
MT_KEY(MRB_OPSYM(cmp), MT_FUNC|MT_PUBLIC),
|
|
MT_KEY(MRB_OPSYM(eq), MT_FUNC|MT_PUBLIC),
|
|
}
|
|
};
|
|
static mt_tbl symbol_rom_mt = {
|
|
SYMBOL_ROM_MT_SIZE, SYMBOL_ROM_MT_SIZE,
|
|
(union mt_ptr*)&symbol_rom_data, NULL
|
|
};
|
|
#endif /* !MRB_NO_PRESYM */
|
|
|
|
void
|
|
mrb_init_symbol(mrb_state *mrb)
|
|
{
|
|
struct RClass *sym;
|
|
|
|
mrb->symbol_class = sym = mrb_define_class_id(mrb, MRB_SYM(Symbol), mrb->object_class); /* 15.2.11 */
|
|
MRB_SET_INSTANCE_TT(sym, MRB_TT_SYMBOL);
|
|
mrb_undef_class_method_id(mrb, sym, MRB_SYM(new));
|
|
|
|
#ifndef MRB_NO_PRESYM
|
|
mrb_mt_init_rom(sym, &symbol_rom_mt);
|
|
#else
|
|
mrb_define_method_id(mrb, sym, MRB_SYM(to_s), sym_to_s, MRB_ARGS_NONE()); /* 15.2.11.3.3 */
|
|
mrb_define_method_id(mrb, sym, MRB_SYM(name), sym_name, MRB_ARGS_NONE());
|
|
mrb_define_method_id(mrb, sym, MRB_SYM(to_sym), mrb_obj_itself, MRB_ARGS_NONE()); /* 15.2.11.3.4 */
|
|
mrb_define_method_id(mrb, sym, MRB_SYM(inspect), sym_inspect, MRB_ARGS_NONE()); /* 15.2.11.3.5(x) */
|
|
mrb_define_method_id(mrb, sym, MRB_OPSYM(cmp), sym_cmp, MRB_ARGS_REQ(1));
|
|
mrb_define_method_id(mrb, sym, MRB_OPSYM(eq), mrb_obj_equal_m, MRB_ARGS_REQ(1));
|
|
#endif
|
|
}
|