Instantly share code, notes, and snippets.

Embed
What would you like to do?
Fif Interactive Repl Used in blog posts
(ns website.fif-repl
(:require
[clojure.string :as str]
[rum.core :as rum]
[fif.core :as fif]
[fif.stack-machine :as stack]
[fif.stack-machine.error-handling :refer [set-error system-error]]
;; Nothing to see here
[website.repl-imports :refer [import-super-secret-words]]))
(defn stack-error-handler
"Custom fif error handler for stack errors"
[sm errobj]
(set-error sm errobj))
(defn system-error-handler
"Custom fif error handler for system errors"
[sm ex]
(println ex) ;; This will also be captured and placed in standard out of prepl
(set-error sm (system-error sm ex "System Error")))
(def website-sm
(-> fif/*default-stack*
stack/enable-debug
import-super-secret-words
(stack/set-system-error-handler system-error-handler)
(stack/set-stack-error-handler stack-error-handler)
(stack/set-step-max 10000))) ;; Prevents infinite loops from going on forever
(defn format-inner-html
"Correctly format newlines within <div>'s placed in markdown. Super
Hacky."
[s]
(-> (.-innerHTML s)
(subs 1)
(str/replace #"<p>" "")
(str/replace #"</p>" "\n")
(str/replace #"_ " "\n")))
(defn prepl-output-fn
"Main Prepl Output Function."
[fif-state {:keys [tag value]}]
(swap! fif-state update :output-string #(reduce str %1 %2) value))
(defn evaluate-sm-fcn
"Stack Machine Evaluation"
[fif-state]
(swap! fif-state assoc :output-string "")
(let [sm (fif/prepl-eval website-sm (:input-string @fif-state) (partial prepl-output-fn fif-state))
main-stack (-> sm fif/get-stack reverse)]
(swap! fif-state assoc :output-stack main-stack)))
(def mixin-evaluate-fif-repl
"Rum Mixin for stack-machine evaluation after mount.
Also includes textarea 'input' event to re-evaluate after change."
{:did-mount
(fn [state]
(let [comp (:rum/react-component state)
dom-node (js/ReactDOM.findDOMNode comp)
fif-state (-> state :rum/args first)]
(evaluate-sm-fcn fif-state)
(when-let [textarea (.querySelector dom-node "textarea")]
(.addEventListener
textarea "input"
(fn [e]
(swap! fif-state assoc :input-string (.-value textarea))
(evaluate-sm-fcn fif-state))))
state))})
(rum/defcs c-fif-repl
<
rum/reactive
mixin-evaluate-fif-repl
[state app-state]
(let [{:keys [input-string output-string output-stack]}
(rum/react app-state)]
[:.fif-repl-component
[:textarea {:spellCheck false
:default-value input-string}]
[:.output-stack (str output-stack)]
[:.output-string output-string]]))
(defn mount-fif-repl
"Turns the given element into a fif-repl. element is assumed to have
<p></p> separated paragraphs as fif script. This is a little hacky,
since i'm placing the <div> elements in a markdown parser. Would
probably be less hacky outside of a markdown parser."
[elem]
(let [fif-state (atom {:elem elem
:input-string (format-inner-html elem)
:output-string ""
:output-stack '(2 2 +)})]
(rum/mount (c-fif-repl fif-state) elem)))
(defn enable-fif-repls
"Turns all elements with 'selector' into a fif-repl."
[selector]
(let [elems (.querySelectorAll js/document selector)]
(doseq [elem-idx (range (.-length elems))]
(let [elem (aget elems elem-idx)]
(mount-fif-repl elem)))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment