\RequirePackage{luatex85} \RequirePackage{luacode} \ProvidesPackage{ghci}[2025/06/24 v0.1 GHCi code execution with TCP via Lua] \newif\ifghciready \ghcireadyfalse \newcommand{\ghciserverhost}{localhost} \newcommand{\ghciserverport}{54123} \directlua{ socket = require("socket") json = require("dkjson") client = socket.connect("\ghciserverhost", \ghciserverport) } \begin{luacode*} function newGhciSession(str) local msg = { tag = "ServerMsg", contents = { tag = "NewSession", contents = str } } if client then client:send(json.encode(msg)) end end function continueGhciSession(str) local msg = { tag = "ServerMsg", contents = { tag = "ContinueSession", contents = str } } if client then client:send(json.encode(msg)) end end newGhciSession("main") \end{luacode*} \begin{luacode*} function ghciCmd(code) local msg = { tag = "GhciMsg", contents = code } return json.encode(msg) end function ghci_eval(code) if client then client:send(ghciCmd(code)) local response_str, err = client:receive("*l") if response_str then local response, _ , decode_err = json.decode(response_str) if decode_err then tex.error("Could not decode JSON from GHCi server : " .. decode_err) else if response.ghciErr and response.ghciErr ~= '' then luatexbase.module_warning ("ghci", response.ghciErr) end return (response.ghciOut:gsub("%s*$", "")) -- remove trailing spaces end else tex.error("Reception error from GHCi server : " .. err) end return response_str else luatexbase.module_warning("ghci" , "GHCi server is not running. Please start it before using ghci4luatex.") return "" end end \end{luacode*} \begin{luacode*} local mybuf = "" local current_end_marker = "\\end{ghci}" function readbuf(buf) if buf:match('%s*' .. current_end_marker) then return buf end mybuf = mybuf .. buf .. "\n" return "" end function startRecording(envname) mybuf = "" current_end_marker = "\\end{" .. envname .. "}" luatexbase.add_to_callback('process_input_buffer', readbuf, 'readbuf') end function stopRecording(should_print) luatexbase.remove_from_callback('process_input_buffer', 'readbuf') local buf_without_end = mybuf:gsub(current_end_marker .. "\n", "") local res = ghci_eval(buf_without_end) if should_print then tex.print(res) end end \end{luacode*} \newenvironment{ghci} {\directlua{startRecording("ghci")}} {\directlua{stopRecording(false)}} \newenvironment{printghci} {\directlua{startRecording("printghci")}} {\directlua{stopRecording(true)}} \newcommand{\hask}[1]{% \directlua{ local result = ghci_eval([===[#1]===]) tex.print(result) }% } \newcommand{\ghcisession}[1]{% \directlua{ newGhciSession([===[#1]===]) }% } \newcommand{\ghcicontinue}[1]{% \directlua{ continueGhciSession([===[#1]===]) }% }