1421 lines
35 KiB
Common Lisp
1421 lines
35 KiB
Common Lisp
;
|
|
;; 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.6 2003/01/16 03:50:46 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)
|
|
)
|
|
)
|
|
)
|