. . . .

Lisp-Emacs Examples

 

updated:2016.07.13

Prev   Next   Site Map   Home  
Text Size
Please reload page for style change

WHY LISP?

LISP is rarely used for commercial software but teaches some interesting concepts and is the language of EMACS, which I use quite often. Unlike all other languages, which encourage individual case myopia, LISP is the embodiment of mathematical induction, which is the only means of proving properties of infinite sets. That such an esoteric concept can be a useful general-purpose language is a testament to the maxim that good programming is the merging of theory and practice.

LISP originally had a reputation for being slow. One reason for this was that it was always interpreted and not compiled or even compressed into byte-codes. Also, implementations were very pure in their treatment of operators as functions (anticipating C++ operator overloading by 30 years) eliminating most of the avenues available to optimize generated code. Further, at the tail end of processing a list, interpreters would climb back out to the head the same way they came in even if they had nothing useful to do. When LISP finally got some compilers (especially in the Scheme dialect developed by Texas Instruments) with intrinsic operators and tail recursion elimination, its performance was comparable to more “practical” languages.

LISP is a good choice for a scripting language embedded in an application. Its very simple grammar makes parsing trivial. When speed is not a significant issue, its pure form of operators as functions and intrinsic induction support and encourage extension. For example, at its core EMACS is a LISP interpreter. On top of this is a layer of basic utilities, all written in LISP. Layers above this provide specific application support. EMACS is known primarily as an editor but, in fact, can be a complete programming IDE, a web browser, a computer user interface, and anything else we would like. It is a unique experience to modify EMACS within EMACS and immediately see the effect. GDB can be used within EMACS, essentially turning the debug session into a document with the power of a real editor to format data and immediately create specialized debugging functions. All of this occurs within a unified framework and language. The only IDE even remotely comparable is Eclipse. But EMACS starts up in seconds whereas waiting for Eclipse to start usually entails a coffee break.

EMACS USER-DEFINED BACKUP

;----------------------------------------------------------------------	
;; Put backups (~) in home/bak
(setq backup-directory-alist `(("." . "~/bak/"))) 

;; Make auto-save files userhome/bak/#lastDir-fileName#"
(setq auto-save-file-name-transforms 
`(("\\(.:\\)*\\(\\([^/]*\\)/\\)*\\(.+\\)" , "~/bak/\\3-\\4" nil))) 

(setq backup-file-name-transforms 
`((".*" "\4%~/bak/"))) ; userHome%bak%firstDir%lastDir%fileName
;`(("\\(.:\\)*\\(\\([^/]*\\)/\\)*\\(.+\\)" "~/bak/\\3-\\4")))
                               ; userHome/bak/lastDir-fileName

