alhassy / repl-driven-development

Press "C-x C-e" to send any piece of code (in any language) to a REPL in the background, within Emacs!

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Editor Integrated REPLs for all languages

MELPA

This library provides the Emacs built-in C─x C─e behaviour for arbitrary languages, provided they have a REPL shell command.

It provides a “send line to REPL process” command, for your language.

img

  Ξ  

  1. Motivation
  2. Mini-Tutorial
  3. Installation & Usage Instructions
  4. Videos
    1. REPL Driven Development :: Teaching a JavaScript runtime, incrementally, to be a web server 🍽️ 🔁 🤖
  5. Bye!

Motivation

Whenever reading/refactoring some code, if you can make some of it self-contained, then you can immediately try it out! No need to load your entire program; nor copy-paste into an external REPL. The benefits of Emacs' built-in C─x C─e for Lisp, and Lisp's Repl Driven Development philosophy, are essentially made possible for arbitrary languages (to some approximate degree, but not fully).

Just as C-u C-x C-e inserts the resulting expression at the current cursour position, so too all repl-driven-development commands allow for a C─u prefix which inserts the result. This allows for a nice scripting experience where results are kept for future use.

Finally, just as C─h e shows you the *Messages* buffer where you can see the evaluations of your Emacs Lisp via C─x C─e; likewise, C─h e shows you the output results of any REPL command created by repl-driven-development.

Mini-Tutorial

Often, while reading a README file, we will (1) copy a shell command, (2) open a terminal, and (3) paste the shell command to run it. We can evaluate arbitrary regions in a shell in one step via C─x C─t with:

   (repl-driven-development [C-x C-t] "bash")

For example, execute C─x C─t anywhere on each line below and see results in an overlay, right by your cursor.

  echo "It is $(date) and I am at $PWD, my name is $(whoami) and I have: $(ls)"

  say "My name is $(whoami) and I like Emacs"

Notice as each line is sent to the Bash process, the line is highlighted briefly in yellow. Moreover, you can hover over the text to see a tooltip with the resulting shell output. Finally, if you invoke C-h k C-x C-t you get help about this new C─x C─t command, such as inserting results at point via C-u C-x C-t or to reset/refresh the current Bash process with C-u -1 C-x C-t.

This also works for any command-line REPL; for example, for Python:

   (repl-driven-development [C-x C-p] "python3")

Then, we can submit the following Python snippets with C─x C─p on each line.

  sum([1, 2, 3, 4])

  list(map(lambda i: 'Fizz'*(not i%3)+'Buzz'*(not i%5) or i, range(1,101)))

These work fine, however there are some shortcomings of this REPL. For example, echoing results could be prettier and it doesn't handle multi-line input very well. You can address these issues using the various hooks / keyword arguments of the repl-driven-development macro.

However, this package comes with preconfigured REPLS for: python, terminal, java, javascript.

Simply use the name of these configurations:

  (repl-driven-development [C-x C-p] python)

Now we can submit the following, with C─x C─p, with no issues:

  def square(x):
    return x * x

  square(5)

Since these new REPL commands are just Emacs functions, we can use several at the time, alternating between them. For example:

  ;; C-x C-e on the next two lines
  (repl-driven-development [C-x C-t] terminal)
  (repl-driven-development [C-x C-p] python)
  echo Hello... > /tmp/o       # C-x C-t here
  print(open("/tmp/o").read()) # C-x C-p here
  echo ...and bye >> /tmp/o    # C-x C-t again
  print(open("/tmp/o").read()) # C-x C-p again

Let's conclude with a GUI example in Java.

  ;; Set “C-x C-j” to evaluate Java code in a background REPL.
  (repl-driven-development [C-x C-j] "jshell")
  // Select this Java snippet, then press “C-x C-j” to evaluate it
  import javax.swing.*;
  JOptionPane.showMessageDialog(new JFrame(){{setAlwaysOnTop(true);}}, "Super nice!")

