Skip to content

Instantly share code, notes, and snippets.

@txus
Created August 26, 2012 20:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save txus/3483372 to your computer and use it in GitHub Desktop.
Save txus/3483372 to your computer and use it in GitHub Desktop.
Revolver - evolving ruby code.

Revolver

Evolving Ruby code

You write the tests, Revolver writes the code.

Inspired by conversations with @agnoster and a programming language written by TJ Holowaychuk (can't remember the repo!!)

Needs Rubinius!

Usage

$ gem install to_source
require 'revolver'

# Revolver, write a program that returns a number between 30 and 50, GO!
revolver = Revolver.new do |result|
  result.is_a?(Fixnum) && result > 30 && result < 50
end

revolver.program
revolver.report # prints the resulting program.
require_relative 'revolver'
# Ask revolver to write a program that calls nil.inspect().
revolver = Revolver.new do |result|
result.is_a?(String) && result == "nil"
end
revolver.program
revolver.report
#!/bin/env rbx
require 'to_source'
A = Rubinius::AST
class Random
ALLOWED_SYMBOL_CHARACTERS = Array('a'..'z') + Array('A'..'Z')
RANDOM_METHOD = Rubinius.ruby19? ? :sample : :choice
def self.string
Array.new(rand(50)) { rand(126).chr }.join
end
def self.symbol
Array.new(rand(50).next) { ALLOWED_SYMBOL_CHARACTERS.send RANDOM_METHOD }.join.to_sym
end
def self.fixnum
rand(100)
end
def self.float
(rand(100) + 0.5)
end
def self.range
min, max = rand(50)
max = min + rand(50)
Range.new(min, max)
end
end
module A
Nodes = [
NilLiteral,
StringLiteral,
FixnumLiteral,
SendWithArguments,
]
module CoreType
def random_method
method = example.methods.sample
arity = example.method(method).arity
{
meth: method,
arity: arity
}
end
end
Nodes.each do |node|
node.send :include, CoreType
end
def self.random
Nodes.shuffle.first.random
end
class NilLiteral
def self.random
new(0)
end
def core_type
NilClass
end
def example
nil
end
end
class FixnumLiteral
def self.random
new(0, Random.fixnum)
end
def core_type
Fixnum
end
def example
@example ||= Random.fixnum
end
end
class StringLiteral
def self.random
new(0, Random.string)
end
def core_type
String
end
def example
@example ||= Random.string
end
end
class SendWithArguments
def self.random
receiver = A.random
return self.random if receiver.is_a?(SendWithArguments)
meth = receiver.random_method
method = meth[:meth]
arity = meth[:arity]
arguments = if arity == -1
[]
else
arity.times.map do |arg|
A.random
end
end
new(0, receiver, method, A::ActualArguments.new(arguments))
end
end
end
class Revolver
attr_reader :programs
MAX_DEPTH = 10
def initialize(&block)
@test = block
@program = nil
end
def program
while true
print '.'
nodes = []
max_depth = 0
while max_depth <= MAX_DEPTH
nodes << A.random
program = Program.new(nodes)
orig_stdout = $stdout
$stdout = File.new('/dev/null', 'w')
program.run
$stdout = orig_stdout
if program.valid? && @test.call(program.result)
result = program.result
@program = program
return
end
max_depth += 1
end
end
end
def report
puts "Program found:"
puts '-------'
puts @program.code
puts
end
end
class Program
attr_reader :result
def initialize(nodes)
@body = nodes
@valid = nil
@result = nil
end
def valid?
!!@valid
end
def code
@body.map do |node|
node.to_source
end.join("\n")
end
def run
cm = Rubinius::Compiler.compile_eval(code, binding.variables)
result = Rubinius.run_script(cm)
@valid = true
@result = result
rescue SyntaxError, Exception
@valid = false
end
end
# Example
#
# Let's ask revolver to write this program:
#
# nil.inspect
#
# revolver = Revolver.new do |result|
# result.is_a?(String) && result == "nil"
# end
#
# revolver.program
# revolver.report
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment