Write a REPL in Ruby
Tags: pry, irb, repl, ruby
Published: 2016-12-12 09:20:15

---
title:       Write a REPL in Ruby
published:   true
description: Lets build a REPL!
cover_image: 
tags:        pry,irb,repl,ruby,console,terminal
---

[Pry](http://pryrepl.org/), [IRB](http://ruby-doc.org/stdlib-2.3.1/libdoc/irb/rdoc/IRB.html), and [RIPL](https://rubygems.org/gems/ripl) are popular Ruby REPLs, but, just for funsies, lets see what it takes to build our own! Code snippets are [here](https://gist.github.com/JoshCheek/baab86f3258ff98c468f3dbc032dc9c5).

----

Simple beginnings
------------------

A <strong>REPL</strong> must "<strong>R</strong>ead" input from the user, "<strong>E</strong>val"uate that input as code, "<strong>P</strong>rint" the result, and "<strong>L</strong>oop" back to the first step to do it all over again. So, lets start with those four steps.

![screenshot](https://cdn-images-1.medium.com/max/600/1*qWygoGudYFcFuPVMDUDKFw.png)


----


Persist local variables
-----------------------

The problem with our REPL at present is it forgets local variables ðŸ˜ Thatâ€™s because local variables are stored in "bindings" on the callstack. When we call `eval`, it makes a new binding to evaluate the code in, and when `eval` is finished, it pops that binding off the callstack discarding our variables. To retain the variables between invocations, we need to evaluate them in the same binding every time.

![screenshot](https://cdn-images-1.medium.com/max/600/1*XxwgW544hDGlwXRTJ17pHQ.png)


----


Readline input
--------------

Our input is... not good ðŸ˜œ But, Ruby ships with [`Readline`](https://ruby-doc.org/stdlib-2.3.3/libdoc/readline/rdoc/Readline.html), which is like `gets`, but adds a prompt, kill ring, fancy movement/editing, cycling through previous inputs, etc.

![animated screenshot](https://cdn-images-1.medium.com/max/600/1*hy59uAXv3wTGRhQMPQ8Eqw.gif)


----


Highlight output
----------------

Looks good, lets syntax highlight our output. [Rouge](https://rubygems.org/gems/rouge) is the way to go these days, but [CodeRay](https://rubygems.org/gems/coderay) has a simpler interface. We just pass it through CodeRay between inspecting and printing.

![screenshot](https://cdn-images-1.medium.com/max/600/1*SH_f_Dg9qTCtzNuBcN3mHg.png)


----


Handle EOF
----------

Currently, if we hit the end of the input stream (eg user presses `control-d`), we explode in `eval`. This is because `input` is `nil`. Lets stop the loop in this situation.

![screenshot](https://cdn-images-1.medium.com/max/600/1*BuCkA9WP0_gePukpFDM8dA.png)


----


Handle Errors
-------------

It would also be nice to handle errors in the input, so lets rescue and display them! The `\e[31m` is the [ANSI escape code](https://en.wikipedia.org/wiki/ANSI_escape_code) to turn the foreground red and the `\e[0m` will turn off the red. Note that,
while common, these codes arenâ€™t portable.

![screenshot](https://cdn-images-1.medium.com/max/600/1*eAJf1FkG56qilcqkuI-40w.png)


----


Where to go from here?
----------------------

So, in 10 lines of code, weâ€™ve constructed a fairly respectable REPL! If you found that fun, try adding some additional functionality:

* Save the previous result in `_` (probably `Binding#local_variable_set`)
* Handle large objects better, eg `{foo: 42, bar: {baz: 1, buz: 2, fuz: 3}, wibble: {magic_word: "sha"+"z"*20+"am"}}}` could get formatted very nicely if, you replace `inspect` with [`pretty_inspect`](http://www.rubydoc.info/stdlib/pp/Kernel#pretty_inspect-instance_method) from `pp.rb`, in the standard library.
* Remember history across invocations (check the [Readline](https://ruby-doc.org/stdlib-2.3.3/libdoc/readline/rdoc/Readline.html) docs)
* Syntax highlight the input (thereâ€™s probably a [Readline](https://ruby-doc.org/stdlib-2.3.3/libdoc/readline/rdoc/Readline.html) method to let you swap it out after itâ€™s been entered)
* Add some commands like pryâ€™s `ls` (search around for a list of reflection methods in order to get the information) and show-source (check these humansâ€™ gems: [bannister](https://rubygems.org/profiles/banister), [ConradIrwin](https://rubygems.org/profiles/ConradIrwin), [ryanf](https://rubygems.org/profiles/ryanf), [kyrylo](https://rubygems.org/profiles/kyrylo))
* Separate the REPLâ€™s variables from the userâ€™s. Enter `local_variables`, and youâ€™ll see youâ€™re sharing them with the program. Youâ€™ll have to get a binding that doesnâ€™t come from that context. Possibly useful: `TOPLEVEL_BINDING`, `eval` on a string, define a method on the singleton class then remove it, `Kernel#load`, and that JavaScript idiom of defining a lambda you immediately call.
* What would it take to make this testable? Youâ€™d probably want to be able to pass it the binding and IO streams. Readline looks plainful, IDK how itâ€™s storing its state, but itâ€™s going to be fragile or broken. Youâ€™ll want to segregate it from the rest of your program as much as possible, and if you want 100% coverage, you may need [PTY](http://www.rubydoc.info/stdlib/pty/PTY)s