We can use a preconfigured Java REPL, to remove the annoying “jshell>” prompt from overlay echos, handle multi-line input, and more.

  (repl-driven-development [C-x C-j] java)
 // REPL result values are shown as overlays:
 // See a list of 23 numbers, which are attached as a tooltip to this text.
 IntStream.range(0, 23).forEach(x -> System.out.println(x))

For more documentation, and examples, see http://alhassy.com/repl-driven-development

Installation & Usage Instructions

This package is on Melpa, MELPA, so you can install it with use-package:

(use-package repl-driven-development
  :ensure t
  :config
  (repl-driven-development [C-x C-j] java)       ;; e“X”ecute “j”ava
  (repl-driven-development [C-x C-n] javascript) ;; e“X”ecute “n”odejs
  (repl-driven-development [C-x C-p] python)     ;; e“X”ecute “p”ython
  (repl-driven-development [C-x C-t] terminal))  ;; e“X”ecute “t”erminal

The above mentions the four pre-configured REPLs that the package comes with: These are like their CLI equivalents, but offer more bells and whistles.

  • For example, the pre-configured java REPL is like (repl-driven-development [C-x C-j] "jshell") but it supports multi-line input: JShell eagerly inserts semicolons onto expressions, so, say, a multi-line Stream pipeline would be interpreted as multiple distinct statements by JShell, whereas our java configuration handles this by stripping the newlines (and any intermediary comments).

You can use any process, for example let's use the tclsh command line process:

(repl-driven-development [s-t] "tclsh"        ;; Make “⌘-t” e“X”ecute “T”cl code,
                         :blink 'pulsar-blue  ;; and highlight submitted lines blue
                         :prompt "%")         ;; and don't show me the tclsh prompt, which is “%”.

The repl-driven-development macro has been tried at least with the following processes.

Please make a Pull Request, or Issue, to increase the following list!
🔗
JavaScript ---and a minimal server
🤔 We suggest using the preconfigured javascript configuration that ships with this package.

We can set up a JavaScript REPL in the background as follows…

   ;; C-x C-j now evaluates arbitrary JavaScript code
   (repl-driven-development [C-x C-j] "node -i")

That's it! Press C-x C-e on the above line so that C-x C-j will now evaluate a selection, or the entire line, as if it were JavaScript code.

  • Why C-x C-j  ?  Well, C-x C-“e” for Emacs Lisp code, and C-x C-“j” for JavaScript code!
  • For instance, copy-paste the following examples into a JavaScript file —or just press C-x C-j in any buffer to evaluate them!
1 + 2                                     // ⮕ 3

1 + '2'                                   // ⮕ '12'

let me = {name: 'Jasim'}; Object.keys(me) // ⮕ ['name']

me.doesNotExist('whoops')                 // ⮕ Uncaught TypeError

All of these results are echoed inline in an overlay, by default. Moreover, there is a REPL buffer created for your REPL so you can see everything you've sent to it, and the output it sent back. This is particularly useful for lengthy error messages, such as those of Java, which cannot be rendered nicely within an overlay.

How this works is that Emacs spawns a new “node -i” process, then C-x C-j sends text to that process. Whenever the process emits any output —on stdout or stderr— then we emit that to the user via an overlay starting with “⮕”.

Finally, “C-h k C-x C-j” will show you the name of the function that is invoked when you press C-x C-j, along with minimal docs.

A useful example would be a minimal server, and requests for it.

// First get stuff with C-x C-e:
// (async-shell-command "npm install -g express axios")

let app = require('express')()
let clicked = 1
app.get('/hi', (req, res) => res.send(`Hello World × ${clicked++}`))

let server = app.listen(3000)
// Now visit   http://localhost:3000/hi   a bunch of times!

// Better yet, see the output programmatically...
let axios = require('axios')
// Press C-x C-j a bunch of times on the following expression ♥‿♥
console.log((await axios.get('http://localhost:3000/hi')).data)

// Consider closing the server when you're done with it.
server.close()

Just as “Emacs is a Lisp Machine”, one can use “VSCodeJS” to use “VSCode as a JS Machine”. See http://alhassy.com/vscode-is-itself-a-javascript-repl.

