Working better, but even the basic sedit functionality isn't right.

This commit is contained in:
simon 2017-06-29 15:29:48 +01:00
parent 04619ed02b
commit 3b90c105d4
3 changed files with 170 additions and 106 deletions

View file

@ -8,12 +8,22 @@ Preliminary, incomplete, alpha quality code. This implements a structure editor
not a display editor in the tradition of InterLisp's DEdit. I do intend to follow up with a not a display editor in the tradition of InterLisp's DEdit. I do intend to follow up with a
display editor, but this is exploratory proof-of-concept code. display editor, but this is exploratory proof-of-concept code.
Note that to work with this, you need to start with
lein trampoline repl
rather than just
lein repl
I've read explanations of why this is, but I don't claim to fully understand them. Treat it as magic,
but trust me on this.
To edit an arbitrary s-expression: To edit an arbitrary s-expression:
(sedit sexpr) (sedit sexpr)
This pretty much works now; it returns an edited copy of the s-expression. Vectors are not handled This pretty much works now; it returns an edited copy of the s-expression. Maps are not yet handled.
intelligently (but could be).
To edit a function definition To edit a function definition

View file

@ -3,5 +3,6 @@
:url "http://example.com/FIXME" :url "http://example.com/FIXME"
:license {:name "Eclipse Public License" :license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"} :url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]] :dependencies [[org.clojure/clojure "1.8.0"]
[jline "2.11"]]
:clean-targets ["classes" "bin"]) :clean-targets ["classes" "bin"])

View file