;---------------------------- files-make-user-back-tm --------------------------
; Purpose: subroutine of files-make-user-back-name. Synthesize backup name 
; according to selected transform mode. This supports transforms that cannot be 
; defined by regular expression. 
; Returns: string backup file name
; Arguments: 
;- file is string actual file+path
;- repl string defines the replacement. This is the second item in a transform
; list. The first character is a small literal integer (e.g. 0, not '0') which 
; selects the mode. The second character is the path separator. The remainder is
; the path to the backup directory root. For all transform modes, the name is 
; composed from the given root path + a mode-specific string derived from the 
; real file path + the file name.
; The mode-specific strings are (by mode):
; 0 = empty, i.e. the name is path+sep+fileName
; For all other modes under DOS/Windows the unembellished drive letter is always
; included. 
; 1 = first two chars of each dir
; 2 = lastDir
; 3 = first two chars of each dir + lastDir
; 4 = firstDir + lastDir
; 5 = firstDir + first two chars of each dir + lastDir
; Modes 0 and 2 are for convenience, as the same result could be achieve with 
; standard regular expressions.
;...............................................................................
(defun files-make-user-back-tm ( file repl )
    (let ( (tm (string-to-char repl))
       (pathSep (substring repl 1 2))
       (path (substring repl 2))
       (last -1)
       (nextIdx 0) 
       cnt name sep )

    ;; Determine the index of the last directory in the file path.
    (set-match-data nil t)
    (while (string-match "\\(.:\\)*\\(\\([^/]*\\)/\\)" file (match-end 0))
        (setq last (1+ last)))

    ;; Get the file name
    (string-match ".*" file (match-end 0))
    (setq name (match-string 0 file))

    ;; If transform mode 0 then return simple path + name. Else rescan file 
    ;; string to build more complex path name.
    (if (> tm 0)
        (progn
        (set-match-data nil t)
        (setq cnt 0)
        ;; At cnt 0 under WinDOS see drive letter; under Linux see / so 
        ;; the first named directory is always at cnt 1.
        (while (string-match "\\(.:\\)*\\(\\([^/]*\\)/\\)" file (match-end 0))
            (cond 
            ((match-string 1 file) ; Windows/DOS drive letter.
                (setq path (concat path 
                    (substring (match-string 1 file) 0 1 ) pathSep)))

            ((not (string= (match-string 3 file) "")) ; directory name
                (setq sep pathSep) ; Reset default so only blank case has to deal with it.
                (setq path (concat path 
                 (cond
                ;; Full name cases
                ((or (and (= cnt 1) (> tm 3)) 
                    (and (= cnt last) (> tm 1))) 
                    (match-string 3 file)) 
                ;; Two-char name cases
                ((= 1 (logand 1 tm))(substring (match-string 3 file) 0 2))
                ;; Everything else is no name and no separator (blank case)
                (t (setq sep ""))) ; cond
                sep))) ; cond directory name

            ;; Neither drive nor directory, i.e. must be first / in Unix.
            ) ; cond
            (setq cnt (1+ cnt)))))
    (concat path name)))

;------------------------- files-make-user-back-name ---------------------------
; Purpose: synthesize a backup file path+name given the real path+name and the
; user-defined transform list. This can be used for both backup and autosave
; files, as it does not add the # or ~ characters and the transform list is an
; argument. For backup files, the transform list is normally defined by 
; backup-file-name-transforms, but that is not a default here. The caller must
; provide the argument.
; Returns: string backup file name or nil if none of the transforms matches file 
; Arguments:
;- file is string actual path+file
;- transforms is list of transform lists, each comprising two regular 
; expressions. If the first matches file, the second is used to create the 
; backup name. Each transform is tested until a match is found or transforms is 
; exhausted. The match expression serves two purposes. It enables location- or 
; file-specific transforms, for example, routing priviledged file backups to a 
; non-public directory. It also identifies components of file for reference by 
; the replacement expression. The replacement expression can take two general 
; forms. If the first character has an ordinal value less than 10, this value 
; selects a predefined transform mode; the second character will be used as a 
; path separator; and the remainder is the backup root directory. Otherwise,the 
; replacement expression is an ordinary RE.
; (("\\(.:\\)*\\(\\([^/]*\\)/\\)*\\(.+\\)" "~/bak/\\3-\\4")) contains 
; one transform, which synthesizes the name userhome/bak/lastRealDir-fileName. 
; ((".*" "\2-~/bak/")) uses transform mode 2 to produce the same results under
; Unix but slightly different results, due to drive letter, under DOS/Windows.
;...............................................................................
(defun files-make-user-back-name ( file transforms )
    "Subroutine of make-backup-file-name-1"
    ( let (result repl) 
    (while (and transforms (not result))
        (if (string-match (car (car transforms)) file)
            (setq result (if (< (string-to-char 
                (setq repl (cadr (car transforms)))) 10)
                (files-make-user-back-tm file repl)
                (replace-match repl t nil file)))) 
        (setq transforms (cdr transforms)))         
    result))                            