🔗
Python
🤔 We suggest using the preconfigured python configuration that ships with this package.

We can set up a Python REPL in the background as follows…

    ;; C-x C-p now evaluates arbitrary Python code
    (repl-driven-development [C-x C-p] "python3 -i")

Example use…

1 + 2             # ⮕ 3

hello = 'world!'  # (No output; this is an effectful operation)

print(hello)      # ⮕ world!

2 + 'hi'          # 🚫 TypeError: unsupported operand type(s) for +

Learn more by reading… Python: A Gentle Introduction to Socket Programming

🔗
Java
🤔 We suggest using the preconfigured java configuration that ships with this package.

We can set up a Java REPL in the background as follows…

(repl-driven-development [C-x C-j] "jshell --enable-preview" :prompt "jshell>")

Now, we can select the following and press C-x C-j to evaluate the Java code:

// Ensure you're not fullscreen, and you'll see a dialog window appear.
import javax.swing.*;
JOptionPane.showMessageDialog(new JFrame(), "Super nice!");

Or doing algebraic datatypes in Java:

sealed interface Maybe {
    record None() implements Maybe {}
    record Just(int x) implements Maybe {}
}

var thisPrettyPrintsNicelyInTheREPL = new Maybe.Just(3);

new Maybe.Just(3).equals(new Maybe.Just(3)) // yay
🔗
Ruby

We can set up a REPL in the background as follows…

   ;; “C-x e r” now “e”valuates arbitrary “r”uby code
   (repl-driven-development [C-x e r] "irb --inf-ruby-mode" :prompt "irb(main):.*>")

For example…

2 + 2

33 + 4

5.times { print "Odelay!" } # ⮕ Odelay! Odelay! Odelay! Odelay! Odelay! 5

['ruby', 'is', 'readable'].map { | food | food.capitalize } # ⮕ ["Ruby", "Is", "Readable"]
🔗
Clojure

We can set up a REPL in the background as follows…

   ;; “C-x C-k” now evaluates arbitrary Clojure code
   (repl-driven-development [C-x C-k] "clojure" :prompt "user=>")

For example…

(+ 1 2) ;; ⮕ 3

(defn square [x] (* x x)) ;; ⮕ #'user/square
(square 3) ;; ⮕ 9
🔗
TypeScript

We can set up a REPL in the background as follows…

   ;; C-x C-j now evaluates arbitrary JavaScript code
   (repl-driven-development [C-x C-t] "npx ts-node")

Then we can use it as follows:

22 + 2

"hello"

However, the output is ugly since it mentions the ^M character.

  • Look at the python configuration that ships with this package for a starting point on how to address this issue.
🔗
Haskell

We can set up a REPL in the background as follows…

   ;; C-x C-h now evaluates arbitrary Haskell code
   (repl-driven-development [C-x C-h] "ghci" :prompt "ghci>")

For example…

-- Sum of the first 100 squares
sum [ x ** 2 | x <- [1..100]] -- ⇒ 338350.0

-- The positive evens at-most 12
[x | x <- [1..12], x `mod` 2 == 0] -- [2,4,6,8,10,12]

-- Define a function...
myLast = head . reverse

-- Then use it...
myLast [1, 2, 3] -- ⇒ 3

Note that Haskell has “typed holes” with the syntax _A:

1 + _A  -- ⇒ Found hole: _A::a; it :: forall {a}. Num a = a

Another language with typed holes is Arend…

🔗
Arend: Quickly making a terse Emacs interface for a language without one

