Current section
Files
Jump to
Current section
Files
src/undertone/repl/extempore.lfe
(defmodule undertone.repl.extempore
(export
(loop 1)
(print 1)
(read 0)
(start 0)
(xt-eval 1)))
(include-lib "logjam/include/logjam.hrl")
;;;;;::=------------------------=::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;::=- core repl functions -=::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;::=------------------------=::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun read ()
(let ((sexp (undertone.sexp:readlines (undertone.xtrepl:prompt))))
(log-debug "Got user input (sexp): ~p" `(,sexp))
sexp))
(defun eval-dispatch
(((= `#m(tokens ,tokens) sexp))
(case tokens
;; Built-ins
(`("call" . ,_) (xt-blocking-eval sexp))
('("check-xt") 'check-xt)
(`("def" ,var ,val) (xt:def var val))
('("eom") 'term)
('("exit") 'quit)
('("h") 'help)
('("help") 'help)
('("list-midi") (xt.midi:list-devices))
(`("load" ,file) `#(load ,file))
('("quit") 'quit)
('("restart") 'restart)
('("term") 'term)
('("v") 'version)
('("version") 'version)
;; Session commands
(`("rerun" ,idx) `#(rerun ,idx))
(`("rerun" ,start ,end) `#(rerun ,start ,end))
('("sess") 'sess)
(`("sess" ,idx) `#(sess ,idx))
(`("sess-line" ,idx) `#(sess-line ,idx))
(`("sess-load" ,file) `#(sess-load ,file))
(`("sess-save" ,file) `#(sess-save ,file))
;; Fall-through
(_ (xt-eval sexp)))))
(defun repl-eval (sexp)
(let ((tokens (mref sexp 'tokens)))
(case tokens
('() 'empty)
(progn
(maybe-save-command sexp)
(eval-dispatch sexp)))))
(defun xt-blocking-eval (sexp)
"This is going to be ugly ... (for now/until we have real parsing)"
;; 1. with the value associated with the 'source' key, replace initial
;; "(call" with ""
;; 2. from the same, do a regex to replace the final ")" with ""
;; 3. without any /real/ parsing, this should give us the body of the
;; Extempore code
;; 4. send that to Extempore as a synchronous message
(let* ((source (string:trim (mref sexp 'source)))
(remove-call (re:replace source "^\\(call" "" '(#(return list))))
(extempore-form (re:replace remove-call "\\)$" "" '(#(return list)))))
(xt.msg:sync extempore-form)))
(defun xt-eval (sexp)
(xt.msg:async (mref sexp 'source)))
(defun print (result)
(case result
;; No-op
('() 'ok)
('empty 'ok)
;; Built-ins
('check-xt (check-extempore))
('help (help))
(`#(load ,file) (load file))
('quit 'ok)
('restart (progn
(log-notice "Restarting the Extempore binary ...")
(undertone.extempore:stop)))
('term (xt.msg:async ""))
('version (version))
;; Session
(`#(rerun ,idx) (rerun (list_to_integer idx)))
(`#(rerun ,start ,end) (rerun (list_to_integer start)
(list_to_integer end)))
('sess (sess))
(`#(sess ,idx) (sess (list_to_integer idx)))
(`#(sess-line ,idx) (sess-line (list_to_integer idx)))
(`#(sess-load ,file) (undertone.xtrepl:session-load file))
(`#(sess-save ,file) (undertone.xtrepl:session-save file))
;; Fall-through
(_ (lfe_io:format "~p~n" `(,result))))
result)
(defun loop
(('quit)
(log-info "Leaving the Extempore REPL ...")
'good-bye)
((_)
(try
(loop (print (repl-eval (read))))
(catch
(`#(,err ,clause ,stack)
(io:format "~p: ~p~n" `(,err ,clause))
(io:format "Stacktrace: ~p~n" `(,stack))
(loop 'restart))))))
(defun start ()
(log-info "Entering the Extempore REPL ...")
(undertone.xtrepl:set-repl 'extempore)
(io:format "~s" `(,(undertone.xtrepl:session-banner)))
(loop 'start))
;;;;;::=--------------------=::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;::=- shell functions -=::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;::=--------------------=::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun check-extempore ()
(xt.msg:async "#(health ok)")
;; Then let's give the logger a chance to print the result before displaying
;; the extempore prompt:
(timer:sleep 500))
(defun help ()
(lfe_io:format "~s" `(,(binary_to_list
(undertone.sysconfig:read-priv
"help/repl-extempore.txt")))))
(defun sess (n)
(io:format "~n---- Recent REPL Session Commands ----~n~n")
(log-debug "Got sess line count: ~p" `(,n))
(show-prev (get-sess-list n))
(io:format "~n"))
(defun sess ()
(sess (undertone.xtrepl:session-show-max)))
(defun sess-line (n)
(log-debug "Got session entries index: ~p" `(,n))
(show-prev (undertone.xtrepl:session n)))
(defun load (file-name)
(let ((`#(ok ,data) (file:read_file file-name)))
(xt.msg:sync (binary_to_list data))))
(defun rerun (n)
;; XXX track which command was last executred in the server state, then when
;; this command it run, it can look there and decide whether to run from
;; the session or the history (when support for cross-session commands is
;; added); this will have to wait until the REPL loop is converted to a
;; gen_server or something similar
(clj:-> (get-cmd n)
(undertone.sexp:parse)
(eval-dispatch)
(print)))
(defun rerun (n m)
(lists:map #'rerun/1 (lists:reverse (lists:seq m n))))
(defun version ()
(lfe_io:format "~p~n" `(,(undertone.server:versions))))
;;;;;::=--------------------------------=::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;::=- utility / support functions -=::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;::=--------------------------------=::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun extract-prev-cmd
((`#(,_ ,elem))
elem))
(defun get-cmd (n)
(let ((last-cmd (undertone.xtrepl:session n)))
(case last-cmd
('() '())
(_ (clj:-> last-cmd
(car)
(extract-prev-cmd))))))
(defun get-sess-list (n)
(let* ((sess-list (undertone.xtrepl:session-list))
(pos (- (length sess-list) n))
(idx (if (=< pos 0) 0 pos)))
(lists:nthtail idx sess-list)))
(defun maybe-save-command
"Don't save the command if it's a re-run or if it's the same as the previous
command."
(((= `#m(tokens ,tkns source ,src) sexp))
(cond ((== (car tkns) "rerun") 'skip-history)
((== src (get-cmd 1)) 'skip-history)
('true (undertone.xtrepl:session-insert (mref sexp 'source))))))
(defun print-prev-line (elem idx)
(lfe_io:format "~p. ~s~n" `(,idx ,(extract-prev-cmd elem))))
(defun show-prev
(('())
'ok)
((`(,h . ,t))
(print-prev-line h (+ 1 (length t)))
(show-prev t)))