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.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

C.Dragon
C.Dragon

Written by C.Dragon

Author of Baby Dragon LineBot

No responses yet

What are your thoughts?