The Arend Theorem Prover has an IntelliJ interface (since it's a JetBrains proof assistant), but no Emacs counterpart —which may be annoying for Agda/Coq programmers accustomed to Emacs but want to experiment with Arend.

We can set up an Arend REPL in the background as follows…

    ;; C-x C-a now evaluates arbitrary Arend code
    (repl-driven-development [C-x C-a]
                             (format "java -jar %s -i"
                                     (f-expand "~/Downloads/Arend.jar")))

Then,

1 Nat.+ 1 -- ⇒ 2
:type 4  -- ⇒ Fin 5

-- Declare a constant
\\func f => 1
:type f -- ⇒ Nat
f -- ⇒ 1

-- Declare a polymorphic identity function, then use it
\\func id {A : \\Type} (a : A) => a
id 12  -- ⇒ 12

-- Arend has “typed holes”
1 Nat.+ {?}  -- ⇒ Nat.+{?}: Goal: Expectedtype: Nat
🔗
PureScript

First brew install spago, then we can set up a PureScript REPL in the background as follows…

    ;; C-x C-p now evaluates arbitrary PureScript code
    (repl-driven-development [C-x C-p] "spago repl")

For example….

import Prelude

-- Define a function
add1 = (\x -> x + 1)

-- Use the function
add1 2    -- ⇒ 3

-- Experiment with a typed hole
1 + ?A  -- ⇒ Hole ?A has the inferred type Int
🔗
Idris

First brew install idris2, then we can set up an Idris REPL in the background as follows…

    ;; C-x C-i now evaluates arbitrary Idris code
    (repl-driven-development [C-x C-i] "idris2")

Here's some random code…

-- Like Lisp, Idris uses “the” for type annotations
the Nat 4  -- ⇒ 4 : Nat

with List sum [1,2,3] -- ⇒ 6

-- defining a new type (REPL specific notation)
:let data Foo : Type where Bar : Foo

:t Bar -- ⇒ Foo

-- Experiment with a typed hole [Same notation as Haskell]
1 + ?A -- prim__add_Integer 1 ?A
🔗
Racket
Racket is a modern programming language in the Lisp/Scheme family.

First brew install --cask racket, then we can set up an Racket REPL in the background as follows…

    ;; C-x C-i now evaluates arbitrary Racket code
    (repl-driven-development [C-x C-r] "racket -I slideshow")

Here's some random code…

(define (series mk) (hc-append 4 (mk 5) (mk 10) (mk 20)))

;; Shows 3 circles of increasing radius, in an external window
(show-pict (series circle))

Meeting Racket for the first time is probably best done with DrRacket.

🔗
Common Lisp

We can set up a REPL in the background as follows…

   ;; “C-x e p” now “e”valuates arbitrary “c”ommon-lisp code
   (repl-driven-development [C-x e c] "sbcl" :prompt "\\*")

For example…

(print "hello world")
🔗
Perl

We can set up a REPL in the background as follows…

   ;; “C-x e p” now “e”valuates arbitrary “p”erl code
   (repl-driven-development [C-x e p] "perl -de0")

For example…

print(1..5)
🔗
Julia

We can set up a REPL in the background as follows…

   ;; “C-x e j” now “e”valuates arbitrary “j”uila code
   (repl-driven-development [C-x e j] "julia" :prompt "julia>")

For example… Let's get a random 2×2 matrix…

rand(2, 2)
🔗
GNU Smalltalk

We can set up a REPL in the background as follows…

   ;; “C-x e j” now “e”valuates arbitrary “s”malltalk code
   (repl-driven-development [C-x e s] "gst" :prompt "st")

For example…

'Hello World!' printNl !
🔗
Tcl

We can set up a REPL in the background as follows…

   ;; “C-x e t” now “e”valuates arbitrary “t”cl code
   (repl-driven-development [C-x e t] "tclsh" :prompt "%")

For example…

set a 1
set b 2
puts $a$b[expr 2 + 3]{bye}
🔗
Lua

We can set up a REPL in the background as follows…

   ;; “C-x e l” now “e”valuates arbitrary “l”ua code
   (repl-driven-development [C-x e l] "lua")

For example…

print("Hello, world")

Videos

REPL Driven Development :: Teaching a JavaScript runtime, incrementally, to be a web server 🍽️ 🔁 🤖

Bye!

About

Press "C-x C-e" to send any piece of code (in any language) to a REPL in the background, within Emacs!

License:GNU General Public License v3.0


Languages

Language:Emacs Lisp 75.2%Language:Java 18.1%Language:JavaScript 4.8%Language:Python 2.0%