(defun make-backup-file-name-1 (file)
    "Subroutine of `make-backup-file-name' and `find-backup-file-name'."
    (let ((alist backup-directory-alist)
         elt backup-directory abs-backup-directory)
    (while alist
        (setq elt (pop alist))
        (if (string-match (car elt) file)
        (setq backup-directory (cdr elt)
            alist nil)))
    ;; If backup-directory is relative, it should be relative to the
    ;; file's directory.  By expanding explicitly here, we avoid
    ;; depending on default-directory.
    (if backup-directory
        (setq abs-backup-directory
        (expand-file-name backup-directory
            (file-name-directory file))))

    (if (and abs-backup-directory (not (file-exists-p abs-backup-directory)))
        (condition-case nil
        (make-directory abs-backup-directory 'parents)
        (file-error (setq backup-directory nil
                abs-backup-directory nil))))

    (if (null backup-directory)
        file
        (if (file-name-absolute-p backup-directory)
        (if (boundp 'backup-file-name-transforms)
            (setq file (files-make-user-back-name file 
                   backup-file-name-transforms))
            ;; else backup-save-file-name-transforms is not defined
            (when (memq system-type '(windows-nt ms-dos cygwin))
            ;; Normalize DOSish file names: downcase the drive
            ;; letter, if any, and replace the leading "x:" with
            ;; "/drive_x".
            (or (file-name-absolute-p file)
                (setq file (expand-file-name file))) ; make defaults explicit
            ;; Replace any invalid file-name characters (for the
            ;; case of backing up remote files).
            (setq file (expand-file-name (convert-standard-filename file)))
            (if (eq (aref file 1) ?:)
                (setq file (concat "/"
                       "drive_"
                       (char-to-string (downcase (aref file 0)))
                       (if (eq (aref file 2) ?/)
                           ""
                           "/")
                       (substring file 2)))))
            ;; Make the name unique by substituting directory
            ;; separators.  It may not really be worth bothering about
            ;; doubling `!'s in the original name...
            (expand-file-name
            (subst-char-in-string
                ?/ ?!
                (replace-regexp-in-string "!" "!!" file))
            backup-directory))

        (expand-file-name (file-name-nondirectory file)
            (file-name-as-directory abs-backup-directory))))))
;---------------------------------------------------------------------

A common complaint of emacs users is that it clutters directories with visible backup (~) and auto-save (#) files. Backup files are always located in the same directory as the the file they refer to. By default, this is also where emacs stores auto-save files but this can be changed by redefining auto-save-file-name-transforms. However, this only supports changing the root, to which emacs appends the absolute path and name of the original file, which may produce a path that exceeds the limits of the OS, crashing emacs. One way to reduce the likelihood of this is to define a transform using only pieces of the path. For example ("\\(.:\\)*\\(\\([^/]*\\)/\\)*\\(.+\\)" "~/bak/\\3!\\4") creates userHome/bak/lastDir!fileName. However, this may not be sufficiently distinctive. For example, vers1/src/file.cfg and vers2/src/file.cfg would have the same auto-save/backup file. The chances of a collision could be reduced significantly without going to the extreme of full path name by including additional pieces of the path. Elisp regular expressions cannot generally do this because they support back reference only to the last instance of a repeating clause ("\\3" in the example refers to the last directory in the path).

I developed a general solution and specific options for a user-defined backup directory structure for both auto-save and backup files. In general, the user can select one of several built-in transform modes. The specific modes that I have implemented are combinations of full directory names and just the first two characters. For example, one mode uses the full first and last directory names with just the first two characters of each intervening directory. An additional problem that I address is that the use of '!' as directory separator may confuse some programs. I allow any character to be selected for the separator. The selectable transform mode and separator are merged into a variant of the transform replacement regular expression. If the first character's ordinal is less than 10 then it selects a transform mode, the next character is the separator, and the remainder is the backup root directory.


EMACS LISP JUSTIFY REGION

;---------------------------------------------------------------------------
(defun justify-region( pfstring )
    "Word-wrap region at fill-column, applying optional prefix at left. The prefix 
may be nothing or one or more characters.Existing instances of the prefix on 
the left edge are removed before formatting."
    (interactive "sPrefix string: " )
    (let ((length (string-width pfstring)) count)
    (if mark-active
        (if (> (point) (mark))
        (exchange-point-and-mark))
            ; Else no region marked. Treat cursor line as region.
        (end-of-line)
        (set-mark (point))
        (beginning-of-line))

    ;; If the first line doesn't have the prefix string then insert it now.
    (if (not (looking-at pfstring))
        (insert pfstring))
    
    (catch 'end
        (while t
        (end-of-line)
;; Reduction loop deletes newlines while EOL < fill-column. At each reduction, 
;; if we see the prefix string, it is removed because it was at the beginning 
;; of the line before the newline was deleted.
        (while (< (current-column) (1- fill-column)) ; Reduction loop.
            (if (>= (point) (- (mark) 2))
            (progn 
                (forward-char 1) ;Leave point at beginning of next line.
                (pop-mark) ; Unmarks region for the few cases that don't.
                (throw 'end 1))
            (delete-char 1)
            (if (looking-at pfstring)
                (delete-char length)) ;kill-word goes too far if pfstring length = 1.
            (fixup-whitespace)    ; Normalize to one space.
            (end-of-line)))       ; Close reduction loop.

;; Expansion loop steps back from EOL one word at a time until the point is on 
;; a word that doesn't cross over the fill-column and breaks the line after 
;; that by inserting newline (unless starting point was already less than fill- 
;; column). The prefix string is inserted on next line if not already there.
        (setq count 0)
        (while (> (current-column) fill-column) ; Expansion loop.
            (backward-word 1)
            (setq count (1+ count)))

;; If the word we intend to move down to the next line is immediately preceded 
;; by ", ', `, (, or < then bring that along as well. If preceded by - itself 
;; preceded by space then bring the - character along. This represents -word as 
;; opposed to word-word. If preceded by the combination </ then bring that. 
;; This is for HTML but shouldn't be a problem for other languages. 
        (cond 
            ((looking-back "</")
            (backward-char 2))
            ((looking-back "[\"\'\`(<]")
            (backward-char 1))
            ((looking-back "[ \t]-")
            (backward-char 1)))

        (if ( > count 0)
            (insert ?\n)
            (forward-char 1))
        (if (not (looking-at pfstring))
            (insert pfstring))))))
;--------------------------------------------------------------------

Emacs supports almost unlimited line length, automatically wrapping at the right edge to fit the display window. However, long lines don't print or copy-paste properly into other documents. The standard function fill-paragraph reformats a region with right margin limit but it affords limited flexibility in handling the left margin of program comment blocks. If it recognizes the language, such as C, it can automatically manage a leading comment character but this is not reliable and does not accept alternatives, for example "rem " in bat code.

My justify-region function correctly reformats text blocks for right margin limit with any given character sequence (including leading, internal, and trailing spaces) as a left edge prefix. The prefix comprises 0 or more characters, for example ;;; or ;; or ; for comment blocks of varying scope in LISP, // for C++, rem for Windows batch, or # for sh and make.

This may be called non-interactively with the prefix string argument pfstring. Interactively, the user is asked for pfstring. This is used exactly. If it contains no trailing space, there will be no space between it and the text. Typically, pfstring is the language's comment character or string followed by one space. Instances of pfstring that appear at the beginning of a line are considered to be discardable. Any that appear elsewhere are considered part of the text and are not disturbed.

The function comprises two loops, reduction and expansion, applied to each line from top to bottom of the region. The reduction loop merges the next line with the current one by removing the newline and pfstring (if any) at the beginning of the next line. This continues until it produces a line that crosses the fill column point. Then the expansion loop traverses this line from the end until it finds a whole word that does not cross the fill-column. The line is split between this and the next word after accounting for certain character combinations that belong with the next word. For example, word-word splits to word- and word, but -word cannot split to - and word.




Prev   Next   Site Map   Home   Top   Valid HTML   Valid CSS