Let's build a little Haskell interpreter that we can use as a safe sandbox on the Web.
We'll use Scotty for our Web
binding, so we'll want to enable OverloadedStrings
and
import some functions:
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty (get, param, request, scotty, ScottyM, text)
import Data.Text.Lazy (pack)
import Control.Monad.IO.Class (liftIO)
import System.Environment (getEnv)
We'll use hint
to
interpret and run client code:
import qualified Language.Haskell.Interpreter as I
We need functions to take arbitrary source code as a string, try to evaluate it, and handle compilation errors:
eval :: String -> IO String
= fmap
eval src either show id)
($ I.setImports ["Prelude"] >> I.eval src) (I.runInterpreter
We also need a way to expose it to the Web:
service :: ScottyM ()
= do
service "/" $ do
get <- request
req <- param "src"
src <- liftIO $ eval src
res $ pack res text
Finally, we need a way to launch it:
main :: IO ()
= do
main <- read <$> getEnv "HASKEVAL_PORT"
port scotty port service
With our main
function defined, we're ready to fire it
up. We can use Nix to import the hint
and
scotty
packages for us:
$ nix-shell -p 'haskellPackages.ghcWithPackages (pkgs: with pkgs; [hint scotty])'
From our Nix shell, we can use Codedown to extract
the Haskell code above, and runhaskell
to launch it:
$ curl -s https://earldouglas.com/toys/haskeval.md |
codedown haskell |
HASKEVAL_PORT=3000 runhaskell
Setting phasers to stun... (port 3000) (ctrl-c to quit)
Let's try it out! Enter a line of Haskell (with the type
Show a => a
) below, and hit Run
.