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
endMyREPL.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
endRipl::Commands.include Ripl::Commands::MyREPLRipl.config[:prompt] = lambda {
"\[#{Ripl.shell.line.to_s}\] MyREPL> "
}Ripl.start
Try it and have fun!
References
All images were owned by the Auther.