Make your own ruby REPL from scratch

C.Dragon
4 min readJul 15, 2021

--

REPL stands as a read-execute-print loop or language shell. It’s a simple interactive environment that takes inputs and evaluates them.

For Rubyist, you probably very familiar with irb (Interactive Ruby Shell):

$ irb
2.6.3 :001 > puts 'hello world!'

Have you ever thought about how this is done? Base on curiosity, I decided to build my own REPL from scratch.

It’s Just an Infinite Loop

This is a straightforward REPL example. Surprisingly, it’s just an infinite loop.

The function gets is a build-in method in ruby to read the user input. The program will read your inputs and executes your code in each loop (line).

# main.rb#!/usr/bin/rubyloop do
p eval(gets) # simple run any ruby command and print
end

Try it out:

$ ./main.rb

If you run into a permission error, try to give the file full permission by

$ chmod 777 main.rb

YES, here you go! It’s not perfect, but it’s a good starting point. You create an interactive ruby shell!

Make it fancier

Of course, we would want to make it fancier! Here’s what we want:

  • Add greeting message when entering our REPL
  • exit command to easily get out of REPL
  • line number prefix on every line, just like the IRB interface.

With some refactor:

#!/usr/bin/rubyclass MyREPL
REPL_PROMPT = 'MyREPL>> '
def initialize
@line_length = 1
end
def run
before_loop
in_loop
after_loop
end
private def before_loop
print "Welcome to my REPL, please type your commands:\n"
end
def in_loop
loop do
print "[#{@line_length}] #{REPL_PROMPT}"
input = gets
if input == 'exit'
break
else
eval(input)
end
@line_length += 1
end
end
def after_loop
puts "Exit"
end
end
MyREPL.new.run

BOOM!

Always test your code

Writing tests for your programs is always a good habit.

$ mkdir myREPL
$ mv main.rb myREPL/main.rb
$ cd myREPL
$ gem install rspec
$ rspec --init

Not familiar with Rspec? check out rspec document for more details

It takes me quite a while to figure out a way to test it:

# spec/main_spec.rbdescribe '#run' do
def run(commands)
raw_output = nil
IO.popen("./main.rb", "r+") do |pipe|
commands.each do |command|
pipe.puts command
end
pipe.close_write
raw_output = pipe.gets(nil)
end
raw_output.split("\n")
end
it 'should execute commands' do
result = run(['puts 1 + 1', 'exit'])
expect(result).to match_array([
"Welcome to my REPL, please type your commands:",
"[1] MyREPL>> 2",
"[2] MyREPL>> "
])
end
end

LGTM 🚀

Standing on the shoulders of giants

If you are interested, you can try to extend your simple REPL to do more things. You may find out arrow-left and arrow-right keys actually not working as expected if you test it more. Also, our program doesn’t support multi-line commands. So there’s a lot more to be improved!

However, there’s also some existing decent REPL which you can easily build customization on.

RIPL is a lightweight ruby shell. It is highly customizable via plugins and supports commands. You can check out their document:

Here is an example to add your own custom method:

#!/usr/bin/env rubyrequire 'ripl'module Ripl::Commands::MyREPL
def print_hello
puts 'hello'
end
end
Ripl::Commands.include Ripl::Commands::MyREPLRipl.config[:prompt] = lambda {
"\[#{Ripl.shell.line.to_s}\] MyREPL> "
}
Ripl.start
ripl Shell

Try it and have fun!

References

All images were owned by the Auther.

--

--