Common Lisp: Add basic completion for toplevel symbols to the REPL
[jackhill/mal.git] / common-lisp / src / step7_quote.lisp
index 26d4a57..ea7d53d 100644 (file)
@@ -5,12 +5,16 @@
         :reader
         :printer
         :core)
+  (:import-from :cl-readline
+                :readline
+                :register-function)
   (:import-from :genhash
                 :hashref
                 :hashmap)
   (:import-from :utils
                 :listify
-                :getenv)
+                :getenv
+                :common-prefix)
   (:export :main))
 
 (in-package :mal)
@@ -18,9 +22,7 @@
 (defvar *repl-env* (env:create-mal-env))
 
 (dolist (binding core:ns)
-  (env:set-env *repl-env*
-               (car binding)
-               (cdr binding)))
+  (env:set-env *repl-env* (car binding) (cdr binding)))
 
 (defvar mal-def! (make-mal-symbol "def!"))
 (defvar mal-let* (make-mal-symbol "let*"))
        (mal-data-value sequence)))
 
 (defun eval-hash-map (hash-map env)
-  (let ((hash-map-value (types:mal-data-value hash-map))
-        (new-hash-table (types:make-mal-value-hash-table)))
+  (let ((hash-map-value (mal-data-value hash-map))
+        (new-hash-table (make-mal-value-hash-table)))
     (genhash:hashmap (lambda (key value)
                        (setf (genhash:hashref (mal-eval key env) new-hash-table)
                              (mal-eval value env)))
                      hash-map-value)
-    (types:make-mal-hash-map new-hash-table)))
+    (make-mal-hash-map new-hash-table)))
 
 (defun eval-ast (ast env)
   (switch-mal-type ast
 (defun is-pair (value)
   (and (or (mal-list-p value)
            (mal-vector-p value))
-       (< 0 (length (types:mal-data-value value)))))
+       (< 0 (length (mal-data-value value)))))
 
 (defun quasiquote (ast)
   (if (not (is-pair ast))
-      (types:make-mal-list (list mal-quote ast))
+      (make-mal-list (list mal-quote ast))
       (let ((forms (map 'list #'identity (mal-data-value ast))))
         (cond
           ((mal-data-value= mal-unquote (first forms))
 
           ((and (is-pair (first forms))
                 (mal-data-value= mal-splice-unquote
-                            (first (mal-data-value (first forms)))))
-           (types:make-mal-list (list mal-concat
-                                      (second (mal-data-value (first forms)))
-                                      (quasiquote (make-mal-list (cdr forms))))))
+                                 (first (mal-data-value (first forms)))))
+           (make-mal-list (list mal-concat
+                                (second (mal-data-value (first forms)))
+                                (quasiquote (make-mal-list (cdr forms))))))
 
-          (t (types:make-mal-list (list mal-cons
-                                        (quasiquote (first forms))
-                                        (quasiquote (make-mal-list (cdr forms))))))))))
+          (t (make-mal-list (list mal-cons
+                                  (quasiquote (first forms))
+                                  (quasiquote (make-mal-list (cdr forms))))))))))
 
 (defun mal-read (string)
   (reader:read-str string))
