summaryrefslogtreecommitdiff
path: root/lisp/modules/indent.lsp
diff options
context:
space:
mode:
authorKaleb Keithley <kaleb@freedesktop.org>2003-11-14 16:49:22 +0000
committerKaleb Keithley <kaleb@freedesktop.org>2003-11-14 16:49:22 +0000
commit0a193e032ba1ecf3f003e027e833dc9d274cb740 (patch)
treea1dcc00cb7f5d26e437e05e658c38fc323fe919d /lisp/modules/indent.lsp
Initial revision
Diffstat (limited to 'lisp/modules/indent.lsp')
-rw-r--r--lisp/modules/indent.lsp1420
1 files changed, 1420 insertions, 0 deletions
diff --git a/lisp/modules/indent.lsp b/lisp/modules/indent.lsp
new file mode 100644
index 0000000..4a7f7aa
--- /dev/null
+++ b/lisp/modules/indent.lsp
@@ -0,0 +1,1420 @@
+;
+;; Copyright (c) 2002 by The XFree86 Project, Inc.
+;;
+;; Permission is hereby granted, free of charge, to any person obtaining a
+;; copy of this software and associated documentation files (the "Software"),
+;; to deal in the Software without restriction, including without limitation
+;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
+;; and/or sell copies of the Software, and to permit persons to whom the
+;; Software is furnished to do so, subject to the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be included in
+;; all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+;; THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+;; OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+;;
+;; Except as contained in this notice, the name of the XFree86 Project shall
+;; not be used in advertising or otherwise to promote the sale, use or other
+;; dealings in this Software without prior written authorization from the
+;; XFree86 Project.
+;;
+;; Author: Paulo César Pereira de Andrade
+;;
+;;
+;; $XFree86: xc/programs/xedit/lisp/modules/indent.lsp,v 1.7 2003/01/29 03:05:53 paulo Exp $
+;;
+
+(provide "indent")
+(require "xedit")
+(in-package "XEDIT")
+
+(defconstant indent-spaces '(#\Tab #\Space))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; The final indentation function.
+;; Parameters:
+;; indent
+;; Number of spaces to insert
+;; offset
+;; Offset to where indentation should be added
+;; no-tabs
+;; If set, tabs aren't inserted
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun indent-text (indent offset &optional no-tabs
+ &aux start line length index current tabs spaces string
+ barrier base result (point (point))
+ )
+
+ ;; Initialize
+ (setq
+ start (scan offset :eol :left)
+ line (read-text start (- offset start))
+ length (length line)
+ index (1- length)
+ current 0
+ base 0
+ )
+
+ (and (minusp indent) (setq indent 0))
+
+ ;; Skip any spaces after offset, "paranoia check"
+ (while (member (char-after offset) indent-spaces)
+ (incf offset)
+ )
+
+ ;; Check if there are only spaces before `offset' and the line `start'
+ (while (and (>= index 0) (member (char line index) indent-spaces))
+ (decf index)
+ )
+
+ ;; `index' will be zero if there are only spaces in the `line'
+ (setq barrier (+ start (incf index)))
+
+ ;; Calculate `base' unmodifiable indentation, if any
+ (dotimes (i index)
+ (if (char= (char line i) #\Tab)
+ (incf base (- 8 (rem base 8)))
+ (incf base)
+ )
+ )
+
+ ;; If any non blank character would need to be deleted
+ (and (> base indent) (return-from indent-text nil))
+
+ ;; Calculate `current' indentation
+ (setq current base)
+ (while (< index length)
+ (if (char= (char line index) #\Tab)
+ (incf current (- 8 (rem current 8)))
+ (incf current)
+ )
+ (incf index)
+ )
+
+ ;; Maybe could also "optimize" the indentation even if it is already
+ ;; correct, removing spaces "inside" tabs.
+ (when (/= indent current)
+ (if no-tabs
+ (setq
+ length (- indent base)
+ result (+ barrier length)
+ string (make-string length :initial-element #\Space)
+ )
+ (progn
+ (multiple-value-setq (tabs spaces) (floor (- indent base) 8))
+ (setq
+ length (+ tabs spaces)
+ result (+ barrier length)
+ string (make-string length :initial-element #\Tab)
+ )
+ (fill string #\Space :start tabs)
+ )
+ )
+
+ (replace-text barrier offset string)
+ (and (>= offset point) (>= point barrier) (goto-char result))
+ )
+)
+(compile 'indent-text)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Helper function, returns indentation of a given offset
+;; If `align' is set, stop once a non blank character is seen, that
+;; is, use `offset' only as a line identifier
+;; If `resolve' is set, it means that the offset is just a hint, it
+;; maybe anywhere in the line
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun offset-indentation (offset &key resolve align
+ &aux
+ char
+ line
+ (start (scan offset :eol :left))
+ (indent 0))
+ (if resolve
+ (loop
+ (if (characterp (setq char (char-after start)))
+ (if (char= char #\Tab)
+ (incf indent (- 8 (rem indent 8)))
+ ;; Not a tab, check if is a space
+ (if (char= char #\Space)
+ (incf indent)
+ ;; Not a tab neither a space
+ (return indent)
+ )
+ )
+ ;; EOF found
+ (return indent)
+ )
+ ;; Increment offset to check next character
+ (incf start)
+ )
+ (progn
+ (setq line (read-text start (- offset start)))
+ (dotimes (i (length line) indent)
+ (if (char= (setq char (char line i)) #\Tab)
+ (incf indent (- 8 (rem indent 8)))
+ (progn
+ (or align (member char indent-spaces)
+ (return indent)
+ )
+ (incf indent)
+ )
+ )
+ )
+ )
+ )
+)
+(compile 'offset-indentation)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; A default/fallback indentation function, just copy indentation
+;; of previous line.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun default-indent (syntax syntable)
+ (let
+ (
+ (offset (scan (point) :eol :left))
+ start
+ left
+ right
+ )
+
+ syntable ;; XXX hack to not generate warning about unused
+ ;; variable, should be temporary (until unused
+ ;; variables can be declared as such)
+
+ (if
+ (or
+ ;; if indentation is disabled
+ (and
+ (hash-table-p (syntax-options syntax))
+ (gethash :disable-indent (syntax-options syntax))
+ )
+ ;; or if not at the start of a new line
+ (> (scan offset :eol :right) offset)
+ )
+ (return-from default-indent)
+ )
+
+ (setq left offset)
+ (loop
+ (setq
+ start left
+ left (scan start :eol :left :count 2)
+ right (scan left :eol :right)
+ )
+ ;; if start of file reached
+ (and (>= left start) (return))
+ (when
+ (setq
+ start
+ (position-if-not
+ #'(lambda (char) (member char indent-spaces))
+ (read-text left (- right left))
+ )
+ )
+
+ ;; indent the current line
+ (indent-text (offset-indentation (+ left start) :align t) offset)
+ (return)
+ )
+ )
+ )
+)
+(compile 'default-indent)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Helper function
+;; Clear line before cursor if it is empty
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun indent-clear-empty-line (&aux left offset right line index)
+ (setq
+ offset (scan (point) :eol :left)
+ left (scan offset :eol :left :count 2)
+ right (scan left :eol :right)
+ )
+
+ ;; If not at the first line in the file and line is not already empty
+ (when (and (/= offset left) (/= left right))
+ (setq
+ line (read-text left (- right left))
+ index (1- (length line))
+ )
+ (while (and (>= index 0) (member (char line index) indent-spaces))
+ (decf index)
+ )
+ ;; If line was only spaces
+ (and (minusp index) (replace-text left right ""))
+ )
+)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Macro to be called whenever an indentation rule decides that
+;; the parser is done.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defmacro indent-macro-terminate (&optional result)
+ `(return-from ind-terminate-block ,result)
+)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Like indent-terminate, but "rejects" the input for the current line
+;; and terminates the loop.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defmacro indent-macro-reject (&optional result)
+ `(progn
+ (setq ind-state ind-prev-state)
+ (return-from ind-terminate-block ,result)
+ )
+)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Like indent-reject, but "rejects" anything before the current token
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defmacro indent-macro-reject-left (&optional result)
+ `(progn
+ (setq ind-state ind-matches)
+ (return-from ind-terminate-block ,result)
+ )
+)
+
+
+(defstruct indtoken
+ regex ;; a string, character or regex
+ token ;; the resulting token, nil or a keyword
+ begin ;; begin a new table
+ switch ;; switch to another table
+ ;; begin and switch fields are used like the ones for the syntax highlight
+ ;; syntoken structure.
+ label ;; filed at compile time
+ code ;; code to execute when it matches
+)
+
+(defstruct indtable
+ label ;; a keyword, name of the table
+ tokens ;; list of indtoken structures
+ tables ;; list of indtable structures
+ augments ;; augment list
+)
+
+(defstruct indaugment
+ labels ;; list of keywords labeling tables
+)
+
+(defstruct indinit
+ variables ;; list of variables and optional initialization
+ ;; Format of variables must be suitable to LET*, example of call:
+ ;; (indinit
+ ;; var1 ;; initialized to NIL
+ ;; (var2 (afun)) ;; initialized to the value returned by AFUN
+ ;; )
+)
+
+(defstruct indreduce
+ token ;; reduced token
+ rules ;; list of rules
+ label ;; unique label associated with rule, this
+ ;; field is automatically filled in the
+ ;; compilation process. this field exists
+ ;; to allow several indreduce definitions
+ ;; that result in the same token
+ check ;; FORM evaluated, if T apply reduce rule
+ code ;; PROGN to be called when a rule matches
+)
+
+;; NOTE, unlike "reduce" rules, "resolve" rules cannot be duplicated
+(defstruct indresolve
+ match ;; the matched token (or a list of tokens)
+ code ;; PROGN to apply for this token
+)
+
+(defstruct indent
+ reduces ;; list of indreduce structures
+ tables ;; list of indtable structures
+ inits ;; initialization list
+ resolves ;; list of indresolve structures
+ token-code ;; code to execute when a token matches
+ check-code ;; code to execute before applying a reduce rule
+ reduce-code ;; code to execute after reduce rule
+ resolve-code ;; code to execute when matching a token
+)
+
+(defmacro defindent (variable label &rest lists)
+ `(if (boundp ',variable)
+ ,variable
+ (progn
+ (proclaim '(special ,variable))
+ (setq ,variable (compile-indent-table ,label ,@lists))
+ )
+ )
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Create an indent token.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defmacro indtoken (pattern token
+ &key icase nospec begin switch code (nosub t))
+ (setq pattern (re-comp (eval pattern) :icase icase :nospec nospec :nosub nosub))
+ (when (consp (re-exec pattern "" :notbol t :noteol t))
+ (error "INDTOKEN: regex ~A matches empty string" pattern)
+ )
+
+ ;; result of macro, return token structure
+ (make-indtoken
+ :regex pattern
+ :token token
+ :begin begin
+ :switch switch
+ :code code
+ )
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Create an indentation table. Basically a list of indentation tokens.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun indtable (label &rest definitions)
+ ;; check for simple errors
+ (unless (keywordp label)
+ (error "INDTABLE: ~A is not a keyword" label)
+ )
+ (dolist (item definitions)
+ (unless
+ (or
+ (atom item)
+ (indtoken-p item)
+ (indtable-p item)
+ (indaugment-p item)
+ )
+ (error "INDTABLE: invalid indent table argument ~A" item)
+ )
+ )
+
+ ;; return indent table structure
+ (make-indtable
+ :label label
+ :tokens (remove-if-not #'indtoken-p definitions)
+ :tables (remove-if-not #'indtable-p definitions)
+ :augments (remove-if-not #'indaugment-p definitions)
+ )
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Add identifier to list of augment tables.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun indaugment (&rest keywords)
+ (dolist (keyword keywords)
+ (unless (keywordp keyword)
+ (error "INDAUGMENT: bad indent table label ~A" keyword)
+ )
+ )
+
+ ;; return augment list structure
+ (make-indaugment :labels keywords)
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Add variables to initialization list
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defmacro indinit (&rest variables)
+ (make-indinit :variables variables)
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Create a "reduction rule"
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defmacro indreduce (token check rules &rest code &aux nullp consp)
+ ;; check for simple errors
+ (unless (or (keywordp token) (null token))
+ (error "INDREDUCE: ~A is not a keyword" token)
+ )
+ (dolist (rule rules)
+ (or (listp rule) (error "INDREDUCE: invalid indent rule ~A" rule))
+ ;; XXX This test is not enough, maybe should add some sort of
+ ;; runtime check to avoid circularity.
+ (and (eq token (car rule)) (null (cdr rule))
+ (error "INDREDUCE: ~A reduces to ~A" token)
+ )
+ (dolist (item rule)
+ (and (or nullp consp) (not (keywordp item))
+ (error "INDREDUCE: a keyword must special pattern")
+ )
+ (if (consp item)
+ (progn
+ (unless
+ (or
+ (and
+ (eq (car item) 'not)
+ (keywordp (cadr item))
+ (null (cddr item))
+ )
+ (and
+ (eq (car item) 'or)
+ (null (member-if-not #'keywordp (cdr item)))
+ )
+ )
+ (error "INDREDUCE: syntax error parsing ~A" item)
+ )
+ (setq consp t)
+ )
+ (progn
+ (setq nullp (null item) consp nil)
+ (unless (or (keywordp item) nullp (eq item t))
+ (error "INDREDUCE: ~A is not a keyword" item)
+ )
+ )
+ )
+ )
+; (and consp
+; (error "INDREDUCE: pattern must be followed by keyword")
+; )
+ )
+
+ ;; result of macro, return indent reduce structure
+ (make-indreduce
+ :token token
+ :check check
+ :rules (remove-if #'null rules)
+ :code code
+ )
+)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Create a "resolve rule"
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defmacro indresolve (match &rest code)
+ ;; check for simple errors
+ (if (consp match)
+ (dolist (token match)
+ (or (keywordp token) (error "INDRESOLVE: ~A is not a keyword" token))
+ )
+ (or (keywordp match) (error "INDRESOLVE: ~A is not a keyword" match))
+ )
+
+ ;; result of macro, return indent resolve structure
+ (make-indresolve
+ :match match
+ :code code
+ )
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Helper function for compile-indent-table. Returns a list of all
+;; tables and tokens for a given table, including tokens and tables
+;; of children.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun list-indtable-elements (table &aux result sub-result)
+ (setq result (cons (indtable-tokens table) (indtable-tables table)))
+ (dolist (child (indtable-tables table))
+ (setq sub-result (list-indtable-elements child))
+ (rplaca result (append (car result) (car sub-result)))
+ (rplacd result (append (cdr result) (cdr sub-result)))
+ )
+ ;; Return pair of all nested tokens and tables
+ result
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; First pass adding augumented tokens to a table, done in two passes
+;; to respect inheritance order.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun compile-indent-augment-list (table table-list &aux labels augment tokens)
+
+ ;; Create a list of all augment tables.
+ (dolist (augment (indtable-augments table))
+ (setq labels (append labels (indaugment-labels augment)))
+ )
+
+ ;; Remove duplicates and references to "itself", without warnings?
+ (setq
+ labels
+ (remove (indtable-label table) (remove-duplicates labels :from-end t))
+ )
+
+ ;; Check if the specified indent tables exists!
+ (dolist (label labels)
+ (unless
+ (setq augment (car (member label table-list :key #'indtable-label)))
+ (error "COMPILE-INDENT-AUGMENT-LIST: Cannot augment ~A in ~A"
+ label
+ (indtable-label table)
+ )
+ )
+
+ ;; Increase list of tokens.
+ (setq tokens (append tokens (indtable-tokens augment)))
+ )
+
+ ;; Store the tokens in the augment list. They will be added
+ ;; to the indent table in the second pass.
+ (setf (indtable-augments table) tokens)
+
+ ;; Recurse on every child table.
+ (dolist (child (indtable-tables table))
+ (compile-indent-augment-list child table-list)
+ )
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Last pass adding augmented tokens to a table.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun link-indent-augment-list (table)
+ (setf
+ (indtable-tokens table)
+ (remove-duplicates
+ (nconc (indtable-tokens table) (indtable-augments table))
+ :key #'indtoken-regex
+ :test #'equal
+ :from-end t
+ )
+
+ ;; Don't need to keep this list anymore.
+ (indtable-augments table)
+ ()
+ )
+
+ (dolist (child (indtable-tables table))
+ (link-indent-augment-list child)
+ )
+)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Compile the indent reduction rules
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun compile-indent-reduces (reduces
+ &aux need label check rules reduce
+ check-code reduce-code)
+ (dolist (item reduces)
+ (setq
+ label (indreduce-label item)
+ check (indreduce-check item)
+ rules (indreduce-rules item)
+ reduce (indreduce-code item)
+ need (and
+ rules
+ (not label)
+ (or
+ reduce
+ (null check)
+ (not (constantp check))
+ )
+ )
+ )
+ (when need
+ (and (null label) (setq label (intern (string (gensym)) 'keyword)))
+
+ (setf (indreduce-label item) label)
+
+ (and
+ (or (null check)
+ (not (constantp check))
+ )
+ (setq
+ check (list (list 'eq '*ind-label* label) check)
+ check-code (nconc check-code (list check))
+ )
+ )
+
+ (and reduce
+ (setq
+ reduce (cons (list 'eq '*ind-label* label) reduce)
+ reduce-code (nconc reduce-code (list reduce))
+ )
+ )
+ )
+ )
+
+ ;; XXX Instead of using COND, could/should use CASE
+ ;; TODO Implement a smart CASE in the bytecode compiler, if
+ ;; possible, should generate a hashtable, or a table
+ ;; of indexes (for example when all elements in the cases
+ ;; are characters) and then jump directly to the code.
+ (if check-code
+ (setq check-code (cons 'cond (nconc check-code '((t t)))))
+ (setq check-code t)
+ )
+ (and reduce-code (setq reduce-code (cons 'cond reduce-code)))
+
+ (values check-code reduce-code)
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Compile the indent resolve code
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun compile-indent-resolves (resolves &aux match resolve resolve-code)
+ (and
+ (/=
+ (length resolves)
+ (length (remove-duplicates resolves :key #'indresolve-match))
+ )
+ ;; XXX Could do a more complete job and tell what is wrong...
+ (error "COMPILE-INDENT-RESOLVES: duplicated labels")
+ )
+
+ (dolist (item resolves)
+ (when (setq resolve (indresolve-code item))
+ (setq
+ match
+ (indresolve-match item)
+
+ resolve
+ (cons
+ (if (listp match)
+ (list 'member '*ind-token* `',match :test `#'eq)
+ (list 'eq '*ind-token* match)
+ )
+ resolve
+ )
+
+ resolve-code
+ (nconc resolve-code (list resolve))
+ )
+ )
+ )
+
+ (and resolve-code (cons 'cond resolve-code))
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Create an indentation table
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun compile-indent-table (name &rest lists
+ &aux main elements switches begins tables symbols
+ label code token-code check-code reduce-code
+ (inits (remove-if-not #'indinit-p lists))
+ (reduces (remove-if-not #'indreduce-p lists))
+ (resolves (remove-if-not #'indresolve-p lists))
+ )
+ (setq
+ lists (delete-if
+ #'(lambda (object)
+ (or
+ (indinit-p object)
+ (indreduce-p object)
+ (indresolve-p object)
+ )
+ )
+ lists)
+ main (apply #'indtable name lists)
+ elements (list-indtable-elements main)
+ switches (remove-if #'null (car elements) :key #'indtoken-switch)
+ begins (remove-if #'null (car elements) :key #'indtoken-begin)
+ tables (cons main (cdr elements))
+ )
+
+ ;; Check for typos in the keywords, or for not defined indent tables.
+ (dolist (item (mapcar #'indtoken-switch switches))
+ (unless
+ (or (and (integerp item) (minusp item))
+ (member item tables :key #'indtable-label)
+ )
+ (error "COMPILE-INDENT-TABLE: SWITCH ~A cannot be matched" item)
+ )
+ )
+ (dolist (item (mapcar #'indtoken-begin begins))
+ (unless (member item tables :key #'indtable-label)
+ (error "COMPILE-INDENT-TABLE: BEGIN ~A cannot be matched" item)
+ )
+ )
+
+ ;; Build augment list.
+ (compile-indent-augment-list main tables)
+ (link-indent-augment-list main)
+
+ ;; Change switch and begin fields to point to the indent table
+ (dolist (item switches)
+ (if (keywordp (indtoken-switch item))
+ (setf
+ (indtoken-switch item)
+ (car (member (indtoken-switch item) tables :key #'indtable-label))
+ )
+ )
+ )
+ (dolist (item begins)
+ (setf
+ (indtoken-begin item)
+ (car (member (indtoken-begin item) tables :key #'indtable-label))
+ )
+ )
+
+ ;; Build initialization list
+ (dolist (init inits)
+ (setq symbols (nconc symbols (indinit-variables init)))
+ )
+
+ ;; Build token code
+ (dolist (item (car elements))
+ (when (setq code (indtoken-code item))
+ (setf
+ label
+ (intern (string (gensym)) 'keyword)
+
+ (indtoken-label item)
+ label
+
+ code
+ (list (list 'eq '*ind-label* label) code)
+
+ token-code
+ (nconc token-code (list code))
+ )
+ )
+ )
+
+ (multiple-value-setq
+ (check-code reduce-code)
+ (compile-indent-reduces reduces)
+ )
+
+ (make-indent
+ :tables tables
+ :inits symbols
+ :reduces reduces
+ :resolves resolves
+ :token-code (and token-code (cons 'cond token-code))
+ :check-code check-code
+ :reduce-code reduce-code
+ :resolve-code (compile-indent-resolves resolves)
+ )
+)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Search rule-pattern in match-pattern
+;; Returns offset of match, and it's length, if any
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun indent-search-rule (rule-pattern match-pattern
+ &aux start rule rulep matchp test offset length)
+ (if (member-if-not #'keywordp rule-pattern)
+ ;; rule has wildcards
+ (progn
+ (setq
+ rulep rule-pattern
+ matchp match-pattern
+ start match-pattern
+ )
+ (loop
+ (setq rule (car rulep))
+ (cond
+ ;; Special pattern
+ ((consp rule)
+ (if (eq (car rule) 'not)
+ (progn
+ (setq
+ test (cadr rule)
+ rulep (cdr rulep)
+ rule (car rulep)
+ )
+ (while
+ (and
+ ;; something to match
+ matchp
+ ;; NOT match is true
+ (not (eq (car matchp) test))
+ ;; next match is not true
+ (not (eq (car matchp) rule))
+ )
+ (setq matchp (cdr matchp))
+ )
+ (if (eq (car matchp) rule)
+ ;; rule matched
+ (setq
+ matchp (cdr matchp)
+ rulep (cdr rulep)
+ )
+ ;; failed
+ (setq
+ rulep rule-pattern
+ matchp (cdr start)
+ start matchp
+ )
+ )
+ )
+ ;; (eq (car rule) 'or)
+ (progn
+ (if (member (car matchp) (cdr rule) :test #'eq)
+ (setq rulep (cdr rulep) matchp (cdr matchp))
+ ;; failed
+ (progn
+ ;; end of match found!
+ (and (null matchp) (return))
+ ;; reset search
+ (setq
+ rulep rule-pattern
+ matchp (cdr start)
+ start matchp
+ )
+ )
+ )
+ )
+ )
+ )
+
+ ;; Skip until end of match-pattern or rule is found
+ ((null rule)
+ (setq rulep (cdr rulep))
+ ;; If matches everything
+ (if (null rulep)
+ (progn (setq matchp nil) (return))
+ ;; If next token cannot be matched
+ (unless
+ (setq
+ matchp
+ (member (car rulep) matchp :test #'eq)
+ )
+ (setq rulep rule-pattern)
+ (return)
+ )
+ )
+ (setq rulep (cdr rulep) matchp (cdr matchp))
+ )
+
+ ;; Matched
+ ((eq rule t)
+ ;; If there isn't a rule to skip
+ (and (null matchp) (return))
+ (setq rulep (cdr rulep) matchp (cdr matchp))
+ )
+
+ ;; Matched
+ ((eq rule (car matchp))
+ (setq rulep (cdr rulep) matchp (cdr matchp))
+ )
+
+ ;; No match
+ (t
+ ;; end of match found!
+ (and (null matchp) (return))
+ ;; reset search
+ (setq
+ rulep rule-pattern
+ matchp (cdr start)
+ start matchp
+ )
+ )
+ )
+
+ ;; if everything matched
+ (or rulep (return))
+ )
+
+ ;; All rules matched
+ (unless rulep
+ ;; Calculate offset and length of match
+ (setq offset 0 length 0)
+ (until (eq match-pattern start)
+ (setq
+ offset (1+ offset)
+ match-pattern (cdr match-pattern)
+ )
+ )
+ (until (eq match-pattern matchp)
+ (setq
+ length (1+ length)
+ match-pattern (cdr match-pattern)
+ )
+ )
+ )
+ )
+ ;; no wildcards
+ (and (setq offset (search rule-pattern match-pattern :test #'eq))
+ (setq length (length rule-pattern))
+ )
+ )
+
+ (values offset length)
+)
+(compile 'indent-search-rule)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Indentation parser
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defmacro indent-macro (ind-definition ind-offset &optional ind-no-tabs)
+ `(prog*
+ (
+ ;; Current indentation table
+ (ind-table (car (indent-tables ,ind-definition)))
+
+ ;; The parser rules
+ (ind-reduces (indent-reduces ,ind-definition))
+
+ ;; Token list for the table
+ (ind-tokens (indtable-tokens ind-table))
+
+ ;; Stack of nested tables/states
+ ind-stack
+
+ ;; indentation to be used
+ (*indent* 0)
+
+ ;; offset to apply indentation
+ *offset*
+
+ ;; Number of lines read
+ (*ind-lines* 1)
+
+ ;; Matched token
+ *ind-token*
+
+ ;; list of tokens after current match, should not be changed
+ *ind-token-list*
+
+ ;; label associated with rule
+ *ind-label*
+
+ ;; offset of match
+ *ind-offset*
+
+ ;; length of match
+ *ind-length*
+
+ ;; insert position
+ (*ind-point* (point))
+
+ (ind-from (scan ,ind-offset :eol :left))
+ (ind-to ,ind-offset)
+ (ind-line (read-text ind-from (- ind-to ind-from)))
+
+ ;; start of current line
+ (*ind-start* ind-from)
+
+ ;; State information
+ ind-state
+
+ ;; For use with (indent-macro-reject)
+ ind-prev-state
+
+ ;; Matches for the current line
+ ind-matches
+
+ ;; Matched tokens not yet used
+ ind-cache
+
+ ;; Pattern being tested
+ ind-token
+
+ ;; Used when searching for a regex
+ ind-match
+
+ ;; Table to change
+ ind-change
+
+ ;; Length of ind-line
+ (ind-length (length ind-line))
+
+ ;; Don't parse after this offset
+ (ind-end ind-length)
+
+ ;; Temporary variables used during loops
+ ind-left
+ ind-right
+ ind-tleft
+ ind-tright
+
+ ;; Set when start of file is found
+ ind-startp
+
+ ;; Flag for regex search
+ (ind-noteol (< ind-to (scan ind-from :eol :right)))
+
+ ;; Initialization variables expanded here
+ ,@(indent-inits (eval ind-definition))
+ )
+
+ ;; Initial input already read
+ (go :ind-loop)
+
+;------------------------------------------------------------------------
+; Read a text line
+:ind-read
+ (setq
+ ind-to ind-from
+ ind-from (scan ind-from :eol :left :count 2)
+ )
+ ;; If start of file reached
+ (and (= ind-to ind-from) (setq ind-startp t) (go :ind-process))
+
+ (setq
+ *ind-lines* (1+ *ind-lines*)
+ ind-to (scan ind-from :eol :right)
+ ind-line (read-text ind-from (- ind-to ind-from))
+ ind-length (length ind-line)
+ ind-end ind-length
+ ind-noteol nil
+ ind-cache nil
+ ind-prev-state ind-state
+ )
+
+;------------------------------------------------------------------------
+; Loop parsing backwards
+:ind-loop
+ (setq ind-matches nil)
+ (dolist (token ind-tokens)
+ ;; Prepare to loop
+ (setq
+ ind-token (indtoken-regex token)
+ ind-left 0
+ )
+ ;; While the pattern matches
+ (loop
+ (setq ind-right ind-left)
+ (if
+ (consp
+ (setq
+ ind-match
+ (re-exec
+ ind-token
+ ind-line
+ :start ind-left
+ :end ind-end
+ :notbol (> ind-left 0)
+ :noteol ind-noteol
+ )
+ )
+ )
+
+ ;; Remember about match
+ (setq
+ ind-match (car ind-match)
+ ind-left (cdr ind-match)
+ ind-matches (cons (cons token ind-match) ind-matches)
+ )
+
+ ;; No match
+ (return)
+ )
+ ;; matched an empty string
+ (and (= ind-left ind-right) (incf ind-left))
+
+ ;; matched a single eol or bol
+ (and (>= ind-left ind-end) (return))
+ )
+ )
+
+ ;; Add new matches to cache
+ (when ind-matches
+ (setq
+ ind-cache
+ (stable-sort
+ (nconc (nreverse ind-matches) ind-cache) #'< :key #'cadr
+ )
+ )
+ )
+
+ ;; If nothing in the cache
+ (or ind-cache (go :ind-process))
+
+ (setq
+ ind-left (cadar ind-cache)
+ ind-right (cddar ind-cache)
+ ind-matches (cdr ind-cache)
+ )
+
+ ;; If only one element in the cache
+ (or ind-matches (go :ind-parse))
+
+ (setq
+ ind-tleft (cadar ind-matches)
+ ind-tright (cddar ind-matches)
+ )
+
+ ;; Remove overlaps
+ (loop
+ (if (or (>= ind-tleft ind-right) (<= ind-tright ind-left))
+ ;; No overlap
+ (progn
+ (setq
+ ind-left ind-tleft
+ ind-right ind-tright
+ ind-matches (cdr ind-matches)
+ )
+ ;; If everything checked
+ (or ind-matches (return))
+ )
+ ;; Overlap found
+ (progn
+ (if (consp (cdr ind-matches))
+ ;; There are yet items to be checked
+ (progn
+ (rplaca ind-matches (cadr ind-matches))
+ (rplacd ind-matches (cddr ind-matches))
+ )
+ ;; Last item
+ (progn
+ (rplacd (last ind-cache 2) nil)
+ (return)
+ )
+ )
+ )
+ )
+
+ ;; Prepare for next check
+ (setq
+ ind-tleft (cadar ind-matches)
+ ind-tright (cddar ind-matches)
+ )
+ )
+
+;------------------------------------------------------------------------
+; Process the matched tokens
+:ind-parse
+ (setq ind-cache (nreverse ind-cache))
+
+:ind-parse-loop
+ (or (setq ind-match (car ind-cache)) (go :ind-process))
+
+ (setq
+ ind-cache (cdr ind-cache)
+ ind-token (car ind-match)
+ )
+
+ (or (member ind-token ind-tokens :test #'eq)
+ (go :ind-parse-loop)
+ )
+
+ ;; If a state should be added
+ (when (setq ind-change (indtoken-token ind-token))
+ (setq
+ ind-left (cadr ind-match)
+ ind-right (cddr ind-match)
+
+ *ind-offset*
+ (+ ind-from ind-left)
+
+ *ind-length*
+ (- ind-right ind-left)
+
+ ind-state
+ (cons
+ (cons ind-change (cons *ind-offset* *ind-length*))
+ ind-state
+ )
+
+ *ind-label*
+ (indtoken-label ind-token)
+ )
+
+ ;; Expand token code
+ ,(indent-token-code (eval ind-definition))
+ )
+
+ ;; Check if needs to switch to another table
+ (when (setq ind-change (indtoken-switch ind-token))
+ ;; Need to switch to a previous table
+ (if (integerp ind-change)
+ ;; Relative switch
+ (while (and ind-stack (minusp ind-change))
+ (setq
+ ind-table (pop ind-stack)
+ ind-change (1+ ind-change)
+ )
+ )
+ ;; Search table in the stack
+ (until
+ (or
+ (null ind-stack)
+ (eq
+ (setq ind-table (pop ind-stack))
+ ind-change
+ )
+ )
+ )
+ )
+
+ ;; If no match or stack became empty
+ (and (null ind-table)
+ (setq
+ ind-table
+ (car (indent-tables ,ind-definition))
+ )
+ )
+ )
+
+ ;; Check if needs to start a new table
+ ;; XXX use ind-tleft to reduce number of local variables
+ (when (setq ind-tleft (indtoken-begin ind-token))
+ (setq
+ ind-change ind-tleft
+ ind-stack (cons ind-table ind-stack)
+ ind-table ind-change
+ )
+ )
+
+ ;; If current "indent pattern table" changed
+ (when ind-change
+ (setq
+ ind-tokens (indtable-tokens ind-table)
+ ind-cache (nreverse ind-cache)
+ ind-end (cadr ind-match)
+ ind-noteol (> ind-length ind-end)
+ )
+ (go :ind-loop)
+ )
+
+ (and ind-cache (go :ind-parse-loop))
+
+;------------------------------------------------------------------------
+; Everything checked, process result
+:ind-process
+
+ ;; If stack is not empty, don't apply rules
+ (and ind-stack (not ind-startp) (go :ind-read))
+
+ (block ind-terminate-block
+ (setq ind-cache nil ind-tleft 0 ind-change (mapcar #'car ind-state))
+ (dolist (entry ind-reduces)
+ (setq
+ *ind-token* (indreduce-token entry)
+ *ind-label* (indreduce-label entry)
+ )
+ (dolist (rule (indreduce-rules entry))
+ (loop
+ ;; Check if reduction can be applied
+ (or
+ (multiple-value-setq
+ (ind-match ind-length)
+ (indent-search-rule rule ind-change)
+ )
+ (return)
+ )
+
+ (setq
+ ;; First element matched
+ ind-matches (nthcdr ind-match ind-state)
+
+ ;; Offset of match
+ *ind-offset* (cadar ind-matches)
+
+ *ind-token-list* (nthcdr ind-match ind-change)
+
+ ;; Length of match, note that *ind-length*
+ ;; Will be transformed to zero bellow if
+ ;; the rule is deleting entries.
+ *ind-length*
+ (if (> ind-length 1)
+ (progn
+ (setq
+ ;; XXX using ind-tright, to reduce
+ ;; number of local variables...
+ ind-tright
+ (nth (1- ind-length) ind-matches)
+
+ ind-right
+ (+ (cadr ind-tright)
+ (cddr ind-tright)
+ )
+ )
+ (- ind-right *ind-offset*)
+ )
+ (cddar ind-matches)
+ )
+ )
+
+ ;; XXX using ind-tleft as a counter, to reduce
+ ;; number of used variables...
+ (and (>= (incf ind-tleft) 1000)
+ ;; Should never apply so many reduce rules on
+ ;; every iteration, if needs to, something is
+ ;; wrong in the indentation definition...
+ (error "~D INDREDUCE iterations, ~
+ now checking (~A ~A)"
+ ind-tleft *ind-token* rule
+ )
+ )
+
+ ;; Check if should apply the reduction
+ (or
+ ;; Expand check code
+ ,(indent-check-code (eval ind-definition))
+ (return)
+ )
+
+ (if (null *ind-token*)
+ ;; Remove match
+ (progn
+ (setq *ind-length* 0)
+ (if (= ind-match 0)
+ ;; Matched the first entry
+ (setq
+ ind-state
+ (nthcdr ind-length ind-matches)
+ )
+ (progn
+ (setq
+ ind-matches
+ (nthcdr (1- ind-match) ind-state)
+ )
+ (rplacd
+ ind-matches
+ (nthcdr (1+ ind-length) ind-matches)
+ )
+ )
+ )
+ )
+
+ ;; Substitute/simplify
+ (progn
+ (rplaca (car ind-matches) *ind-token*)
+ (when (> ind-length 1)
+ (rplacd (cdar ind-matches) *ind-length*)
+ (rplacd
+ ind-matches
+ (nthcdr ind-length ind-matches)
+ )
+ )
+ )
+ )
+ (setq
+ ind-cache t
+ ind-change (mapcar #'car ind-state)
+ )
+
+ ;; Expand reduce code
+ ,(indent-reduce-code (eval ind-definition))
+ )
+ )
+ )
+
+ ;; ind-cache will be T if at least one change was done
+ (and ind-cache (go :ind-process))
+
+ ;; Start of file reached
+ (or ind-startp (go :ind-read))
+
+ ) ;; end of ind-terminate-block
+
+
+ (block ind-terminate-block
+ (setq *ind-token-list* (mapcar #'car ind-state))
+ (dolist (item ind-state)
+ (setq
+ *ind-token* (car item)
+ *ind-offset* (cadr item)
+ *ind-length* (cddr item)
+ )
+ ;; Expand resolve code
+ ,(indent-resolve-code (eval ind-definition))
+ (setq *ind-token-list* (cdr *ind-token-list*))
+ )
+ )
+
+ (and (integerp *indent*)
+ (integerp *offset*)
+ (indent-text *indent* *offset* ,ind-no-tabs)
+ )
+ )
+)