├── CODE_OF_CONDUCT ├── LICENSE ├── README.md ├── Rakefile ├── examples ├── counter.rb ├── mol.rb ├── pid.rb └── random.rb ├── ext └── viva │ ├── core.c │ └── extconf.rb ├── lib ├── viva.rb └── viva │ └── version.rb ├── spec └── viva_spec.rb └── viva.gemspec /CODE_OF_CONDUCT: -------------------------------------------------------------------------------- 1 | ,---. ,---.-./`) ,---. .--. ____ .-'''-. .--. .--. ____ ,---. .--. 2 | | \ / \ .-.')| \ | | .' __ `. / _ \| |_ | | .' __ `.| \ | | 3 | | , \/ , / `-' \| , \ | |/ ' \ \ (`' )/`--'| _( )_ | |/ ' \ \ , \ | | 4 | | |\_ /| |`-'`"`| |\_ \| ||___| / |(_ o _). |(_ o _) | ||___| / | |\_ \| | 5 | | _( )_/ | |.---. | _( )_\ | _.-` | (_,_). '. | (_,_) \ | | _.-` | _( )_\ | 6 | | (_ o _) | || | | (_ o _) |.' _ |.---. \ :| |/ \| |.' _ | (_ o _) | 7 | | (_,_) | || | | (_,_)\ || _( )_ |\ `-' || ' /\ ` || _( )_ | (_,_)\ | 8 | | | | || | | | | |\ (_ o _) / \ / | / \ |\ (_ o _) / | | | 9 | '--' '--''---' '--' '--' '.(_,_).' `-...-' `---' `---` '.(_,_).''--' '--' 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION, AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | ## What's this? 6 | 7 | **Viva** is a little C extension that exposes the **vi**rtual **va**riable API to Ruby land for science and the lulz. 8 | 9 | ### What's that? 10 | 11 | Contrary to popular misconception, many of Ruby's global variables are in fact not. Consider the case of `$~` (known to [`English`](http://ruby-doc.org/stdlib-2.3.0/libdoc/English/rdoc/English.html) speakers as `$LAST_MATCH_INFO`): 12 | 13 | ```ruby 14 | def foo 15 | 'foo'.match /\w+/ and p $~ 16 | end 17 | 18 | 'bar'.match /\w+/ 19 | 20 | p $~ # "bar" from the current scope 21 | foo # "foo" from #foo's scope 22 | p $~ # "bar" again 23 | ``` 24 | 25 | It's evident that something tricksy is going on here; if this allegedly "global" variable doesn't maintain its value across scopes, whence does it come? I'll spare you the gory details: it's defined in terms of [a pair of C functions](https://git.io/vzwAN). Referring to `$~` will invoke `match_getter()` to obtain a value and *assigning to `$~`* will cause the provided `MatchData` instance to be used for `$~` and all of its descendants (of which there are [potentially thousands](https://git.io/vzwxR)). 26 | 27 | #### Cool story, bro. 28 | 29 | That's all well and good, yeah? "vvars" are scoped and pretty much do The Right Thing™. What's more, there's nothing stopping you from digging in and defining your own. But who wants to write C? [Bestest](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=4&cad=rja&uact=8&ved=0ahUKEwjqlvHc4L_KAhVF4iYKHRAZAxMQFggzMAM&url=http%3A%2F%2Fwww.merriam-webster.com%2Fdictionary%2Fbestest&usg=AFQjCNF5NASit_I0DsFXBB2l4baH0n7nFQ&bvm=bv.112454388,d.eWE) of all would be if we could define virtual getters and setters with Ruby callables, and so I've made that a thing. 30 | 31 | ### La raison d'être 32 | 33 | There's an old shell variable in Zsh—I know it's in Bash, it's probably in Zsh—called `RANDOM`, and the tin's not lying: 34 | 35 | ```bash 36 | $ echo $RANDOM $RANDOM $RANDOM 37 | 8784 15644 1499 38 | ``` 39 | 40 | [How neat is that?](http://i.imgur.com/SCRFkTZ.png) Let's give Ruby a `$RANDOM` that's neater still: 41 | 42 | ```ruby 43 | require 'viva' 44 | 45 | max = 32768 # Be like Bash... for now. 46 | getter = -> { rand max } 47 | setter = -> spec { max = spec } 48 | 49 | Viva.define :RANDOM, getter, setter 50 | 51 | p Array.new(4) { $RANDOM } 52 | # => [31791, 5045, 21337, 14134] 53 | 54 | $RANDOM = 1..6 55 | 56 | p Array.new(4) { $RANDOM } 57 | # => [3, 5, 6, 2] 58 | ``` 59 | 60 | Bash's offering looks positively static in comparison. ¡Viva la Rubylución! 61 | 62 | ## Usage 63 | 64 | `Viva.define` is the primary interface. It accepts exactly three arguments: a name for the virtual variable to be defined and two callables (instances of `Method` or `Proc`) to be used as the getter and setter thereof, respectively. 65 | 66 | The name is coerced via `Kernel#String`; preceded with a `'$'`; and subjected to the rules for global identifiers, with the additional stipulation that numeric names may only be between `-9` and `0`, inclusively. This constraint is informed by the parser rather than enforced by the virtual variable API: `$-10` and below are outright syntax errors, and assignment to the positives is explicitly forbidden. We can *refer* to them just fine, but they're "super-virtual" and will, at present, simply ignore any efforts to tailor their behavior to one's wily whim. I consider the inability to use them to be a :bug: in **Viva**. 67 | 68 | **TL;DR;SMAT**: 69 | 70 | valid |invalid 71 | :-----:|:-----: 72 | `0` |`1` 73 | `-4` |`-10` 74 | `"$"` |`" "` 75 | `"-_"` |`"_-"` 76 | `Viva` |`nil` 77 | `:*` |`:^` 78 | 79 | The getter can be defined to take an argument, which will be the "actual" value of the requested variable. Likewise, the setter receives the value that was assigned. The point, of course, is that you're free to do with these values what you will. 80 | 81 | Invoking `Viva.define` with a falsy value instead of a callable will undefine it and restore that operation to its regularly scheduled programming (bog-standard retrieval and assignment); regrettably, this holds true for variables which previously had some special meaning (pretty much everything in `English`). This is another :bug:. 82 | 83 | `define_getter` and `define_setter` are convenience methods that take blocks and leave their better halves in place: 84 | 85 | ```ruby 86 | Viva.define_setter('$') do |pid| 87 | p "Someone wanted our PID to be #{pid}." 88 | end 89 | 90 | %w[$ PID PROCESS_ID].each do |name| 91 | Viva.define_getter(name) { 1337 } 92 | end 93 | 94 | $$ = 42 # => "Someone wanted our PID to be 42." 95 | p $$ # => 1337 96 | ``` 97 | 98 | Just for laughs, **Viva** provides a bit of syntactic sugalt for defining both accessors simultaneously using "two blocks": 99 | 100 | ```ruby 101 | Viva(:MoL).--> v { v * 2 } { |v| v * 7 } 102 | 103 | $MoL = 3 104 | p $MoL # => 42 105 | ``` 106 | 107 | That you have to put the parameter in different places makes it look pretty silly, which I argue is a feature. 108 | 109 | Finally, there's `undef_getter` and `undef_setter` which, while perfectly self-explanatory, go a long way toward revealing what's actually going on here: 110 | 111 | ```ruby 112 | def undef_getter var 113 | Getters.delete "$#{var}" 114 | end 115 | ``` 116 | 117 | No effort has been made to conceal the internals from the user. This one's for you, [`FrozenCore`](https://youtu.be/SBdqCYKWISU?t=463). 118 | 119 | ### FQAs 120 | 121 | * Is this real life? 122 | 123 | Likelier than not, but perhaps one of the solipsists has the right of it. 124 | 125 | * Why is this happening? 126 | 127 | Some men just want to watch the world-writable variables burn with the glory of a thousand suns. If only I could be so grossly incandescent... 128 | 129 | * Should I use this? 130 | 131 | I'm inclined to think [_why would've liked it](http://favstar.fm/users/_why/status/1231698950); your team are liable to [respond less positively](https://www.youtube.com/watch?v=H07zYvkNYL8). Horses for courses and the like. 132 | 133 | ### Contributing 134 | 135 | You are strongly but gently advised to reconsider. If you simply won't be deterred, may the fork be with you. 136 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | Rake::TestTask.new :spec do |t| 4 | t.test_files = ['spec/viva_spec.rb'] 5 | end 6 | 7 | task :default do 8 | IO.new(0).puts < { c += 1 } { |v| c = v } 5 | 6 | p Array.new(3) { $COUNTER } # => [0, 1, 2] 7 | $COUNTER = 10 8 | p Array.new(3) { $COUNTER } # => [11, 12, 13] 9 | -------------------------------------------------------------------------------- /examples/mol.rb: -------------------------------------------------------------------------------- 1 | require 'viva' 2 | 3 | Viva(:MoL).--> v { v * 2 } { |v| v * 7 } 4 | 5 | $MoL = 3 6 | p $MoL # => 42 7 | -------------------------------------------------------------------------------- /examples/pid.rb: -------------------------------------------------------------------------------- 1 | require 'viva' 2 | 3 | Viva.define_setter('$') do |pid| 4 | p "Someone wanted our PID to be #{pid}." 5 | end 6 | 7 | %w[$ PID PROCESS_ID].each do |name| 8 | Viva.define_getter(name) { 1337 } 9 | end 10 | 11 | $$ = 42 # => "Someone wanted our PID to be 42." 12 | p $$ # => 1337 13 | -------------------------------------------------------------------------------- /examples/random.rb: -------------------------------------------------------------------------------- 1 | require 'viva' 2 | 3 | max = 32768 4 | getter = -> { rand max } 5 | setter = -> spec { max = spec } 6 | 7 | Viva.define :RANDOM, getter, setter 8 | 9 | p Array.new(4) { $RANDOM } 10 | $RANDOM = 1..6 11 | p Array.new(4) { $RANDOM } 12 | -------------------------------------------------------------------------------- /ext/viva/core.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static VALUE mViva, getters, setters; 4 | static ID id_call; 5 | 6 | /* `struct rb_global_variable` doesn't get exported from variable.c, but this 7 | * should suffice to have everything in the right place when we go looking. */ 8 | struct gvar { int _, __; void *val; }; 9 | 10 | static VALUE 11 | viva_getter(ID id, void *data) 12 | { 13 | VALUE getter = rb_hash_aref(getters, rb_id2str(id)); 14 | VALUE val = data ? data : Qnil; /* the old value, if any */ 15 | 16 | if (RTEST(getter)) 17 | return rb_funcall(getter, id_call, abs(rb_proc_arity(getter)), val); 18 | return val; 19 | } 20 | 21 | static void 22 | viva_setter(VALUE val, ID id, void *_, struct gvar *var) 23 | { 24 | VALUE setter = rb_hash_aref(setters, rb_id2str(id)); 25 | 26 | var->val = RTEST(setter) ? rb_funcall(setter, id_call, 1, val) : val; 27 | } 28 | 29 | #define error_msg "`%s`is not allowed as a virtual variable name" 30 | 31 | static VALUE 32 | viva_define(VALUE self, VALUE var, VALUE getter, VALUE setter) 33 | { 34 | char *str; 35 | var = rb_String(var); 36 | rb_str_update(var, 0, 0, rb_str_new("$", 1)); 37 | str = StringValueCStr(var); 38 | 39 | /* Reject invalid global identifiers and fully digital ones other than $0, 40 | * since the former would be syntax errors and the latter inaccessible. */ 41 | if (!rb_is_global_id(rb_intern(str)) || atoi(str + 1) > 0) 42 | rb_raise(rb_eNameError, error_msg, str); 43 | 44 | rb_hash_aset(getters, var, getter); 45 | rb_hash_aset(setters, var, setter); 46 | rb_define_virtual_variable(str, viva_getter, viva_setter); 47 | 48 | return rb_str_intern(var); 49 | } 50 | 51 | void 52 | Init_core() 53 | { 54 | mViva = rb_define_module("Viva"); 55 | id_call = rb_intern("call"); 56 | 57 | rb_define_const(mViva, "Getters", getters = rb_hash_new()); 58 | rb_define_const(mViva, "Setters", setters = rb_hash_new()); 59 | rb_define_singleton_method(mViva, "define", viva_define, 3); 60 | } 61 | -------------------------------------------------------------------------------- /ext/viva/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | 3 | create_makefile 'viva/core' 4 | -------------------------------------------------------------------------------- /lib/viva.rb: -------------------------------------------------------------------------------- 1 | require 'viva/core' 2 | 3 | module Viva 4 | module_function 5 | 6 | def define_getter var 7 | define var, proc, Setters["$#{var}"] 8 | end 9 | 10 | def define_setter var 11 | define var, Getters["$#{var}"], proc 12 | end 13 | 14 | def undef_getter var 15 | Getters.delete "$#{var}" 16 | end 17 | 18 | def undef_setter var 19 | Setters.delete "$#{var}" 20 | end 21 | 22 | def - getter, &setter 23 | define @var, getter, setter 24 | end 25 | end 26 | 27 | module Kernel 28 | module_function def Viva var 29 | Viva.tap { |v| v.instance_variable_set :@var, var } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/viva/version.rb: -------------------------------------------------------------------------------- 1 | module Viva 2 | VERSION = '0.42.1337' 3 | end 4 | -------------------------------------------------------------------------------- /spec/viva_spec.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'minitest/pride' 3 | require 'viva' 4 | 5 | describe Viva do 6 | describe '.define' do 7 | describe 'when given a valid virtual variable name' do 8 | it 'should return its name as a globalized Symbol' do 9 | [0, -4, '$', '-_', Viva, :*].each do |name| 10 | expected = :"$#{name}" 11 | Viva.define(name, nil, nil).must_equal expected 12 | end 13 | end 14 | end 15 | 16 | describe 'when given an invalid virtual variable name' do 17 | it 'should complain and refrain (inaccessible < useless)' do 18 | [1, -10, ' ', '_-', nil, :^,].each do |name| 19 | -> { Viva.define name, nil, nil }.must_raise NameError 20 | end 21 | end 22 | end 23 | 24 | # NOTE: These next two specs are essentially identical. 25 | # How does one verify that nonexistent code didn't run? 26 | 27 | it 'should be able to create a getter with no setter' do 28 | Viva.define :a, -> actual { actual * 2 }, nil 29 | $a = 21 30 | $a.must_equal 42 31 | end 32 | 33 | it 'should be able to create a setter with no getter' do 34 | Viva.define :b, nil, -> actual { actual * 2 } 35 | $b = 21 36 | $b.must_equal 42 37 | end 38 | end 39 | 40 | # This more or less runs the thing through its paces, so let's stop here. 41 | 42 | it "should be able to emulate GCC's __COUNTER__ (with bonus reset)" do 43 | c = -1 44 | Viva(:COUNTER).--> { c += 1 } { |v| c = v } 45 | 46 | Array.new(3) { $COUNTER }.must_equal [0, 1, 2] 47 | $COUNTER = 10 48 | Array.new(3) { $COUNTER }.must_equal [11, 12, 13] 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /viva.gemspec: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path '../lib', __FILE__ 2 | require 'viva/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'viva' 6 | s.version = Viva::VERSION 7 | s.author = 'D.E. Akers' 8 | s.email = '0x0dea@gmail.com' 9 | 10 | s.summary = 'Viva exposes the virtual variable API to Ruby land.' 11 | s.homepage = 'https://github.com/0x0dea/viva' 12 | s.license = 'WTFPL' 13 | 14 | s.files = `git ls-files`.split 15 | s.extensions = %w[ext/viva/extconf.rb Rakefile] 16 | end 17 | --------------------------------------------------------------------------------