@@ -86,8 +88,8 @@
 (defun mal-eval (ast env)
   (loop
      do (cond
-          ((null ast) (return types:mal-nil))
-          ((not (types:mal-list-p ast)) (return (eval-ast ast env)))
+          ((null ast) (return mal-nil))
+          ((not (mal-list-p ast)) (return (eval-ast ast env)))
           ((zerop (length (mal-data-value ast))) (return ast))
           (t (let ((forms (mal-data-value ast)))
                (cond
 
                  ((mal-data-value= mal-let* (first forms))
                   (let ((new-env (env:create-mal-env :parent env))
-                        (bindings (utils:listify (types:mal-data-value (second forms)))))
+                        (bindings (utils:listify (mal-data-value (second forms)))))
 
                     (mapcar (lambda (binding)
                               (env:set-env new-env
                                            (car binding)
                                            (mal-eval (or (cdr binding)
-                                                         types:mal-nil)
+                                                         mal-nil)
                                                      new-env)))
                             (loop
                                for (symbol value) on bindings
 
                  ((mal-data-value= mal-if (first forms))
                   (let ((predicate (mal-eval (second forms) env)))
-                    (setf ast (if (or (mal-data-value= predicate types:mal-nil)
-                                      (mal-data-value= predicate types:mal-false))
+                    (setf ast (if (or (mal-data-value= predicate mal-nil)
+                                      (mal-data-value= predicate mal-false))
                                   (fourth forms)
                                   (third forms)))))
 
                  ((mal-data-value= mal-fn* (first forms))
                   (return (let ((arglist (second forms))
                                 (body (third forms)))
-                            (types:make-mal-fn (lambda (&rest args)
-                                                 (mal-eval body (env:create-mal-env :parent env
-                                                                                    :binds (map 'list
-                                                                                                #'identity
-                                                                                                (mal-data-value arglist))
-                                                                                    :exprs args)))
-                                               :attrs (list (cons 'params arglist)
-                                                            (cons 'ast body)
-                                                            (cons 'env env))))))
+                            (make-mal-fn (lambda (&rest args)
+                                           (mal-eval body (env:create-mal-env :parent env
+                                                                              :binds (listify (mal-data-value arglist))
+                                                                              :exprs args)))
+                                         :attrs (list (cons 'params arglist)
+                                                      (cons 'ast body)
+                                                      (cons 'env env))))))
 
                  (t (let* ((evaluated-list (eval-ast ast env))
                            (function (car evaluated-list)))
                       ;; If first element is a mal function unwrap it
-                      (if (not (types:mal-fn-p function))
+                      (if (not (mal-fn-p function))
                           (return (apply (mal-data-value function)
                                          (cdr evaluated-list)))
-                          (let* ((attrs (types:mal-data-attrs function)))
+                          (let* ((attrs (mal-data-attrs function)))
                             (setf ast (cdr (assoc 'ast attrs))
                                   env (env:create-mal-env :parent (cdr (assoc 'env attrs))
                                                           :binds (map 'list
 
 (defun rep (string)
   (handler-case
-      (mal-print (mal-eval (mal-read string)
-                           *repl-env*))
+      (mal-print (mal-eval (mal-read string) *repl-env*))
     (error (condition)
-      (format nil
-              "~a"
-              condition))))
+      (format nil "~a" condition))))
 
 (env:set-env *repl-env*
-             (types:make-mal-symbol "eval")
-             (types:make-mal-builtin-fn (lambda (ast)
-                                          (mal-eval ast *repl-env*))))
+             (make-mal-symbol "eval")
+             (make-mal-builtin-fn (lambda (ast)
+                                    (mal-eval ast *repl-env*))))
 
 (rep "(def! not (fn* (a) (if a false true)))")
 (rep "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
 
 (defvar *use-readline-p* nil)
 
+(defun complete-toplevel-symbols (input &rest ignored)
+  (declare (ignorable ignored))
+
+  (let (candidates)
+    (loop for key being the hash-keys of (env:mal-env-bindings *repl-env*)
+       when (let ((pos (search input key))) (and pos (zerop pos)))
+       do (push key candidates))
+
+    (if (= 1 (length candidates))
+        (cons (car candidates) candidates)
+        (cons (apply #'utils:common-prefix candidates) candidates))))
+
 (defun raw-input (prompt)
   (format *standard-output* prompt)
   (force-output *standard-output*)
 
 (defun mal-readline (prompt)
   (if *use-readline-p*
-      (cl-readline:readline :prompt prompt
-                            :add-history t
-                            :novelty-check (lambda (old new)
-                                             (not (string= old new))))
+      (rl:readline :prompt prompt :add-history t :novelty-check #'string/=)
       (raw-input prompt)))
 
 (defun mal-writeline (string)
                 *standard-output* (ext:make-stream :output :buffered t)
                 *error-output* (ext:make-stream :error :buffered t))
 
+  ;; CCL fails with a error while registering completion function
+  ;; See also https://github.com/mrkkrp/cl-readline/issues/5
+  #-ccl (rl:register-function :complete #'complete-toplevel-symbols)
+
   (let ((args (if argv-provided-p
                   argv
                   (cdr (utils:raw-command-line-arguments)))))
     (env:set-env *repl-env*
-                 (types:make-mal-symbol "*ARGV*")
-                 (types:wrap-value (cdr args) :listp t))
+                 (make-mal-symbol "*ARGV*")
+                 (make-mal-list (mapcar #'make-mal-string (cdr args))))
     (if (null args)
         (repl)
         (run-file (car args)))))