@ -1,5 +1,8 @@
(ns fedit.core (ns fedit.core
(:use clojure.repl)) (:require [clojure.repl :refer :all]
[clojure.pprint :refer [pprint]])
(:import [jline.console ConsoleReader]))
(defn clear-terminal (defn clear-terminal
"Clear the terminal screen - should be possible to do this by printing a \f, but "Clear the terminal screen - should be possible to do this by printing a \f, but
@ -7,87 +10,137 @@
[] []
(dotimes [_ 25] (println))) (dotimes [_ 25] (println)))
(defn print-indent
"indent this many spaces and then print this s-expression"
[x spaces]
(dotimes [_ spaces] (print " "))
(println x)
x)
(defn recursively-frob-strings
"Walk this s-expression, replacing strings with quoted strings.
TODO: does not fix strings in vectors"
[sexpr]
(cond
(nil? sexpr) nil
(symbol? sexpr) sexpr
(empty? sexpr) ()
(list? sexpr)(cons (recursively-frob-strings (first sexpr))(recursively-frob-strings (rest sexpr)))
(string? sexpr)(str "\"" sexpr "\"")
true sexpr))
(defn rereadable-print-str
"print-str doesn't produce a re-readable output, because it does not surround
embedded strings with quotation marks. This attempts to fix this problem."
[sexpr]
(let [fixed (recursively-frob-strings sexpr)]
(print-str fixed)))
(defn pretty-print
"Print this s-expression neatly indented.
TODO: Does not yet handle vectors intelligently"
([sexpr] (pretty-print sexpr 0))
([sexpr indent]
(cond
(string? sexpr)
(let [printform (str "\"" sexpr "\"")](print-indent printform indent))
(list? sexpr)
(let [asstring (rereadable-print-str sexpr)]
;; print-str isn't right here because it does not substitute in quotation marks around strings
;; need to write a new function of my own.
(cond
(< (+ indent (count asstring)) 80) (print-indent asstring indent)
true (do
(let [firstline (str "(" (rereadable-print-str (first sexpr)))]
(print-indent firstline indent))
(doall (map (fn [x] (pretty-print x (+ indent 2))) (rest sexpr)))
(print-indent ")" indent))))
true (print-indent sexpr indent))
sexpr))
(defn read-char (defn read-char
"Ultimately this will read a single character, probably requiring some Java hackery; but for now "Read from standard input a single character which is one of these targets return it."
just read" [targets]
[] (let [cr (ConsoleReader.)
(read)) keyint (.readCharacter cr)
key (char keyint)]
(if
(some #(= % key) targets)
key
(recur targets))))
(def symbol-menu
{\r "Return"
\s "Substitute"
\x "eXcise"})
(def sequence-menu
{\a "cAr"
\d "cDr"
\r "Return"
\s "Substitute"
\x "eXcise"})
(defn prompt-and-read (defn prompt-and-read
"Show a prompt, and read a form from the input "Show a prompt, and read a form from the input
TODO: the read should be on the same line as the prompt - again, possibly some hackery needed." TODO: the read should be on the same line as the prompt - again, possibly some hackery needed."
[prompt] [prompt]
;; print, on its own, does not flush the buffer. ;; print, on its own, does not flush the buffer.
(println prompt) (print (str prompt " "))
(read)) (flush)
(read-string
(read-line)))
(defn print-menu
"Print this menu."
[menu]
(println
(apply
str
(cons
"Enter one character: "
(map
#(str " \t" % ": " (menu %))
(keys menu))))))
(defn- prepare-screen
"Prepare the screen for editing this s-expression"
[sexpr menu]
(clear-terminal)
(pprint sexpr)
(print-menu menu))
(defn- sedit-list
"Edit something believed to be a list."
[l]
(if
(list? l)
(do
(prepare-screen l sequence-menu)
(let [key (read-char (keys sequence-menu))]
(case key
\x nil
\s (sedit (prompt-and-read "==?"))
\a (sedit (let [[car & cdr] l] (cons (sedit car) cdr)))
\d (sedit (let [[car & cdr] l] (cons car (sedit cdr))))
\r l
(sedit l))))
l))
(defn- sedit-vector
"Edit something believed to be a vector. Different from
sedit-list, since vectors are recomposed differently."
[v]
(if
(vector? v)
(do
(prepare-screen v sequence-menu)
(let [key (read-char (keys sequence-menu))]
(case key
\x nil
\s (sedit (prompt-and-read "==?"))
\a (sedit (let [[car & cdr] v] (apply vector (cons (sedit car) cdr))))
\d (sedit (let [[car & cdr] v] (apply vector (cons car (sedit cdr)))))
\r v
(sedit v))))
v))
(defn- sedit-token
"Edit something which from our point of view is a single token
(which for now includes strings)."
[token]
(prepare-screen token symbol-menu)
(let [key (read-char (keys symbol-menu))]
(case key
\x nil
\s (sedit (prompt-and-read "==?"))
\r token
(sedit token))))
(defn cons?
"Return true if this sexpr is either a cons or a list.
Bizarrely, in Clojure, a cons cell is not a list."
[sexpr]
(or
(list? sexpr)
(instance? clojure.lang.Cons sexpr)))
(defn sedit (defn sedit
"Edit an S-Expression, and return a modified version of it" "Edit an S-Expression, and return a modified version of it"
[sexpr] [sexpr]
(clear-terminal) (cond
(pretty-print sexpr) (nil? sexpr) (sedit-token sexpr)
(cond (list? sexpr) (println "Enter one character: a:CAR; d:CDR; s:Substitute; x:Cut; r:Return") (cons? sexpr) (sedit-list sexpr)
true (println "Enter one character: s:Substitute; x:Cut; r:Return")) (vector? sexpr) (sedit-vector sexpr)
(let [key (read-char)] (or
(cond (symbol? sexpr)
(= key 'x) nil (number? sexpr)
(= key 's) (prompt-and-read "==?") (string? sexpr)) (sedit-token sexpr)
(and (= key 'a)(list? sexpr)(> (count sexpr) 0)) true (println (str "Unexpected: " (type sexpr)))))
(let [car (sedit (first sexpr)) cdr (rest sexpr)](sedit (cons car cdr)))
(and (= key 'd)(list? sexpr))
(let [car (first sexpr) cdr (sedit (rest sexpr))](sedit (cons car cdr)))
(= key 'r) sexpr
true (sedit sexpr))))
(defn fedit (defn fedit
"Edit a named function or macro, and recompile the result. "Edit a named function or macro, and recompile the result.