Working better, but even the basic sedit functionality isn't right.
This commit is contained in:
parent
04619ed02b
commit
3b90c105d4
76
README.md
76
README.md
|
@ -4,16 +4,26 @@ An in-core editor for Clojure data structures and, particularly, functions.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Preliminary, incomplete, alpha quality code. This implements a structure editor in a terminal,
|
Preliminary, incomplete, alpha quality code. This implements a structure editor in a terminal,
|
||||||
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
|
||||||
|
|
||||||
|
@ -28,85 +38,85 @@ NOTE: This is broken, see working notes below; but it is showing promise.
|
||||||
Currently, Clojure metadata on a function symbol as follows:
|
Currently, Clojure metadata on a function symbol as follows:
|
||||||
|
|
||||||
{
|
{
|
||||||
:arglists ([sexpr]),
|
:arglists ([sexpr]),
|
||||||
:ns #<Namespace fedit.core>,
|
:ns #<Namespace fedit.core>,
|
||||||
:name sedit, :column 1,
|
:name sedit, :column 1,
|
||||||
:doc "Edit an S-Expression, and return a modified version of it",
|
:doc "Edit an S-Expression, and return a modified version of it",
|
||||||
:line 74,
|
:line 74,
|
||||||
:file "fedit/core.clj"
|
:file "fedit/core.clj"
|
||||||
}
|
}
|
||||||
|
|
||||||
In order to be able to recover the source of a function which has not yet been committed to the file
|
In order to be able to recover the source of a function which has not yet been committed to the file
|
||||||
system, it would be necessary to store the source s-expression on the metadata. You cannot add new
|
system, it would be necessary to store the source s-expression on the metadata. You cannot add new
|
||||||
metadata to an existing symbol (? check this), but, again, as the package reloader effectively does
|
metadata to an existing symbol (? check this), but, again, as the package reloader effectively does
|
||||||
so, there must be a way, although it may be dark and mysterious. Obviously if we're smashing and
|
so, there must be a way, although it may be dark and mysterious. Obviously if we're smashing and
|
||||||
rebinding the function's compiled form we're doing something dark and mysterious anyway.
|
rebinding the function's compiled form we're doing something dark and mysterious anyway.
|
||||||
|
|
||||||
### Generating/persisting packages
|
### Generating/persisting packages
|
||||||
|
|
||||||
Editing a function which is in an existing package has problems associated with it. We cannot easily
|
Editing a function which is in an existing package has problems associated with it. We cannot easily
|
||||||
save it back to its original file, as that will throw out the line numbering of every other definition
|
save it back to its original file, as that will throw out the line numbering of every other definition
|
||||||
in the file. Also, critically, files contain textual comments which are not read by the reader, and
|
in the file. Also, critically, files contain textual comments which are not read by the reader, and
|
||||||
consequently would be smashed by overwriting the old definition with the new definition.
|
consequently would be smashed by overwriting the old definition with the new definition.
|
||||||
|
|
||||||
Consequently I'm thinking that a revised package manager for Clojure-with-in-core-editing should
|
Consequently I'm thinking that a revised package manager for Clojure-with-in-core-editing should
|
||||||
generate new packages with names of the form packagename_serial; that when files are edited in core,
|
generate new packages with names of the form packagename_serial; that when files are edited in core,
|
||||||
the serial number should be incremented to above the highest existing serial number for that package,
|
the serial number should be incremented to above the highest existing serial number for that package,
|
||||||
and the new package (with the new serial number) should depend on the next-older version of the
|
and the new package (with the new serial number) should depend on the next-older version of the
|
||||||
package (obviously, recursively). The function (use 'packagename) should be rewritten so if passed
|
package (obviously, recursively). The function (use 'packagename) should be rewritten so if passed
|
||||||
a package name without a version number part, it would seek the highest numbered version of the
|
a package name without a version number part, it would seek the highest numbered version of the
|
||||||
specified package available on the path.
|
specified package available on the path.
|
||||||
|
|
||||||
At the end of a Clojure session (or, actually, at any stage within a session) the user could issue
|
At the end of a Clojure session (or, actually, at any stage within a session) the user could issue
|
||||||
a directive
|
a directive
|
||||||
|
|
||||||
(persist-edits)
|
(persist-edits)
|
||||||
|
|
||||||
Until this directive had been called, none of the in-core edits which had been made in the session
|
Until this directive had been called, none of the in-core edits which had been made in the session
|
||||||
would be saved. When the directive was made, the persister would go through all functions/symbols
|
would be saved. When the directive was made, the persister would go through all functions/symbols
|
||||||
which had been edited during the session, and if they had package metadata would immediately save
|
which had been edited during the session, and if they had package metadata would immediately save
|
||||||
them; if they had no package metadata would prompt for it.
|
them; if they had no package metadata would prompt for it.
|
||||||
|
|
||||||
## Working notes
|
## Working notes
|
||||||
|
|
||||||
### 20130919 13:20
|
### 20130919 13:20
|
||||||
|
|
||||||
The function 'source-fn' in package 'clojure.repl' returns, as a string, the source of the
|
The function 'source-fn' in package 'clojure.repl' returns, as a string, the source of the
|
||||||
function (or macro) whose name is passed to it as argument. It does this by checking the metadata
|
function (or macro) whose name is passed to it as argument. It does this by checking the metadata
|
||||||
associated with the function object using the 'meta' function. This metadata (if present) is a map
|
associated with the function object using the 'meta' function. This metadata (if present) is a map
|
||||||
containing the keys ':file' and ':line'. I'm guessing, therefore, that this metadata is set up while
|
containing the keys ':file' and ':line'. I'm guessing, therefore, that this metadata is set up while
|
||||||
reading the source file.
|
reading the source file.
|
||||||
|
|
||||||
The function 'read-string' can be used to parse a string into an S-expression. I'm taking it
|
The function 'read-string' can be used to parse a string into an S-expression. I'm taking it
|
||||||
as read that the string returned by source-fn will always be a single well-formed S-expression.
|
as read that the string returned by source-fn will always be a single well-formed S-expression.
|
||||||
|
|
||||||
As a first pass, I'll write a function sedit which takes an s-expression as argument, pretty prints
|
As a first pass, I'll write a function sedit which takes an s-expression as argument, pretty prints
|
||||||
it to the screen, and awaits a key stroke from the user. The following keys will be recognised:
|
it to the screen, and awaits a key stroke from the user. The following keys will be recognised:
|
||||||
|
|
||||||
* A: ['CAR'] call sedit recursively on the CAR of the current s-expression;
|
* A: ['CAR'] call sedit recursively on the CAR of the current s-expression;
|
||||||
return a cons of the result of this with the cdr of the current s-expression. Obviously, only
|
return a cons of the result of this with the cdr of the current s-expression. Obviously, only
|
||||||
available if the current s-expression is a list with at least one element.
|
available if the current s-expression is a list with at least one element.
|
||||||
* D: ['CDR'] call sedit recursively on the CDR of the current s-expression;
|
* D: ['CDR'] call sedit recursively on the CDR of the current s-expression;
|
||||||
return a cons of the CAR of the current s-expression with the result of this. Obviously, only
|
return a cons of the CAR of the current s-expression with the result of this. Obviously, only
|
||||||
available if the current s-expression is a list.
|
available if the current s-expression is a list.
|
||||||
* S: ['Substitute'] read a new s-expression from the user and return it in place of the
|
* S: ['Substitute'] read a new s-expression from the user and return it in place of the
|
||||||
current s-exression
|
current s-exression
|
||||||
* X: ['Cut'] return nil.
|
* X: ['Cut'] return nil.
|
||||||
|
|
||||||
### 20130920 10:37
|
### 20130920 10:37
|
||||||
|
|
||||||
Now sort-of working. One change can be made to an s-expression, and it can be made anywhere in the
|
Now sort-of working. One change can be made to an s-expression, and it can be made anywhere in the
|
||||||
s-expression. For some reason having made one change you can't then navigate further into the
|
s-expression. For some reason having made one change you can't then navigate further into the
|
||||||
s-expression to make another change; I suspect this is a lazy-evaluation problem, but I haven't yet
|
s-expression to make another change; I suspect this is a lazy-evaluation problem, but I haven't yet
|
||||||
fixed it.
|
fixed it.
|
||||||
|
|
||||||
Also, the 'clear screen' functionality is *extremely* crude, and you have to type a carriage return
|
Also, the 'clear screen' functionality is *extremely* crude, and you have to type a carriage return
|
||||||
after every command character, which slows down the user interaction badly. For a proof-of-concept
|
after every command character, which slows down the user interaction badly. For a proof-of-concept
|
||||||
demonstrator that isn't critical, but if anyone is actually going to use this thing it needs to be
|
demonstrator that isn't critical, but if anyone is actually going to use this thing it needs to be
|
||||||
fixed.
|
fixed.
|
||||||
|
|
||||||
I've written a wrapper round sedit called fedit, which grabs the source of a function from its
|
I've written a wrapper round sedit called fedit, which grabs the source of a function from its
|
||||||
metadata and passes it to sedit, attempting to redefine the function from the result; this fails for
|
metadata and passes it to sedit, attempting to redefine the function from the result; this fails for
|
||||||
the basic clojure 'all data is immutable' reason. But, when you invoke (use 'namespace :reload), it
|
the basic clojure 'all data is immutable' reason. But, when you invoke (use 'namespace :reload), it
|
||||||
is able to redefine all the functions, so I must be able to work out how this is done.
|
is able to redefine all the functions, so I must be able to work out how this is done.
|
||||||
|
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
|
@ -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))))
|
||||||
|
|
||||||
(defn prompt-and-read
|
|
||||||
|
(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
|
||||||
"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.
|
||||||
|
|
Loading…
Reference in a new issue