Skip to content

Instantly share code, notes, and snippets.

@haileys
Last active May 16, 2022 09:03
Show Gist options
  • Save haileys/4367567 to your computer and use it in GitHub Desktop.
Save haileys/4367567 to your computer and use it in GitHub Desktop.

References in Ruby

Just for fun, I decided to add references to Ruby.

I decided to use Perl's backslash syntax, so for example you'd write \foo to take a reference to the local variable foo. You can also take a reference to instance variables, class variables and globals.

I have attached a patch in this gist if you're interested in the implementation or you want to try it out on your own copy of Ruby.

This was just a fun little exercise for me - I don't expect that this would ever be merged in to Ruby itself.

def swap(a, b)
a.value, b.value = b.value, a.value
end
$foo = 123
swap \a, \$foo
# a == 123, $foo == nil
$foo = ":~)"
swap \a, \$foo
# a == ":~)", $foo == 123
class IO
def >>(ref)
ref.value = gets.chomp
end
end
$stdout << "What's your name? \n"
$stdin >> \name
$stdout << "Hello #{name}!\n"
# What's your name
# Hailey
# Hello Hailey!
diff --git a/common.mk b/common.mk
index 349aa9a..d044d76 100644
--- a/common.mk
+++ b/common.mk
@@ -73,6 +73,7 @@ COMMONOBJS = array.$(OBJEXT) \
regexec.$(OBJEXT) \
regparse.$(OBJEXT) \
regsyntax.$(OBJEXT) \
+ ref.$(OBJEXT) \
ruby.$(OBJEXT) \
safe.$(OBJEXT) \
signal.$(OBJEXT) \
@@ -712,6 +713,8 @@ regparse.$(OBJEXT): {$(VPATH)}regparse.c {$(VPATH)}regparse.h \
$(RUBY_H_INCLUDES)
regsyntax.$(OBJEXT): {$(VPATH)}regsyntax.c {$(VPATH)}regint.h \
{$(VPATH)}regenc.h {$(VPATH)}oniguruma.h $(RUBY_H_INCLUDES)
+ref.$(OBJEXT): {$(VPATH)}ref.c {$(VPATH)}ruby.h {$(VPATH)}vm_core.h \
+ {$(VPATH)}node.h {$(VPATH)}gc.h
ruby.$(OBJEXT): {$(VPATH)}ruby.c $(RUBY_H_INCLUDES) {$(VPATH)}util.h \
$(ENCODING_H_INCLUDES) {$(VPATH)}eval_intern.h $(VM_CORE_H_INCLUDES) \
{$(VPATH)}dln.h {$(VPATH)}internal.h
diff --git a/compile.c b/compile.c
index 3daa607..aebbab7 100644
--- a/compile.c
+++ b/compile.c
@@ -4517,6 +4517,14 @@ enum compile_array_type_t {
}
break;
}
+ case NODE_LVAR_REF:{
+ if (!poped) {
+ ID id = node->nd_vid;
+ int idx = iseq->local_iseq->local_size - get_local_var_idx(iseq, id);
+ ADD_INSN2(ret, nd_line(node), reflocal, INT2FIX(idx), INT2FIX(get_lvar_level(iseq)));
+ }
+ break;
+ }
case NODE_LVAR:{
if (!poped) {
ID id = node->nd_vid;
@@ -4527,6 +4535,17 @@ enum compile_array_type_t {
}
break;
}
+ case NODE_DVAR_REF:{
+ int lv, idx, ls;
+ if (!poped) {
+ idx = get_dyna_var_idx(iseq, node->nd_vid, &lv, &ls);
+ if (idx < 0) {
+ rb_bug("unknown dvar (%s)", rb_id2name(node->nd_vid));
+ }
+ ADD_INSN2(ret, nd_line(node), reflocal, INT2FIX(ls - idx), INT2FIX(lv));
+ }
+ break;
+ }
case NODE_DVAR:{
int lv, idx, ls;
debugi("nd_vid", node->nd_vid);
@@ -4555,6 +4574,12 @@ enum compile_array_type_t {
}
break;
}
+ case NODE_IVAR_REF:{
+ if(!poped) {
+ ADD_INSN1(ret, nd_line(node), refivar, ID2SYM(node->nd_vid));
+ }
+ break;
+ }
case NODE_CONST:{
debugi("nd_vid", node->nd_vid);
@@ -4584,6 +4609,12 @@ enum compile_array_type_t {
}
break;
}
+ case NODE_CVAR_REF:{
+ if (!poped) {
+ ADD_INSN1(ret, nd_line(node), refcvar, ID2SYM(node->nd_vid));
+ }
+ break;
+ }
case NODE_NTH_REF:{
if (!poped) {
ADD_INSN2(ret, nd_line(node), getspecial, INT2FIX(1) /* '~' */,
diff --git a/inits.c b/inits.c
index fe0aade..c39a039 100644
--- a/inits.c
+++ b/inits.c
@@ -61,5 +61,6 @@
CALL(Complex);
CALL(version);
CALL(vm_trace);
+ CALL(ref);
}
#undef CALL
diff --git a/insns.def b/insns.def
index c339180..1772607 100644
--- a/insns.def
+++ b/insns.def
@@ -249,6 +249,41 @@ setglobal
SET_GLOBAL((VALUE)entry, val);
}
+DEFINE_INSN
+reflocal
+(lindex_t idx, rb_num_t level)
+()
+(VALUE ref)
+{
+ int i, lev = (int)level;
+ VALUE env, *ep = GET_EP();
+
+ for(i = 0; i < lev; i++) {
+ ep = GET_PREV_EP(ep);
+ }
+
+ env = ep[1]; /* ep[1] holds the env val referencing the ep */
+ ref = rb_ref_local(env, idx);
+}
+
+DEFINE_INSN
+refivar
+(ID var)
+()
+(VALUE ref)
+{
+ ref = rb_ref_ivar(GET_SELF(), var);
+}
+
+DEFINE_INSN
+refcvar
+(ID var)
+()
+(VALUE ref)
+{
+ NODE *cref = rb_vm_get_cref(GET_ISEQ(), GET_EP());
+ ref = rb_ref_cvar(vm_get_cvar_base(cref, GET_CFP()), var);
+}
/**********************************************************/
/* deal with values */
diff --git a/internal.h b/internal.h
index 2f05f48..f3a0a56 100644
--- a/internal.h
+++ b/internal.h
@@ -250,6 +250,13 @@ struct rb_execarg {
VALUE rb_reg_compile(VALUE str, int options, const char *sourcefile, int sourceline);
VALUE rb_reg_check_preprocess(VALUE);
+/* ref.c */
+VALUE rb_ref_local(VALUE env, size_t index);
+VALUE rb_ref_ivar(VALUE self, ID var);
+VALUE rb_ref_cvar(VALUE self, ID var);
+struct rb_global_entry;
+VALUE rb_ref_global(struct rb_global_entry* ge);
+
/* signal.c */
int rb_get_next_signal(void);
int rb_sigaltstack_size(void);
diff --git a/node.h b/node.h
index 0918609..6e6b41f 100644
--- a/node.h
+++ b/node.h
@@ -112,16 +112,26 @@ enum node_type {
#define NODE_YIELD NODE_YIELD
NODE_LVAR,
#define NODE_LVAR NODE_LVAR
+ NODE_LVAR_REF,
+#define NODE_LVAR_REF NODE_LVAR_REF
NODE_DVAR,
#define NODE_DVAR NODE_DVAR
+ NODE_DVAR_REF,
+#define NODE_DVAR_REF NODE_DVAR_REF
NODE_GVAR,
#define NODE_GVAR NODE_GVAR
+ NODE_GVAR_REF,
+#define NODE_GVAR_REF NODE_GVAR_REF
NODE_IVAR,
#define NODE_IVAR NODE_IVAR
+ NODE_IVAR_REF,
+#define NODE_IVAR_REF NODE_IVAR_REF
NODE_CONST,
#define NODE_CONST NODE_CONST
NODE_CVAR,
#define NODE_CVAR NODE_CVAR
+ NODE_CVAR_REF,
+#define NODE_CVAR_REF NODE_CVAR_REF
NODE_NTH_REF,
#define NODE_NTH_REF NODE_NTH_REF
NODE_BACK_REF,
@@ -400,11 +410,16 @@ enum node_type {
#define NEW_OP_ASGN_OR(i,val) NEW_NODE(NODE_OP_ASGN_OR,i,val,0)
#define NEW_OP_ASGN_AND(i,val) NEW_NODE(NODE_OP_ASGN_AND,i,val,0)
#define NEW_GVAR(v) NEW_NODE(NODE_GVAR,v,0,rb_global_entry(v))
+#define NEW_GVAR_REF(v) NEW_NODE(NODE_LIT,rb_ref_global(rb_global_entry(v)),0,0)
#define NEW_LVAR(v) NEW_NODE(NODE_LVAR,v,0,0)
+#define NEW_LVAR_REF(v) NEW_NODE(NODE_LVAR_REF,v,0,0)
#define NEW_DVAR(v) NEW_NODE(NODE_DVAR,v,0,0)
+#define NEW_DVAR_REF(v) NEW_NODE(NODE_DVAR_REF,v,0,0)
#define NEW_IVAR(v) NEW_NODE(NODE_IVAR,v,0,0)
+#define NEW_IVAR_REF(v) NEW_NODE(NODE_IVAR_REF,v,0,0)
#define NEW_CONST(v) NEW_NODE(NODE_CONST,v,0,0)
#define NEW_CVAR(v) NEW_NODE(NODE_CVAR,v,0,0)
+#define NEW_CVAR_REF(v) NEW_NODE(NODE_CVAR_REF,v,0,0)
#define NEW_NTH_REF(n) NEW_NODE(NODE_NTH_REF,0,n,0)
#define NEW_BACK_REF(n) NEW_NODE(NODE_BACK_REF,0,n,0)
#define NEW_MATCH(c) NEW_NODE(NODE_MATCH,c,0,0)
diff --git a/parse.y b/parse.y
index e42cd9e..3db1e57 100644
--- a/parse.y
+++ b/parse.y
@@ -811,6 +811,7 @@ static void token_info_pop(struct parser_params*, const char *token);
%token tDSTAR "**arg"
%token tAMPER "&"
%token tLAMBDA "->"
+%token tREF "\\"
%token tSYMBEG tSTRING_BEG tXSTRING_BEG tREGEXP_BEG tWORDS_BEG tQWORDS_BEG tSYMBOLS_BEG tQSYMBOLS_BEG
%token tSTRING_DBEG tSTRING_DEND tSTRING_DVAR tSTRING_END tLAMBEG
@@ -2573,6 +2574,32 @@ primary : literal
| qsymbols
| var_ref
| backref
+ | '\\' tIVAR
+ {
+ $$ = NEW_IVAR_REF($2);
+ }
+ | '\\' tCVAR
+ {
+ $$ = NEW_CVAR_REF($2);
+ }
+ | '\\' tGVAR
+ {
+ $$ = NEW_GVAR_REF($2);
+ }
+ | '\\' tIDENTIFIER
+ {
+ if(dyna_in_block() && dvar_defined($2)) {
+ $$ = NEW_DVAR_REF($2);
+ } else {
+ /* if not a local variable, use this as an implicit definition */
+ if(!local_id($2)) {
+ dyna_var($2);
+ $$ = NEW_LVAR_REF($2);
+ } else {
+ $$ = NEW_LVAR_REF($2);
+ }
+ }
+ }
| tFID
{
/*%%%*/
diff --git a/ref.c b/ref.c
new file mode 100644
index 0000000..006c2e1
--- /dev/null
+++ b/ref.c
@@ -0,0 +1,198 @@
+#include "ruby/ruby.h"
+#include "vm_core.h"
+#include "node.h"
+#include "gc.h"
+
+VALUE rb_cRef;
+
+typedef enum {
+ REF_NONE,
+ REF_LOCAL,
+ REF_IVAR,
+ REF_CVAR,
+ REF_GVAR
+}
+rb_ref_type_t;
+
+typedef struct {
+ rb_ref_type_t type;
+ union {
+ struct {
+ VALUE env;
+ size_t index;
+ } local;
+ struct {
+ VALUE self;
+ ID var;
+ } ivar, cvar;
+ struct rb_global_entry *global;
+ } as;
+}
+rb_ref_t;
+
+static void
+ref_mark(void *ptr)
+{
+ rb_ref_t *ref = ptr;
+ RUBY_MARK_ENTER("ref");
+ if(ref) {
+ switch(ref->type) {
+ case REF_NONE:
+ break;
+ case REF_LOCAL:
+ RUBY_MARK_UNLESS_NULL(ref->as.local.env);
+ break;
+ case REF_IVAR:
+ case REF_CVAR:
+ RUBY_MARK_UNLESS_NULL(ref->as.ivar.self);
+ break;
+ case REF_GVAR:
+ break;
+ }
+ }
+ RUBY_MARK_LEAVE("ref");
+}
+
+static void
+ref_free(void* ptr)
+{
+ if(ptr) {
+ ruby_xfree(ptr);
+ }
+}
+
+static size_t
+ref_memsize(const void* ptr)
+{
+ return ptr ? sizeof(rb_ref_t) : 0;
+}
+
+static const rb_data_type_t
+ref_data_type = {
+ "ref",
+ {
+ ref_mark,
+ ref_free,
+ ref_memsize
+ }
+};
+
+static VALUE
+ref_alloc(VALUE klass)
+{
+ VALUE obj;
+ rb_ref_t* ref = NULL;
+ obj = TypedData_Make_Struct(klass, rb_ref_t, &ref_data_type, ref);
+ ref->type = REF_NONE;
+ return obj;
+}
+
+VALUE
+rb_ref_local(VALUE env, size_t idx)
+{
+ VALUE refv = ref_alloc(rb_cRef);
+ rb_ref_t* ref;
+ TypedData_Get_Struct(refv, rb_ref_t, &ref_data_type, ref);
+ ref->type = REF_LOCAL;
+ ref->as.local.env = env;
+ ref->as.local.index = idx;
+ return refv;
+}
+
+VALUE
+rb_ref_ivar(VALUE self, ID var)
+{
+ VALUE refv = ref_alloc(rb_cRef);
+ rb_ref_t* ref;
+ TypedData_Get_Struct(refv, rb_ref_t, &ref_data_type, ref);
+ ref->type = REF_IVAR;
+ ref->as.ivar.self = self;
+ ref->as.ivar.var = var;
+ return refv;
+}
+
+VALUE
+rb_ref_cvar(VALUE self, ID var)
+{
+ VALUE refv = ref_alloc(rb_cRef);
+ rb_ref_t* ref;
+ TypedData_Get_Struct(refv, rb_ref_t, &ref_data_type, ref);
+ ref->type = REF_CVAR;
+ ref->as.ivar.self = self;
+ ref->as.ivar.var = var;
+ return refv;
+}
+
+VALUE
+rb_ref_global(struct rb_global_entry* ge)
+{
+ VALUE refv = ref_alloc(rb_cRef);
+ rb_ref_t* ref;
+ TypedData_Get_Struct(refv, rb_ref_t, &ref_data_type, ref);
+ ref->type = REF_GVAR;
+ ref->as.global = ge;
+ return refv;
+}
+
+static VALUE*
+local_ref(rb_ref_t* ref)
+{
+ rb_env_t* env;
+ GetEnvPtr(ref->as.local.env, env);
+ return env->env + env->local_size - ref->as.local.index;
+}
+
+static VALUE
+ref_value(VALUE self)
+{
+ rb_ref_t* ref;
+ TypedData_Get_Struct(self, rb_ref_t, &ref_data_type, ref);
+ switch(ref->type) {
+ case REF_NONE:
+ rb_raise(rb_eTypeError, "Can't get value of uninitialized reference");
+ case REF_LOCAL:
+ return *local_ref(ref);
+ case REF_IVAR:
+ return rb_ivar_get(ref->as.ivar.self, ref->as.ivar.var);
+ case REF_CVAR:
+ return rb_cvar_get(ref->as.cvar.self, ref->as.cvar.var);
+ case REF_GVAR:
+ return rb_gvar_get(ref->as.global);
+ }
+ return Qundef;
+}
+
+static VALUE
+ref_value_set(VALUE self, VALUE val)
+{
+ rb_ref_t* ref;
+ TypedData_Get_Struct(self, rb_ref_t, &ref_data_type, ref);
+ switch(ref->type) {
+ case REF_NONE:
+ rb_raise(rb_eTypeError, "Can't get value of uninitialized reference");
+ break;
+ case REF_LOCAL:
+ *local_ref(ref) = val;
+ break;
+ case REF_IVAR:
+ rb_ivar_set(ref->as.ivar.self, ref->as.ivar.var, val);
+ break;
+ case REF_CVAR:
+ rb_cvar_set(ref->as.cvar.self, ref->as.cvar.var, val);
+ break;
+ case REF_GVAR:
+ rb_gvar_set(ref->as.global, val);
+ break;
+ }
+ return val;
+}
+
+void
+Init_ref()
+{
+ rb_cRef = rb_define_class("Ref", rb_cObject);
+ rb_define_alloc_func(rb_cRef, ref_alloc);
+ rb_define_method(rb_cRef, "value", ref_value, 0);
+ rb_define_method(rb_cRef, "value=", ref_value_set, 1);
+}
+
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment